import { IService } from '../interfaces';

/**
 * Service factory function
 */
export type ServiceFactory<T = any> = () => T | Promise<T>;

/**
 * Service registration options
 */
export interface ServiceRegistration<T = any> {
  factory: ServiceFactory<T>;
  singleton?: boolean;
  dependencies?: string[];
}

/**
 * Dependency injection container
 */
export class DIContainer {
  private services = new Map<string, ServiceRegistration>();
  private singletons = new Map<string, any>();
  private resolving = new Set<string>();
  // Track pending resolutions to allow concurrent callers to await the same promise
  private pendingResolutions = new Map<string, Promise<any>>();

  /**
   * Register a service with the container
   */
  register<T>(token: string, factory: ServiceFactory<T>, options?: Omit<ServiceRegistration<T>, 'factory'>): void {
    this.services.set(token, {
      factory,
      singleton: options?.singleton ?? false,
      dependencies: options?.dependencies ?? []
    });
  }

  /**
   * Register a singleton service
   */
  singleton<T>(token: string, factory: ServiceFactory<T>, dependencies?: string[]): void {
    this.register(token, factory, { singleton: true, dependencies });
  }

  /**
   * Register a transient service
   */
  transient<T>(token: string, factory: ServiceFactory<T>, dependencies?: string[]): void {
    this.register(token, factory, { singleton: false, dependencies });
  }

  /**
   * Register an existing instance as a singleton
   */
  registerInstance<T>(token: string, instance: T): void {
    this.singletons.set(token, instance);
    // Also register a factory that returns the instance
    this.register(token, () => instance, { singleton: true });
  }

  /**
   * Resolve a service from the container
   */
  async resolve<T>(token: string): Promise<T> {
    // Return singleton if already created (prefer fast path)
    if (this.singletons.has(token)) {
      return this.singletons.get(token);
    }

    // If a resolution is already in progress for this token, share the promise
    if (this.pendingResolutions.has(token)) {
      return await this.pendingResolutions.get(token) as T;
    }

    // Check for circular dependencies (true recursion within a single resolution chain)
    if (this.resolving.has(token)) {
      throw new Error(`Circular dependency detected: ${token}`);
    }

    const registration = this.services.get(token);
    if (!registration) {
      throw new Error(`Service not found: ${token}`);
    }

    this.resolving.add(token);

    // Create a shared promise for this resolution so concurrent callers can await it
    const resolutionPromise = (async () => {
      try {
        // Resolve dependencies first
        const dependencies: any[] = [];
        if (registration.dependencies) {
          for (const dep of registration.dependencies) {
            dependencies.push(await this.resolve(dep));
          }
        }

        // Create service instance
        const instance = await registration.factory();

        // Initialize service if it implements IService
        if (this.isService(instance)) {
          await instance.initialize?.();
        }

        // Store singleton
        if (registration.singleton) {
          this.singletons.set(token, instance);
        }

        return instance as T;
      } finally {
        this.resolving.delete(token);
        this.pendingResolutions.delete(token);
      }
    })();

    this.pendingResolutions.set(token, resolutionPromise);

    return await resolutionPromise;
  }

  /**
   * Resolve service synchronously (for already initialized singletons)
   */
  resolveSync<T>(token: string): T {
    if (this.singletons.has(token)) {
      return this.singletons.get(token);
    }
    throw new Error(`Service not available synchronously: ${token}`);
  }

  /**
   * Check if service is registered
   */
  has(token: string): boolean {
    return this.services.has(token);
  }

  /**
   * Get all registered service tokens
   */
  getRegisteredServices(): string[] {
    return Array.from(this.services.keys());
  }

  /**
   * Clear all services and singletons
   */
  async clear(): Promise<void> {
    // Destroy all singleton services
    const entries = Array.from(this.singletons.entries());
    for (const [token, instance] of entries) {
      if (this.isService(instance)) {
        await instance.destroy?.();
      }
    }

    this.services.clear();
    this.singletons.clear();
    this.resolving.clear();
  }

  /**
   * Remove a specific service
   */
  async remove(token: string): Promise<void> {
    // Destroy singleton if exists
    if (this.singletons.has(token)) {
      const instance = this.singletons.get(token);
      if (this.isService(instance)) {
        await instance.destroy?.();
      }
      this.singletons.delete(token);
    }

    this.services.delete(token);
  }

  /**
   * Health check for all services
   */
  async healthCheck(): Promise<Record<string, boolean>> {
    const health: Record<string, boolean> = {};

    const entries = Array.from(this.singletons.entries());
    for (const [token, instance] of entries) {
      if (this.isService(instance)) {
        try {
          health[token] = await instance.isHealthy?.() ?? true;
        } catch (error) {
          health[token] = false;
        }
      } else {
        health[token] = true;
      }
    }

    return health;
  }

  /**
   * Check if object implements IService interface
   */
  private isService(obj: any): obj is IService {
    return obj && typeof obj === 'object' && 'id' in obj && 'name' in obj;
  }
}

// Global container instance
export const container = new DIContainer();