import * as plugins from '../plugins.js';
import { NetworkProxy } from '../networkproxy/classes.np.networkproxy.js';
import { Port80Handler, Port80HandlerEvents, type ICertificateData } from '../port80handler/classes.port80handler.js';
import type { IConnectionRecord, IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';

/**
 * Manages NetworkProxy integration for TLS termination
 */
export class NetworkProxyBridge {
  private networkProxy: NetworkProxy | null = null;
  private port80Handler: Port80Handler | null = null;
  
  constructor(private settings: IPortProxySettings) {}
  
  /**
   * Set the Port80Handler to use for certificate management
   */
  public setPort80Handler(handler: Port80Handler): void {
    this.port80Handler = handler;
    
    // Register for certificate events
    handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateEvent.bind(this));
    handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateEvent.bind(this));
    
    // If NetworkProxy is already initialized, connect it with Port80Handler
    if (this.networkProxy) {
      this.networkProxy.setExternalPort80Handler(handler);
    }
    
    console.log('Port80Handler connected to NetworkProxyBridge');
  }
  
  /**
   * Initialize NetworkProxy instance
   */
  public async initialize(): Promise<void> {
    if (!this.networkProxy && this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
      // Configure NetworkProxy options based on PortProxy settings
      const networkProxyOptions: any = {
        port: this.settings.networkProxyPort!,
        portProxyIntegration: true,
        logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info',
        useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available
      };

      // Copy ACME settings for backward compatibility (if port80HandlerConfig not set)
      if (!this.settings.port80HandlerConfig && this.settings.acme) {
        networkProxyOptions.acme = { ...this.settings.acme };
      }

      this.networkProxy = new NetworkProxy(networkProxyOptions);

      console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
      
      // Connect Port80Handler if available
      if (this.port80Handler) {
        this.networkProxy.setExternalPort80Handler(this.port80Handler);
      }

      // Convert and apply domain configurations to NetworkProxy
      await this.syncDomainConfigsToNetworkProxy();
    }
  }
  
  /**
   * Handle certificate issuance or renewal events
   */
  private handleCertificateEvent(data: ICertificateData): void {
    if (!this.networkProxy) return;
    
    console.log(`Received certificate for ${data.domain} from Port80Handler, updating NetworkProxy`);
    
    try {
      // Find existing config for this domain
      const existingConfigs = this.networkProxy.getProxyConfigs()
        .filter(config => config.hostName === data.domain);
      
      if (existingConfigs.length > 0) {
        // Update existing configs with new certificate
        for (const config of existingConfigs) {
          config.privateKey = data.privateKey;
          config.publicKey = data.certificate;
        }
        
        // Apply updated configs
        this.networkProxy.updateProxyConfigs(existingConfigs)
          .then(() => console.log(`Updated certificate for ${data.domain} in NetworkProxy`))
          .catch(err => console.log(`Error updating certificate in NetworkProxy: ${err}`));
      } else {
        // Create a new config for this domain
        console.log(`No existing config found for ${data.domain}, creating new config in NetworkProxy`);
      }
    } catch (err) {
      console.log(`Error handling certificate event: ${err}`);
    }
  }
  
  /**
   * Get the NetworkProxy instance
   */
  public getNetworkProxy(): NetworkProxy | null {
    return this.networkProxy;
  }
  
  /**
   * Get the NetworkProxy port
   */
  public getNetworkProxyPort(): number {
    return this.networkProxy ? this.networkProxy.getListeningPort() : this.settings.networkProxyPort || 8443;
  }
  
  /**
   * Start NetworkProxy
   */
  public async start(): Promise<void> {
    if (this.networkProxy) {
      await this.networkProxy.start();
      console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
    }
  }
  
  /**
   * Stop NetworkProxy
   */
  public async stop(): Promise<void> {
    if (this.networkProxy) {
      try {
        console.log('Stopping NetworkProxy...');
        await this.networkProxy.stop();
        console.log('NetworkProxy stopped successfully');
      } catch (err) {
        console.log(`Error stopping NetworkProxy: ${err}`);
      }
    }
  }
  
  /**
   * Register domains with Port80Handler
   */
  public registerDomainsWithPort80Handler(domains: string[]): void {
    if (!this.port80Handler) {
      console.log('Cannot register domains - Port80Handler not initialized');
      return;
    }
    
    for (const domain of domains) {
      // Skip wildcards
      if (domain.includes('*')) {
        console.log(`Skipping wildcard domain for ACME: ${domain}`);
        continue;
      }
      
      // Register the domain
      try {
        this.port80Handler.addDomain({
          domainName: domain,
          sslRedirect: true,
          acmeMaintenance: true
        });
        
        console.log(`Registered domain with Port80Handler: ${domain}`);
      } catch (err) {
        console.log(`Error registering domain ${domain} with Port80Handler: ${err}`);
      }
    }
  }
  
  /**
   * Forwards a TLS connection to a NetworkProxy for handling
   */
  public forwardToNetworkProxy(
    connectionId: string,
    socket: plugins.net.Socket,
    record: IConnectionRecord,
    initialData: Buffer,
    customProxyPort?: number,
    onError?: (reason: string) => void
  ): void {
    // Ensure NetworkProxy is initialized
    if (!this.networkProxy) {
      console.log(
        `[${connectionId}] NetworkProxy not initialized. Cannot forward connection.`
      );
      if (onError) {
        onError('network_proxy_not_initialized');
      }
      return;
    }

    // Use the custom port if provided, otherwise use the default NetworkProxy port
    const proxyPort = customProxyPort || this.networkProxy.getListeningPort();
    const proxyHost = 'localhost'; // Assuming NetworkProxy runs locally

    if (this.settings.enableDetailedLogging) {
      console.log(
        `[${connectionId}] Forwarding TLS connection to NetworkProxy at ${proxyHost}:${proxyPort}`
      );
    }

    // Create a connection to the NetworkProxy
    const proxySocket = plugins.net.connect({
      host: proxyHost,
      port: proxyPort,
    });

    // Store the outgoing socket in the record
    record.outgoing = proxySocket;
    record.outgoingStartTime = Date.now();
    record.usingNetworkProxy = true;

    // Set up error handlers
    proxySocket.on('error', (err) => {
      console.log(`[${connectionId}] Error connecting to NetworkProxy: ${err.message}`);
      if (onError) {
        onError('network_proxy_connect_error');
      }
    });

    // Handle connection to NetworkProxy
    proxySocket.on('connect', () => {
      if (this.settings.enableDetailedLogging) {
        console.log(`[${connectionId}] Connected to NetworkProxy at ${proxyHost}:${proxyPort}`);
      }

      // First send the initial data that contains the TLS ClientHello
      proxySocket.write(initialData);

      // Now set up bidirectional piping between client and NetworkProxy
      socket.pipe(proxySocket);
      proxySocket.pipe(socket);

      // Update activity on data transfer (caller should handle this)
      if (this.settings.enableDetailedLogging) {
        console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
      }
    });
  }
  
  /**
   * Synchronizes domain configurations to NetworkProxy
   */
  public async syncDomainConfigsToNetworkProxy(): Promise<void> {
    if (!this.networkProxy) {
      console.log('Cannot sync configurations - NetworkProxy not initialized');
      return;
    }

    try {
      // Get SSL certificates from assets
      // Import fs directly since it's not in plugins
      const fs = await import('fs');

      let certPair;
      try {
        certPair = {
          key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
          cert: fs.readFileSync('assets/certs/cert.pem', 'utf8'),
        };
      } catch (certError) {
        console.log(`Warning: Could not read default certificates: ${certError}`);
        console.log(
          'Using empty certificate placeholders - ACME will generate proper certificates if enabled'
        );

        // Use empty placeholders - NetworkProxy will use its internal defaults
        // or ACME will generate proper ones if enabled
        certPair = {
          key: '',
          cert: '',
        };
      }

      // Convert domain configs to NetworkProxy configs
      const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
        this.settings.domainConfigs,
        certPair
      );

      // Log ACME-eligible domains
      const acmeEnabled = this.settings.port80HandlerConfig?.enabled || this.settings.acme?.enabled;
      if (acmeEnabled) {
        const acmeEligibleDomains = proxyConfigs
          .filter((config) => !config.hostName.includes('*')) // Exclude wildcards
          .map((config) => config.hostName);

        if (acmeEligibleDomains.length > 0) {
          console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
          
          // Register these domains with Port80Handler if available
          if (this.port80Handler) {
            this.registerDomainsWithPort80Handler(acmeEligibleDomains);
          }
        } else {
          console.log('No domains eligible for ACME certificates found in configuration');
        }
      }

      // Update NetworkProxy with the converted configs
      await this.networkProxy.updateProxyConfigs(proxyConfigs);
      console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
    } catch (err) {
      console.log(`Failed to sync configurations: ${err}`);
    }
  }
  
  /**
   * Request a certificate for a specific domain
   */
  public async requestCertificate(domain: string): Promise<boolean> {
    // Delegate to Port80Handler if available
    if (this.port80Handler) {
      try {
        // Check if the domain is already registered
        const cert = this.port80Handler.getCertificate(domain);
        if (cert) {
          console.log(`Certificate already exists for ${domain}`);
          return true;
        }
        
        // Register the domain for certificate issuance
        this.port80Handler.addDomain({
          domainName: domain,
          sslRedirect: true,
          acmeMaintenance: true
        });
        
        console.log(`Domain ${domain} registered for certificate issuance`);
        return true;
      } catch (err) {
        console.log(`Error requesting certificate: ${err}`);
        return false;
      }
    }
    
    // Fall back to NetworkProxy if Port80Handler is not available
    if (!this.networkProxy) {
      console.log('Cannot request certificate - NetworkProxy not initialized');
      return false;
    }

    if (!this.settings.port80HandlerConfig?.enabled && !this.settings.acme?.enabled) {
      console.log('Cannot request certificate - ACME is not enabled');
      return false;
    }

    try {
      const result = await this.networkProxy.requestCertificate(domain);
      if (result) {
        console.log(`Certificate request for ${domain} submitted successfully`);
      } else {
        console.log(`Certificate request for ${domain} failed`);
      }
      return result;
    } catch (err) {
      console.log(`Error requesting certificate: ${err}`);
      return false;
    }
  }
}