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 { AcmeManager } from './classes.pp.acmemanager.js';
import { PortRangeManager } from './classes.pp.portrangemanager.js';
import { ConnectionHandler } from './classes.pp.connectionhandler.js';

/**
 * PortProxy - Main class that coordinates all components
 */
export class PortProxy {
  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 acmeManager: AcmeManager;
  private portRangeManager: PortRangeManager;
  private connectionHandler: ConnectionHandler;
  
  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,
      acme: settingsArg.acme || {
        enabled: false,
        port: 80,
        contactEmail: 'admin@example.com',
        useProduction: false,
        renewThresholdDays: 30,
        autoRenew: true,
        certificateStore: './certs',
        skipConfiguredCerts: false,
      },
    };
    
    // 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);
    this.acmeManager = new AcmeManager(this.settings, this.networkProxyBridge);
    
    // 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;
  
  /**
   * 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 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 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();
    }
  }
  
  /**
   * Updates the ACME certificate settings
   */
  public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
    console.log('Updating ACME certificate settings');
    
    // Delegate to AcmeManager
    await this.acmeManager.updateAcmeSettings(acmeSettings);
  }
  
  /**
   * Requests a certificate for a specific domain
   */
  public async requestCertificate(domain: string): Promise<boolean> {
    // Delegate to AcmeManager
    return this.acmeManager.requestCertificate(domain);
  }
  
  /**
   * 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
    };
  }
}