import * as plugins from '../../plugins.js';
import type { IDomainConfig } from '../../forwarding/config/domain-config.js';
import type { ICertificateData, IDomainForwardConfig, IDomainOptions } from '../models/certificate-types.js';
import { Port80HandlerEvents, CertProvisionerEvents } from '../events/certificate-events.js';
import { Port80Handler } from '../../http/port80/port80-handler.js';
// We need to define this interface until we migrate NetworkProxyBridge
interface INetworkProxyBridge {
  applyExternalCertificate(certData: ICertificateData): void;
}

// This will be imported after NetworkProxyBridge is migrated
// import type { NetworkProxyBridge } from '../../proxies/smart-proxy/network-proxy-bridge.js';

// For backward compatibility
export type TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01';

/**
 * Type for static certificate provisioning
 */
export type TCertProvisionObject = plugins.tsclass.network.ICert | 'http01' | 'dns01';

/**
 * CertProvisioner manages certificate provisioning and renewal workflows,
 * unifying static certificates and HTTP-01 challenges via Port80Handler.
 */
export class CertProvisioner extends plugins.EventEmitter {
  private domainConfigs: IDomainConfig[];
  private port80Handler: Port80Handler;
  private networkProxyBridge: INetworkProxyBridge;
  private certProvisionFunction?: (domain: string) => Promise<TCertProvisionObject>;
  private forwardConfigs: IDomainForwardConfig[];
  private renewThresholdDays: number;
  private renewCheckIntervalHours: number;
  private autoRenew: boolean;
  private renewManager?: plugins.taskbuffer.TaskManager;
  // Track provisioning type per domain
  private provisionMap: Map<string, 'http01' | 'dns01' | 'static'>;

  /**
   * @param domainConfigs Array of domain configuration objects
   * @param port80Handler HTTP-01 challenge handler instance
   * @param networkProxyBridge Bridge for applying external certificates
   * @param certProvider Optional callback returning a static cert or 'http01'
   * @param renewThresholdDays Days before expiry to trigger renewals
   * @param renewCheckIntervalHours Interval in hours to check for renewals
   * @param autoRenew Whether to automatically schedule renewals
   * @param forwardConfigs Domain forwarding configurations for ACME challenges
   */
  constructor(
    domainConfigs: IDomainConfig[],
    port80Handler: Port80Handler,
    networkProxyBridge: INetworkProxyBridge,
    certProvider?: (domain: string) => Promise<TCertProvisionObject>,
    renewThresholdDays: number = 30,
    renewCheckIntervalHours: number = 24,
    autoRenew: boolean = true,
    forwardConfigs: IDomainForwardConfig[] = []
  ) {
    super();
    this.domainConfigs = domainConfigs;
    this.port80Handler = port80Handler;
    this.networkProxyBridge = networkProxyBridge;
    this.certProvisionFunction = certProvider;
    this.renewThresholdDays = renewThresholdDays;
    this.renewCheckIntervalHours = renewCheckIntervalHours;
    this.autoRenew = autoRenew;
    this.provisionMap = new Map();
    this.forwardConfigs = forwardConfigs;
  }

  /**
   * Start initial provisioning and schedule renewals.
   */
  public async start(): Promise<void> {
    // Subscribe to Port80Handler certificate events
    this.setupEventSubscriptions();

    // Apply external forwarding for ACME challenges
    this.setupForwardingConfigs();

    // Initial provisioning for all domains
    await this.provisionAllDomains();

    // Schedule renewals if enabled
    if (this.autoRenew) {
      this.scheduleRenewals();
    }
  }

  /**
   * Set up event subscriptions for certificate events
   */
  private setupEventSubscriptions(): void {
    // We need to reimplement subscribeToPort80Handler here
    this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (data: ICertificateData) => {
      this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, { ...data, source: 'http01', isRenewal: false });
    });

    this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (data: ICertificateData) => {
      this.emit(CertProvisionerEvents.CERTIFICATE_RENEWED, { ...data, source: 'http01', isRenewal: true });
    });

    this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (error) => {
      this.emit(CertProvisionerEvents.CERTIFICATE_FAILED, error);
    });
  }

  /**
   * Set up forwarding configurations for the Port80Handler
   */
  private setupForwardingConfigs(): void {
    for (const config of this.forwardConfigs) {
      const domainOptions: IDomainOptions = {
        domainName: config.domain,
        sslRedirect: config.sslRedirect || false,
        acmeMaintenance: false,
        forward: config.forwardConfig,
        acmeForward: config.acmeForwardConfig
      };
      this.port80Handler.addDomain(domainOptions);
    }
  }

  /**
   * Provision certificates for all configured domains
   */
  private async provisionAllDomains(): Promise<void> {
    const domains = this.domainConfigs.flatMap(cfg => cfg.domains);

    for (const domain of domains) {
      await this.provisionDomain(domain);
    }
  }

  /**
   * Provision a certificate for a single domain
   * @param domain Domain to provision
   */
  private async provisionDomain(domain: string): Promise<void> {
    const isWildcard = domain.includes('*');
    let provision: TCertProvisionObject = 'http01';

    // Try to get a certificate from the provision function
    if (this.certProvisionFunction) {
      try {
        provision = await this.certProvisionFunction(domain);
      } catch (err) {
        console.error(`certProvider error for ${domain}:`, err);
      }
    } else if (isWildcard) {
      // No certProvider: cannot handle wildcard without DNS-01 support
      console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`);
      return;
    }

    // Handle different provisioning methods
    if (provision === 'http01') {
      if (isWildcard) {
        console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
        return;
      }

      this.provisionMap.set(domain, 'http01');
      this.port80Handler.addDomain({
        domainName: domain,
        sslRedirect: true,
        acmeMaintenance: true
      });
    } else if (provision === 'dns01') {
      // DNS-01 challenges would be handled by the certProvisionFunction
      this.provisionMap.set(domain, 'dns01');
      // DNS-01 handling would go here if implemented
    } else {
      // Static certificate (e.g., DNS-01 provisioned or user-provided)
      this.provisionMap.set(domain, 'static');
      const certObj = provision as plugins.tsclass.network.ICert;
      const certData: ICertificateData = {
        domain: certObj.domainName,
        certificate: certObj.publicKey,
        privateKey: certObj.privateKey,
        expiryDate: new Date(certObj.validUntil),
        source: 'static',
        isRenewal: false
      };

      this.networkProxyBridge.applyExternalCertificate(certData);
      this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, certData);
    }
  }

  /**
   * Schedule certificate renewals using a task manager
   */
  private scheduleRenewals(): void {
    this.renewManager = new plugins.taskbuffer.TaskManager();

    const renewTask = new plugins.taskbuffer.Task({
      name: 'CertificateRenewals',
      taskFunction: async () => await this.performRenewals()
    });

    const hours = this.renewCheckIntervalHours;
    const cronExpr = `0 0 */${hours} * * *`;

    this.renewManager.addAndScheduleTask(renewTask, cronExpr);
    this.renewManager.start();
  }

  /**
   * Perform renewals for all domains that need it
   */
  private async performRenewals(): Promise<void> {
    for (const [domain, type] of this.provisionMap.entries()) {
      // Skip wildcard domains for HTTP-01 challenges
      if (domain.includes('*') && type === 'http01') continue;

      try {
        await this.renewDomain(domain, type);
      } catch (err) {
        console.error(`Renewal error for ${domain}:`, err);
      }
    }
  }

  /**
   * Renew a certificate for a specific domain
   * @param domain Domain to renew
   * @param provisionType Type of provisioning for this domain
   */
  private async renewDomain(domain: string, provisionType: 'http01' | 'dns01' | 'static'): Promise<void> {
    if (provisionType === 'http01') {
      await this.port80Handler.renewCertificate(domain);
    } else if ((provisionType === 'static' || provisionType === 'dns01') && this.certProvisionFunction) {
      const provision = await this.certProvisionFunction(domain);

      if (provision !== 'http01' && provision !== 'dns01') {
        const certObj = provision as plugins.tsclass.network.ICert;
        const certData: ICertificateData = {
          domain: certObj.domainName,
          certificate: certObj.publicKey,
          privateKey: certObj.privateKey,
          expiryDate: new Date(certObj.validUntil),
          source: 'static',
          isRenewal: true
        };

        this.networkProxyBridge.applyExternalCertificate(certData);
        this.emit(CertProvisionerEvents.CERTIFICATE_RENEWED, certData);
      }
    }
  }

  /**
   * Stop all scheduled renewal tasks.
   */
  public async stop(): Promise<void> {
    if (this.renewManager) {
      this.renewManager.stop();
    }
  }

  /**
   * Request a certificate on-demand for the given domain.
   * @param domain Domain name to provision
   */
  public async requestCertificate(domain: string): Promise<void> {
    const isWildcard = domain.includes('*');

    // Determine provisioning method
    let provision: TCertProvisionObject = 'http01';

    if (this.certProvisionFunction) {
      provision = await this.certProvisionFunction(domain);
    } else if (isWildcard) {
      // Cannot perform HTTP-01 on wildcard without certProvider
      throw new Error(`Cannot request certificate for wildcard domain without certProvisionFunction: ${domain}`);
    }

    if (provision === 'http01') {
      if (isWildcard) {
        throw new Error(`Cannot request HTTP-01 certificate for wildcard domain: ${domain}`);
      }
      await this.port80Handler.renewCertificate(domain);
    } else if (provision === 'dns01') {
      // DNS-01 challenges would be handled by external mechanisms
      // This is a placeholder for future implementation
      console.log(`DNS-01 challenge requested for ${domain}`);
    } else {
      // Static certificate (e.g., DNS-01 provisioned) supports wildcards
      const certObj = provision as plugins.tsclass.network.ICert;
      const certData: ICertificateData = {
        domain: certObj.domainName,
        certificate: certObj.publicKey,
        privateKey: certObj.privateKey,
        expiryDate: new Date(certObj.validUntil),
        source: 'static',
        isRenewal: false
      };

      this.networkProxyBridge.applyExternalCertificate(certData);
      this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, certData);
    }
  }

  /**
   * Add a new domain for certificate provisioning
   * @param domain Domain to add
   * @param options Domain configuration options
   */
  public async addDomain(domain: string, options?: {
    sslRedirect?: boolean;
    acmeMaintenance?: boolean;
  }): Promise<void> {
    const domainOptions: IDomainOptions = {
      domainName: domain,
      sslRedirect: options?.sslRedirect || true,
      acmeMaintenance: options?.acmeMaintenance || true
    };

    this.port80Handler.addDomain(domainOptions);
    await this.provisionDomain(domain);
  }
}

// For backward compatibility
export { CertProvisioner as CertificateProvisioner }