import * as plugins from '../../plugins.js';
import type { IDomainConfig } from './domain-config.js';
import { ForwardingHandler } from '../handlers/base-handler.js';
import { ForwardingHandlerEvents } from './forwarding-types.js';
import { ForwardingHandlerFactory } from '../factory/forwarding-factory.js';

/**
 * Events emitted by the DomainManager
 */
export enum DomainManagerEvents {
  DOMAIN_ADDED = 'domain-added',
  DOMAIN_REMOVED = 'domain-removed',
  DOMAIN_MATCHED = 'domain-matched',
  DOMAIN_MATCH_FAILED = 'domain-match-failed',
  CERTIFICATE_NEEDED = 'certificate-needed',
  CERTIFICATE_LOADED = 'certificate-loaded',
  ERROR = 'error'
}

/**
 * Manages domains and their forwarding handlers
 */
export class DomainManager extends plugins.EventEmitter {
  private domainConfigs: IDomainConfig[] = [];
  private domainHandlers: Map<string, ForwardingHandler> = new Map();

  /**
   * Create a new DomainManager
   * @param initialDomains Optional initial domain configurations
   */
  constructor(initialDomains?: IDomainConfig[]) {
    super();
    
    if (initialDomains) {
      this.setDomainConfigs(initialDomains);
    }
  }
  
  /**
   * Set or replace all domain configurations
   * @param configs Array of domain configurations
   */
  public async setDomainConfigs(configs: IDomainConfig[]): Promise<void> {
    // Clear existing handlers
    this.domainHandlers.clear();
    
    // Store new configurations
    this.domainConfigs = [...configs];
    
    // Initialize handlers for each domain
    for (const config of this.domainConfigs) {
      await this.createHandlersForDomain(config);
    }
  }
  
  /**
   * Add a new domain configuration
   * @param config The domain configuration to add
   */
  public async addDomainConfig(config: IDomainConfig): Promise<void> {
    // Check if any of these domains already exist
    for (const domain of config.domains) {
      if (this.domainHandlers.has(domain)) {
        // Remove existing handler for this domain
        this.domainHandlers.delete(domain);
      }
    }
    
    // Add the new configuration
    this.domainConfigs.push(config);
    
    // Create handlers for the new domain
    await this.createHandlersForDomain(config);
    
    this.emit(DomainManagerEvents.DOMAIN_ADDED, {
      domains: config.domains,
      forwardingType: config.forwarding.type
    });
  }
  
  /**
   * Remove a domain configuration
   * @param domain The domain to remove
   * @returns True if the domain was found and removed
   */
  public removeDomainConfig(domain: string): boolean {
    // Find the config that includes this domain
    const index = this.domainConfigs.findIndex(config => 
      config.domains.includes(domain)
    );
    
    if (index === -1) {
      return false;
    }
    
    // Get the config
    const config = this.domainConfigs[index];
    
    // Remove all handlers for this config
    for (const domainName of config.domains) {
      this.domainHandlers.delete(domainName);
    }
    
    // Remove the config
    this.domainConfigs.splice(index, 1);
    
    this.emit(DomainManagerEvents.DOMAIN_REMOVED, {
      domains: config.domains
    });
    
    return true;
  }
  
  /**
   * Find the handler for a domain
   * @param domain The domain to find a handler for
   * @returns The handler or undefined if no match
   */
  public findHandlerForDomain(domain: string): ForwardingHandler | undefined {
    // Try exact match
    if (this.domainHandlers.has(domain)) {
      return this.domainHandlers.get(domain);
    }
    
    // Try wildcard matches
    const wildcardHandler = this.findWildcardHandler(domain);
    if (wildcardHandler) {
      return wildcardHandler;
    }
    
    // No match found
    return undefined;
  }
  
  /**
   * Handle a connection for a domain
   * @param domain The domain
   * @param socket The client socket
   * @returns True if the connection was handled
   */
  public handleConnection(domain: string, socket: plugins.net.Socket): boolean {
    const handler = this.findHandlerForDomain(domain);
    
    if (!handler) {
      this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
        domain,
        remoteAddress: socket.remoteAddress
      });
      return false;
    }
    
    this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
      domain,
      handlerType: handler.constructor.name,
      remoteAddress: socket.remoteAddress
    });
    
    // Handle the connection
    handler.handleConnection(socket);
    return true;
  }
  
  /**
   * Handle an HTTP request for a domain
   * @param domain The domain
   * @param req The HTTP request
   * @param res The HTTP response
   * @returns True if the request was handled
   */
  public handleHttpRequest(domain: string, req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): boolean {
    const handler = this.findHandlerForDomain(domain);
    
    if (!handler) {
      this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
        domain,
        remoteAddress: req.socket.remoteAddress
      });
      return false;
    }
    
    this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
      domain,
      handlerType: handler.constructor.name,
      remoteAddress: req.socket.remoteAddress
    });
    
    // Handle the request
    handler.handleHttpRequest(req, res);
    return true;
  }
  
  /**
   * Create handlers for a domain configuration
   * @param config The domain configuration
   */
  private async createHandlersForDomain(config: IDomainConfig): Promise<void> {
    try {
      // Create a handler for this forwarding configuration
      const handler = ForwardingHandlerFactory.createHandler(config.forwarding);
      
      // Initialize the handler
      await handler.initialize();
      
      // Set up event forwarding
      this.setupHandlerEvents(handler, config);
      
      // Store the handler for each domain in the config
      for (const domain of config.domains) {
        this.domainHandlers.set(domain, handler);
      }
    } catch (error) {
      this.emit(DomainManagerEvents.ERROR, {
        domains: config.domains,
        error: error instanceof Error ? error.message : String(error)
      });
    }
  }
  
  /**
   * Set up event forwarding from a handler
   * @param handler The handler
   * @param config The domain configuration for this handler
   */
  private setupHandlerEvents(handler: ForwardingHandler, config: IDomainConfig): void {
    // Forward relevant events
    handler.on(ForwardingHandlerEvents.CERTIFICATE_NEEDED, (data) => {
      this.emit(DomainManagerEvents.CERTIFICATE_NEEDED, {
        ...data,
        domains: config.domains
      });
    });
    
    handler.on(ForwardingHandlerEvents.CERTIFICATE_LOADED, (data) => {
      this.emit(DomainManagerEvents.CERTIFICATE_LOADED, {
        ...data,
        domains: config.domains
      });
    });
    
    handler.on(ForwardingHandlerEvents.ERROR, (data) => {
      this.emit(DomainManagerEvents.ERROR, {
        ...data,
        domains: config.domains
      });
    });
  }
  
  /**
   * Find a handler for a domain using wildcard matching
   * @param domain The domain to find a handler for
   * @returns The handler or undefined if no match
   */
  private findWildcardHandler(domain: string): ForwardingHandler | undefined {
    // Exact match already checked in findHandlerForDomain
    
    // Try subdomain wildcard (*.example.com)
    if (domain.includes('.')) {
      const parts = domain.split('.');
      if (parts.length > 2) {
        const wildcardDomain = `*.${parts.slice(1).join('.')}`;
        if (this.domainHandlers.has(wildcardDomain)) {
          return this.domainHandlers.get(wildcardDomain);
        }
      }
    }
    
    // Try full wildcard
    if (this.domainHandlers.has('*')) {
      return this.domainHandlers.get('*');
    }
    
    // No match found
    return undefined;
  }
  
  /**
   * Get all domain configurations
   * @returns Array of domain configurations
   */
  public getDomainConfigs(): IDomainConfig[] {
    return [...this.domainConfigs];
  }
}