import { Module, DynamicModule, Provider, Global } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

import { AnalyticsClient } from '../analytics-client';

import { ANALYTICS_CONFIG, ANALYTICS_CLIENT } from './constants';
import { AnalyticsInterceptor } from './analytics-interceptor';

import type {
  AnalyticsModuleOptions,
  AnalyticsModuleAsyncOptions,
  AnalyticsOptionsFactory,
} from './types';

/**
 * NestJS module for analytics client integration.
 *
 * Provides dependency injection for AnalyticsClient and optional global interceptor
 * for automatic tracking of decorated methods.
 *
 * @example
 * ```typescript
 * // Synchronous configuration
 * @Module({
 *   imports: [
 *     AnalyticsModule.forRoot({
 *       apiBaseUrl: 'http://localhost:3000',
 *       appName: 'my-service',
 *       enableGlobalInterceptor: true,
 *     }),
 *   ],
 * })
 * export class AppModule {}
 *
 * // Async configuration with ConfigService
 * @Module({
 *   imports: [
 *     AnalyticsModule.forRootAsync({
 *       imports: [ConfigModule],
 *       useFactory: (config: ConfigService) => ({
 *         apiBaseUrl: config.get('ANALYTICS_API_URL'),
 *         appName: config.get('APP_NAME'),
 *         enableGlobalInterceptor: true,
 *       }),
 *       inject: [ConfigService],
 *     }),
 *   ],
 * })
 * export class AppModule {}
 * ```
 */
@Global()
@Module({})
export class AnalyticsModule {
  /**
   * Configure the analytics module with synchronous options
   */
  static forRoot(options: AnalyticsModuleOptions): DynamicModule {
    const analyticsConfigProvider: Provider = {
      provide: ANALYTICS_CONFIG,
      useValue: options,
    };

    const analyticsClientProvider: Provider = {
      provide: ANALYTICS_CLIENT,
      useFactory: (config: AnalyticsModuleOptions) => {
        return new AnalyticsClient(config);
      },
      inject: [ANALYTICS_CONFIG],
    };

    const providers: Provider[] = [
      analyticsConfigProvider,
      analyticsClientProvider,
    ];

    // Add global interceptor if enabled
    if (options.enableGlobalInterceptor) {
      providers.push({
        provide: APP_INTERCEPTOR,
        useClass: AnalyticsInterceptor,
      });
    }

    return {
      module: AnalyticsModule,
      providers,
      exports: [ANALYTICS_CLIENT, ANALYTICS_CONFIG],
    };
  }

  /**
   * Configure the analytics module with async options
   */
  static forRootAsync(options: AnalyticsModuleAsyncOptions): DynamicModule {
    const analyticsClientProvider: Provider = {
      provide: ANALYTICS_CLIENT,
      useFactory: (config: AnalyticsModuleOptions) => {
        return new AnalyticsClient(config);
      },
      inject: [ANALYTICS_CONFIG],
    };

    const asyncProviders = this.createAsyncProviders(options);
    const providers: Provider[] = [...asyncProviders, analyticsClientProvider];

    // We can't determine if global interceptor is enabled at module import time
    // for async configuration, so we conditionally add it in the factory
    const imports = options.imports || [];

    return {
      module: AnalyticsModule,
      imports,
      providers,
      exports: [ANALYTICS_CLIENT, ANALYTICS_CONFIG],
    };
  }

  /**
   * Create async providers for configuration
   */
  private static createAsyncProviders(
    options: AnalyticsModuleAsyncOptions,
  ): Provider[] {
    if (options.useFactory) {
      return [
        {
          provide: ANALYTICS_CONFIG,
          useFactory: options.useFactory,
          inject: (options.inject as Array<string | symbol | (new (...args: unknown[]) => unknown)>) || [],
        },
      ];
    }

    if (options.useClass) {
      return [
        {
          provide: ANALYTICS_CONFIG,
          useFactory: async (optionsFactory: AnalyticsOptionsFactory) => {
            return await optionsFactory.createAnalyticsOptions();
          },
          inject: [options.useClass],
        },
        {
          provide: options.useClass,
          useClass: options.useClass,
        },
      ];
    }

    if (options.useExisting) {
      return [
        {
          provide: ANALYTICS_CONFIG,
          useFactory: async (optionsFactory: AnalyticsOptionsFactory) => {
            return await optionsFactory.createAnalyticsOptions();
          },
          inject: [options.useExisting],
        },
      ];
    }

    throw new Error(
      'Invalid AnalyticsModule async configuration. Must provide useFactory, useClass, or useExisting.',
    );
  }
}
