import * as plugins from '../plugins.js';
import type { IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
import { ConnectionManager } from './classes.pp.connectionmanager.js';
import { SecurityManager } from './classes.pp.securitymanager.js';
import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
import { TlsManager } from './classes.pp.tlsmanager.js';
import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
import { TimeoutManager } from './classes.pp.timeoutmanager.js';
import { PortRangeManager } from './classes.pp.portrangemanager.js';
import { ConnectionHandler } from './classes.pp.connectionhandler.js';
import { Port80Handler, Port80HandlerEvents } from '../port80handler/classes.port80handler.js';
import * as path from 'path';
import * as fs from 'fs';

/**
 * SmartProxy - Main class that coordinates all components
 */
export class SmartProxy {
  private netServers: plugins.net.Server[] = [];
  private connectionLogger: NodeJS.Timeout | null = null;
  private isShuttingDown: boolean = false;
  
  // Component managers
  private connectionManager: ConnectionManager;
  private securityManager: SecurityManager;
  public domainConfigManager: DomainConfigManager;
  private tlsManager: TlsManager;
  private networkProxyBridge: NetworkProxyBridge;
  private timeoutManager: TimeoutManager;
  private portRangeManager: PortRangeManager;
  private connectionHandler: ConnectionHandler;
  
  // Port80Handler for ACME certificate management
  private port80Handler: Port80Handler | null = null;
  
  constructor(settingsArg: IPortProxySettings) {
    // Set reasonable defaults for all settings
    this.settings = {
      ...settingsArg,
      targetIP: settingsArg.targetIP || 'localhost',
      initialDataTimeout: settingsArg.initialDataTimeout || 120000,
      socketTimeout: settingsArg.socketTimeout || 3600000,
      inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
      maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
      inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
      gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
      noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
      keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
      keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
      maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
      disableInactivityCheck: settingsArg.disableInactivityCheck || false,
      enableKeepAliveProbes: 
        settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
      enableDetailedLogging: settingsArg.enableDetailedLogging || false,
      enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
      enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
      allowSessionTicket: 
        settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
      maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
      connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
      keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
      keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
      extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
      networkProxyPort: settingsArg.networkProxyPort || 8443,
      port80HandlerConfig: settingsArg.port80HandlerConfig || {},
      globalPortRanges: settingsArg.globalPortRanges || [],
    };
    
    // Set port80HandlerConfig defaults, using legacy acme config if available
    if (!this.settings.port80HandlerConfig || Object.keys(this.settings.port80HandlerConfig).length === 0) {
      if (this.settings.acme) {
        // Migrate from legacy acme config
        this.settings.port80HandlerConfig = {
          enabled: this.settings.acme.enabled,
          port: this.settings.acme.port || 80,
          contactEmail: this.settings.acme.contactEmail || 'admin@example.com',
          useProduction: this.settings.acme.useProduction || false,
          renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
          autoRenew: this.settings.acme.autoRenew !== false, // Default to true
          certificateStore: this.settings.acme.certificateStore || './certs',
          skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
          httpsRedirectPort: this.settings.fromPort,
          renewCheckIntervalHours: 24
        };
      } else {
        // Set defaults if no config provided
        this.settings.port80HandlerConfig = {
          enabled: false,
          port: 80,
          contactEmail: 'admin@example.com',
          useProduction: false,
          renewThresholdDays: 30,
          autoRenew: true,
          certificateStore: './certs',
          skipConfiguredCerts: false,
          httpsRedirectPort: this.settings.fromPort,
          renewCheckIntervalHours: 24
        };
      }
    }
    
    // Initialize component managers
    this.timeoutManager = new TimeoutManager(this.settings);
    this.securityManager = new SecurityManager(this.settings);
    this.connectionManager = new ConnectionManager(
      this.settings, 
      this.securityManager, 
      this.timeoutManager
    );
    this.domainConfigManager = new DomainConfigManager(this.settings);
    this.tlsManager = new TlsManager(this.settings);
    this.networkProxyBridge = new NetworkProxyBridge(this.settings);
    this.portRangeManager = new PortRangeManager(this.settings);
    
    // Initialize connection handler
    this.connectionHandler = new ConnectionHandler(
      this.settings,
      this.connectionManager,
      this.securityManager,
      this.domainConfigManager,
      this.tlsManager,
      this.networkProxyBridge,
      this.timeoutManager,
      this.portRangeManager
    );
  }
  
  /**
   * The settings for the port proxy
   */
  public settings: IPortProxySettings;
  
  /**
   * Initialize the Port80Handler for ACME certificate management
   */
  private async initializePort80Handler(): Promise<void> {
    const config = this.settings.port80HandlerConfig;
    
    if (!config || !config.enabled) {
      console.log('Port80Handler is disabled in configuration');
      return;
    }
    
    try {
      // Ensure the certificate store directory exists
      if (config.certificateStore) {
        const certStorePath = path.resolve(config.certificateStore);
        if (!fs.existsSync(certStorePath)) {
          fs.mkdirSync(certStorePath, { recursive: true });
          console.log(`Created certificate store directory: ${certStorePath}`);
        }
      }
      
      // Create Port80Handler with options from config
      this.port80Handler = new Port80Handler({
        port: config.port,
        contactEmail: config.contactEmail,
        useProduction: config.useProduction,
        renewThresholdDays: config.renewThresholdDays,
        httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
        renewCheckIntervalHours: config.renewCheckIntervalHours,
        enabled: config.enabled,
        autoRenew: config.autoRenew,
        certificateStore: config.certificateStore,
        skipConfiguredCerts: config.skipConfiguredCerts
      });
      
      // Register domain forwarding configurations
      if (config.domainForwards) {
        for (const forward of config.domainForwards) {
          this.port80Handler.addDomain({
            domainName: forward.domain,
            sslRedirect: true,
            acmeMaintenance: true,
            forward: forward.forwardConfig,
            acmeForward: forward.acmeForwardConfig
          });
          
          console.log(`Registered domain forwarding for ${forward.domain}`);
        }
      }
      
      // Register all non-wildcard domains from domain configs
      for (const domainConfig of this.settings.domainConfigs) {
        for (const domain of domainConfig.domains) {
          // Skip wildcards
          if (domain.includes('*')) continue;
          
          this.port80Handler.addDomain({
            domainName: domain,
            sslRedirect: true,
            acmeMaintenance: true
          });
          
          console.log(`Registered domain ${domain} with Port80Handler`);
        }
      }
      
      // Set up event listeners
      this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (certData) => {
        console.log(`Certificate issued for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
      });
      
      this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (certData) => {
        console.log(`Certificate renewed for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
      });
      
      this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (failureData) => {
        console.log(`Certificate ${failureData.isRenewal ? 'renewal' : 'issuance'} failed for ${failureData.domain}: ${failureData.error}`);
      });
      
      this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (expiryData) => {
        console.log(`Certificate for ${expiryData.domain} is expiring in ${expiryData.daysRemaining} days`);
      });
      
      // Share Port80Handler with NetworkProxyBridge
      this.networkProxyBridge.setPort80Handler(this.port80Handler);
      
      // Start Port80Handler
      await this.port80Handler.start();
      console.log(`Port80Handler started on port ${config.port}`);
    } catch (err) {
      console.log(`Error initializing Port80Handler: ${err}`);
    }
  }
  
  /**
   * Start the proxy server
   */
  public async start() {
    // Don't start if already shutting down
    if (this.isShuttingDown) {
      console.log("Cannot start PortProxy while it's shutting down");
      return;
    }

    // Initialize Port80Handler if enabled
    await this.initializePort80Handler();

    // Initialize and start NetworkProxy if needed
    if (
      this.settings.useNetworkProxy &&
      this.settings.useNetworkProxy.length > 0
    ) {
      await this.networkProxyBridge.initialize();
      await this.networkProxyBridge.start();
    }

    // Validate port configuration
    const configWarnings = this.portRangeManager.validateConfiguration();
    if (configWarnings.length > 0) {
      console.log("Port configuration warnings:");
      for (const warning of configWarnings) {
        console.log(` - ${warning}`);
      }
    }

    // Get listening ports from PortRangeManager
    const listeningPorts = this.portRangeManager.getListeningPorts();

    // Create servers for each port
    for (const port of listeningPorts) {
      const server = plugins.net.createServer((socket) => {
        // Check if shutting down
        if (this.isShuttingDown) {
          socket.end();
          socket.destroy();
          return;
        }
        
        // Delegate to connection handler
        this.connectionHandler.handleConnection(socket);
      }).on('error', (err: Error) => {
        console.log(`Server Error on port ${port}: ${err.message}`);
      });
      
      server.listen(port, () => {
        const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
        console.log(
          `PortProxy -> OK: Now listening on port ${port}${
            this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : ''
          }${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
        );
      });
      
      this.netServers.push(server);
    }

    // Set up periodic connection logging and inactivity checks
    this.connectionLogger = setInterval(() => {
      // Immediately return if shutting down
      if (this.isShuttingDown) return;

      // Perform inactivity check
      this.connectionManager.performInactivityCheck();

      // Log connection statistics
      const now = Date.now();
      let maxIncoming = 0;
      let maxOutgoing = 0;
      let tlsConnections = 0;
      let nonTlsConnections = 0;
      let completedTlsHandshakes = 0;
      let pendingTlsHandshakes = 0;
      let keepAliveConnections = 0;
      let networkProxyConnections = 0;
      
      // Get connection records for analysis
      const connectionRecords = this.connectionManager.getConnections();
      
      // Analyze active connections
      for (const record of connectionRecords.values()) {
        // Track connection stats
        if (record.isTLS) {
          tlsConnections++;
          if (record.tlsHandshakeComplete) {
            completedTlsHandshakes++;
          } else {
            pendingTlsHandshakes++;
          }
        } else {
          nonTlsConnections++;
        }

        if (record.hasKeepAlive) {
          keepAliveConnections++;
        }

        if (record.usingNetworkProxy) {
          networkProxyConnections++;
        }

        maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
        if (record.outgoingStartTime) {
          maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
        }
      }

      // Get termination stats
      const terminationStats = this.connectionManager.getTerminationStats();

      // Log detailed stats
      console.log(
        `Active connections: ${connectionRecords.size}. ` +
        `Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
        `Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}. ` +
        `Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
        `Termination stats: ${JSON.stringify({
          IN: terminationStats.incoming,
          OUT: terminationStats.outgoing,
        })}`
      );
    }, this.settings.inactivityCheckInterval || 60000);

    // Make sure the interval doesn't keep the process alive
    if (this.connectionLogger.unref) {
      this.connectionLogger.unref();
    }
  }
  
  /**
   * Stop the proxy server
   */
  public async stop() {
    console.log('PortProxy shutting down...');
    this.isShuttingDown = true;

    // Stop the Port80Handler if running
    if (this.port80Handler) {
      try {
        await this.port80Handler.stop();
        console.log('Port80Handler stopped');
        this.port80Handler = null;
      } catch (err) {
        console.log(`Error stopping Port80Handler: ${err}`);
      }
    }

    // Stop accepting new connections
    const closeServerPromises: Promise<void>[] = this.netServers.map(
      (server) =>
        new Promise<void>((resolve) => {
          if (!server.listening) {
            resolve();
            return;
          }
          server.close((err) => {
            if (err) {
              console.log(`Error closing server: ${err.message}`);
            }
            resolve();
          });
        })
    );

    // Stop the connection logger
    if (this.connectionLogger) {
      clearInterval(this.connectionLogger);
      this.connectionLogger = null;
    }

    // Wait for servers to close
    await Promise.all(closeServerPromises);
    console.log('All servers closed. Cleaning up active connections...');

    // Clean up all active connections
    this.connectionManager.clearConnections();

    // Stop NetworkProxy
    await this.networkProxyBridge.stop();

    // Clear all servers
    this.netServers = [];

    console.log('PortProxy shutdown complete.');
  }
  
  /**
   * Updates the domain configurations for the proxy
   */
  public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
    console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
    
    // Update domain configs in DomainConfigManager
    this.domainConfigManager.updateDomainConfigs(newDomainConfigs);
    
    // If NetworkProxy is initialized, resync the configurations
    if (this.networkProxyBridge.getNetworkProxy()) {
      await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
    }
    
    // If Port80Handler is running, register non-wildcard domains
    if (this.port80Handler && this.settings.port80HandlerConfig?.enabled) {
      for (const domainConfig of newDomainConfigs) {
        for (const domain of domainConfig.domains) {
          // Skip wildcards
          if (domain.includes('*')) continue;
          
          this.port80Handler.addDomain({
            domainName: domain,
            sslRedirect: true,
            acmeMaintenance: true
          });
        }
      }
      
      console.log('Registered non-wildcard domains with Port80Handler');
    }
  }
  
  /**
   * Updates the Port80Handler configuration
   */
  public async updatePort80HandlerConfig(config: IPortProxySettings['port80HandlerConfig']): Promise<void> {
    if (!config) return;
    
    console.log('Updating Port80Handler configuration');
    
    // Update the settings
    this.settings.port80HandlerConfig = {
      ...this.settings.port80HandlerConfig,
      ...config
    };
    
    // Check if we need to restart Port80Handler
    let needsRestart = false;
    
    // Restart if enabled state changed
    if (this.port80Handler && config.enabled === false) {
      needsRestart = true;
    } else if (!this.port80Handler && config.enabled === true) {
      needsRestart = true;
    } else if (this.port80Handler && (
      config.port !== undefined || 
      config.contactEmail !== undefined ||
      config.useProduction !== undefined ||
      config.renewThresholdDays !== undefined ||
      config.renewCheckIntervalHours !== undefined
    )) {
      // Restart if critical settings changed
      needsRestart = true;
    }
    
    if (needsRestart) {
      // Stop if running
      if (this.port80Handler) {
        try {
          await this.port80Handler.stop();
          this.port80Handler = null;
          console.log('Stopped Port80Handler for configuration update');
        } catch (err) {
          console.log(`Error stopping Port80Handler: ${err}`);
        }
      }
      
      // Start with new config if enabled
      if (this.settings.port80HandlerConfig.enabled) {
        await this.initializePort80Handler();
        console.log('Restarted Port80Handler with new configuration');
      }
    } else if (this.port80Handler) {
      // Just update domain forwards if they changed
      if (config.domainForwards) {
        for (const forward of config.domainForwards) {
          this.port80Handler.addDomain({
            domainName: forward.domain,
            sslRedirect: true,
            acmeMaintenance: true,
            forward: forward.forwardConfig,
            acmeForward: forward.acmeForwardConfig
          });
        }
        console.log('Updated domain forwards in Port80Handler');
      }
    }
  }
  
  /**
   * Request a certificate for a specific domain
   */
  public async requestCertificate(domain: string): Promise<boolean> {
    // Validate domain format
    if (!this.isValidDomain(domain)) {
      console.log(`Invalid domain format: ${domain}`);
      return false;
    }
    
    // Use Port80Handler if available
    if (this.port80Handler) {
      try {
        // Check if we already have a certificate
        const cert = this.port80Handler.getCertificate(domain);
        if (cert) {
          console.log(`Certificate already exists for ${domain}, valid until ${cert.expiryDate.toISOString()}`);
          return true;
        }
        
        // Register 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 registering domain with Port80Handler: ${err}`);
        return false;
      }
    }
    
    // Fall back to NetworkProxyBridge
    return this.networkProxyBridge.requestCertificate(domain);
  }
  
  /**
   * Validates if a domain name is valid for certificate issuance
   */
  private isValidDomain(domain: string): boolean {
    // Very basic domain validation
    if (!domain || domain.length === 0) {
      return false;
    }
    
    // Check for wildcard domains (they can't get ACME certs)
    if (domain.includes('*')) {
      console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
      return false;
    }
    
    // Check if domain has at least one dot and no invalid characters
    const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    if (!validDomainRegex.test(domain)) {
      console.log(`Domain "${domain}" has invalid format`);
      return false;
    }
    
    return true;
  }
  
  /**
   * Get statistics about current connections
   */
  public getStatistics(): any {
    const connectionRecords = this.connectionManager.getConnections();
    const terminationStats = this.connectionManager.getTerminationStats();
    
    let tlsConnections = 0;
    let nonTlsConnections = 0;
    let keepAliveConnections = 0;
    let networkProxyConnections = 0;
    
    // Analyze active connections
    for (const record of connectionRecords.values()) {
      if (record.isTLS) tlsConnections++;
      else nonTlsConnections++;
      if (record.hasKeepAlive) keepAliveConnections++;
      if (record.usingNetworkProxy) networkProxyConnections++;
    }
    
    return {
      activeConnections: connectionRecords.size,
      tlsConnections,
      nonTlsConnections,
      keepAliveConnections,
      networkProxyConnections,
      terminationStats,
      acmeEnabled: !!this.port80Handler,
      port80HandlerPort: this.port80Handler ? this.settings.port80HandlerConfig?.port : null
    };
  }
  
  /**
   * Get a list of eligible domains for ACME certificates
   */
  public getEligibleDomainsForCertificates(): string[] {
    // Collect all non-wildcard domains from domain configs
    const domains: string[] = [];
    
    for (const config of this.settings.domainConfigs) {
      // Skip domains that can't be used with ACME
      const eligibleDomains = config.domains.filter(domain => 
        !domain.includes('*') && this.isValidDomain(domain)
      );
      
      domains.push(...eligibleDomains);
    }
    
    return domains;
  }
  
  /**
   * Get status of certificates managed by Port80Handler
   */
  public getCertificateStatus(): any {
    if (!this.port80Handler) {
      return {
        enabled: false,
        message: 'Port80Handler is not enabled'
      };
    }
    
    // Get eligible domains
    const eligibleDomains = this.getEligibleDomainsForCertificates();
    const certificateStatus: Record<string, any> = {};
    
    // Check each domain
    for (const domain of eligibleDomains) {
      const cert = this.port80Handler.getCertificate(domain);
      
      if (cert) {
        const now = new Date();
        const expiryDate = cert.expiryDate;
        const daysRemaining = Math.floor((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
        
        certificateStatus[domain] = {
          status: 'valid',
          expiryDate: expiryDate.toISOString(),
          daysRemaining,
          renewalNeeded: daysRemaining <= this.settings.port80HandlerConfig.renewThresholdDays
        };
      } else {
        certificateStatus[domain] = {
          status: 'missing',
          message: 'No certificate found'
        };
      }
    }
    
    return {
      enabled: true,
      port: this.settings.port80HandlerConfig.port,
      useProduction: this.settings.port80HandlerConfig.useProduction,
      autoRenew: this.settings.port80HandlerConfig.autoRenew,
      certificates: certificateStatus
    };
  }
}