import { expect } from 'chai';
import { UnknownDependenciesException } from '../../../errors/exceptions/unknown-dependencies.exception';
import {
  INVALID_MODULE_MESSAGE,
  UNDEFINED_MODULE_MESSAGE,
  UNKNOWN_EXPORT_MESSAGE,
} from '../../../errors/messages';
import { Module } from '../../../injector/module';
import { stringCleaner } from '../../utils/string.cleaner';

describe('Error Messages', () => {
  const CatsModule = { name: 'CatsModule' };
  const AppModule = { name: 'AppModule' };

  describe('UNKNOWN_DEPENDENCIES_MESSAGE', () => {
    const index = 0;
    it('should display class', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?, CatService). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
      @Module({
        imports: [ /* the Module containing dependency */ ]
      })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      class CatService {}

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatService', {
          index,
          dependencies: ['', CatService],
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the provide token', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?, MY_TOKEN). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
      @Module({
      imports: [ /* the Module containing dependency */ ]
      })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatService', {
          index,
          dependencies: ['', 'MY_TOKEN'],
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the provide token as double-quoted string for string-based tokens', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?). Please make sure that the argument "FooRepository" at index [0] is available in the current context.

      Potential solutions:
      - If "FooRepository" is a provider, is it part of the current Module?
      - If "FooRepository" is exported from a separate @Module, is that module imported within Module?
      @Module({
      imports: [ /* the Module containing "FooRepository" */ ]
      })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatService', {
          index: 0,
          dependencies: ['FooRepository'],
          name: 'FooRepository',
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the function name', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?, CatFunction). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
      @Module({
        imports: [ /* the Module containing dependency */ ]
      })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      function CatFunction() {}
      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatService', {
          index,
          dependencies: ['', CatFunction],
        }).message,
      );
      expect(actualMessage).to.equal(expectedResult);
    });
    it('should use "+" if unknown dependency name', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?, +). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
        @Module({
          imports: [ /* the Module containing dependency */ ]
        })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatService', {
          index,
          dependencies: ['', undefined!],
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the module name', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatService (?, MY_TOKEN). Please make sure that the argument dependency at index [0] is available in the TestModule context.

      Potential solutions:
      - Is TestModule a valid NestJS module?
      - If dependency is a provider, is it part of the current TestModule?
      - If dependency is exported from a separate @Module, is that module imported within TestModule?
        @Module({
          imports: [ /* the Module containing dependency */ ]
        })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      class MetaType {
        name: string;
      }
      class TestModule {
        metatype: MetaType;
      }
      const myModule = new TestModule();
      const myMetaType = new MetaType();
      myMetaType.name = 'TestModule';
      myModule.metatype = myMetaType;

      const actualMessage = stringCleaner(
        new UnknownDependenciesException(
          'CatService',
          { index, dependencies: ['', 'MY_TOKEN'] },
          myModule as Module,
        ).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the symbol name of the provider', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the Symbol(CatProvider) (?). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
        @Module({
          imports: [ /* the Module containing dependency */ ]
        })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException(Symbol('CatProvider'), {
          index,
          dependencies: [''],
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should display the symbol dependency of the provider', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the CatProvider (?, Symbol(DogProvider)). Please make sure that the argument dependency at index [0] is available in the current context.

      Potential solutions:
      - If dependency is a provider, is it part of the current Module?
      - If dependency is exported from a separate @Module, is that module imported within Module?
        @Module({
          imports: [ /* the Module containing dependency */ ]
        })

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('CatProvider', {
          index,
          dependencies: ['', Symbol('DogProvider')],
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should detect likely import type issue and provide specific guidance', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the ResourceController (ResourceService, ?). Please make sure that the argument dependency at index [1] is available in the current context.

      Potential solutions:
      - The dependency at index [1] appears to be undefined at runtime
      - This commonly occurs when using 'import type' instead of 'import' for injectable classes
      - Check your imports and change:
        ❌ import type { SomeService } from './some.service';
        ✅ import { SomeService } from './some.service';
      - Ensure the imported class is decorated with @Injectable() or is a valid provider
      - If using dynamic imports, ensure the class is available at runtime, not just for type checking

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('ResourceController', {
          index: 1,
          dependencies: ['ResourceService', Object], // Object simulates import type issue
          name: 'SomeService',
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should detect import type issue with mixed dependencies', () => {
      const expectedResult =
        stringCleaner(`Nest can't resolve dependencies of the ResourceController (ValidService, ?, AnotherService). Please make sure that the argument dependency at index [1] is available in the current context.

      Potential solutions:
      - The dependency at index [1] appears to be undefined at runtime
      - This commonly occurs when using 'import type' instead of 'import' for injectable classes
      - Check your imports and change:
        ❌ import type { SomeService } from './some.service';
        ✅ import { SomeService } from './some.service';
      - Ensure the imported class is decorated with @Injectable() or is a valid provider
      - If using dynamic imports, ensure the class is available at runtime, not just for type checking

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      class ValidService {}
      class AnotherService {}

      const actualMessage = stringCleaner(
        new UnknownDependenciesException('ResourceController', {
          index: 1,
          dependencies: [ValidService, Object, AnotherService], // mixed valid/Object
          name: 'SomeService',
        }).message,
      );

      expect(actualMessage).to.equal(expectedResult);
    });
    it('should add documentation links to export errors', () => {
      const expectedResult =
        stringCleaner(`Nest cannot export a provider/module that is not a part of the currently processed module (TestModule). Please verify whether the exported TestService is available in this particular context.

      Possible Solutions:
      - Is TestService part of the relevant providers/imports within TestModule?

      For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
      `);

      const actualMessage = stringCleaner(
        UNKNOWN_EXPORT_MESSAGE('TestService', 'TestModule'),
      );

      expect(actualMessage).to.equal(expectedResult);
    });
  });

  describe('UNDEFINED_MODULE_EXCEPTION', () => {
    it('should display the module name with the undefined index and scope', () => {
      const expectedMessage =
        stringCleaner(`Nest cannot create the CatsModule instance.
The module at index [0] of the CatsModule "imports" array is undefined.

Potential causes:
- A circular dependency between modules. Use forwardRef() to avoid it. Read more: https://docs.nestjs.com/fundamentals/circular-dependency
- The module at index [0] is of type "undefined". Check your import statements and the type of the module.

Scope [AppModule -> CatsModule]`);

      const actualMessage = stringCleaner(
        UNDEFINED_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule]),
      );

      expect(actualMessage).to.be.eq(expectedMessage);
    });
  });

  describe('INVALID_MODULE_MESSAGE', () => {
    it('should display the module name with the invalid index and scope', () => {
      const expectedMessage =
        stringCleaner(`Nest cannot create the CatsModule instance.
Received an unexpected value at index [0] of the CatsModule "imports" array.

Scope [AppModule -> CatsModule]`);

      const actualMessage = stringCleaner(
        INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule]),
      );

      expect(actualMessage).to.be.eq(expectedMessage);
    });
  });
});
