import {
  DynamicModule,
  Global,
  Module,
  ModuleMetadata,
  Provider,
  Type,
} from '@nestjs/common';
import { KyselyConfig } from 'kysely';
import { createKyselyProvider } from './kysely-client.factory';

export const KYSELY_CLIENT_PROVIDER_TOKEN = 'KyselyClientProviderToken';
export const KYSELY_CONFIG_TOKEN = 'KYSELY_CONFIG_TOKEN';

export interface KyselyConfigFactory {
  createKyselyConfig(): Promise<KyselyConfig> | KyselyConfig;
}

export interface KyselyModuleAsyncOptions
  extends Pick<ModuleMetadata, 'imports'> {
  isGlobal?: boolean;
  useExisting?: Type<KyselyConfigFactory>;
  useClass?: Type<KyselyConfigFactory>;
  useFactory?: (
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ...args: any[]
  ) => KyselyConfig;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  inject?: any[];
}

@Global()
@Module({})
export class DatabaseModule {
  public static forRoot(config: KyselyConfig): DynamicModule {
    const provider: Provider = createKyselyProvider(config);

    return {
      module: DatabaseModule,
      exports: [provider],
      providers: [provider],
    };
  }

  public static forRootAsync(options: KyselyModuleAsyncOptions): DynamicModule {
    return {
      global: options.isGlobal,
      module: DatabaseModule,
      imports: options.imports || [],
      providers: this.createAsyncProviders(options),
    };
  }

  private static createAsyncProviders(
    options: KyselyModuleAsyncOptions,
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return this.createAsyncOptionsProvider(options);
    }

    if (options.useClass) {
      return [
        ...this.createAsyncOptionsProvider(options),
        {
          provide: options.useClass,
          useClass: options.useClass,
        },
      ];
    }

    return [...this.createAsyncOptionsProvider(options)];
  }

  private static createAsyncOptionsProvider(
    options: KyselyModuleAsyncOptions,
  ): Provider[] {
    if (options.useFactory) {
      const config = options.useFactory(options.inject);
      return [
        // {
        //   provide: KYSELY_CONFIG_TOKEN,
        //   useFactory: options.useFactory,
        //   inject: options.inject || [],
        // },
        {
          provide: KYSELY_CLIENT_PROVIDER_TOKEN,
          useExisting: createKyselyProvider(config)
        }
      ];
    }

    if (options.useExisting) {
      return [
        {
          provide: KYSELY_CONFIG_TOKEN,
          useFactory: async (optionsFactory: KyselyConfigFactory) =>
            await optionsFactory.createKyselyConfig(),
          inject: [options.useExisting],
        },
      ];
    }

    if (options.useClass) {
      return [
        {
          provide: KYSELY_CONFIG_TOKEN,
          useFactory: async (optionsFactory: KyselyConfigFactory) =>
            await optionsFactory.createKyselyConfig(),
          inject: [options.useClass],
        },
      ];
    }

    return [
      {
        provide: KYSELY_CONFIG_TOKEN,
        useFactory: async (optionsFactory: KyselyConfigFactory) =>
          await optionsFactory.createKyselyConfig(),
        inject: [],
      },
    ];
  }
}
