import * as plugins from '../../plugins.js';
import { logger } from '../../core/utils/logger.js';

// Importing required components
import { ConnectionManager } from './connection-manager.js';
import { SecurityManager } from './security-manager.js';
import { TlsManager } from './tls-manager.js';
import { HttpProxyBridge } from './http-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js';
import { PortManager } from './port-manager.js';
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
import { RouteConnectionHandler } from './route-connection-handler.js';
import { NFTablesManager } from './nftables-manager.js';

// Certificate manager
import { SmartCertManager, type ICertStatus } from './certificate-manager.js';

// Import types and utilities
import type {
  ISmartProxyOptions
} from './models/interfaces.js';
import type { IRouteConfig } from './models/route-types.js';

// Import mutex for route update synchronization
import { Mutex } from './utils/mutex.js';

// Import ACME state manager
import { AcmeStateManager } from './acme-state-manager.js';

// Import metrics collector
import { MetricsCollector } from './metrics-collector.js';
import type { IMetrics } from './models/metrics-types.js';

/**
 * SmartProxy - Pure route-based API
 *
 * SmartProxy is a unified proxy system that works with routes to define connection handling behavior.
 * Each route contains matching criteria (ports, domains, etc.) and an action to take (forward, redirect, block).
 *
 * Configuration is provided through a set of routes, with each route defining:
 * - What to match (ports, domains, paths, client IPs)
 * - What to do with matching traffic (forward, redirect, block)
 * - How to handle TLS (passthrough, terminate, terminate-and-reencrypt)
 * - Security settings (IP restrictions, connection limits)
 * - Advanced options (timeout, headers, etc.)
 */
export class SmartProxy extends plugins.EventEmitter {
  // Port manager handles dynamic listener management
  private portManager: PortManager;
  private connectionLogger: NodeJS.Timeout | null = null;
  private isShuttingDown: boolean = false;
  
  // Component managers
  public connectionManager: ConnectionManager;
  public securityManager: SecurityManager;
  public tlsManager: TlsManager;
  public httpProxyBridge: HttpProxyBridge;
  public timeoutManager: TimeoutManager;
  public routeManager: RouteManager;
  public routeConnectionHandler: RouteConnectionHandler;
  public nftablesManager: NFTablesManager;
  
  // Certificate manager for ACME and static certificates
  public certManager: SmartCertManager | null = null;
  
  // Global challenge route tracking
  private globalChallengeRouteActive: boolean = false;
  private routeUpdateLock: any = null; // Will be initialized as AsyncMutex
  public acmeStateManager: AcmeStateManager;
  
  // Metrics collector
  public metricsCollector: MetricsCollector;
  
  // Track port usage across route updates
  private portUsageMap: Map<number, Set<string>> = new Map();
  
  /**
   * Constructor for SmartProxy
   *
   * @param settingsArg Configuration options containing routes and other settings
   * Routes define how traffic is matched and handled, with each route having:
   * - match: criteria for matching traffic (ports, domains, paths, IPs)
   * - action: what to do with matched traffic (forward, redirect, block)
   *
   * Example:
   * ```ts
   * const proxy = new SmartProxy({
   *   routes: [
   *     {
   *       match: {
   *         ports: 443,
   *         domains: ['example.com', '*.example.com']
   *       },
   *       action: {
   *         type: 'forward',
   *         target: { host: '10.0.0.1', port: 8443 },
   *         tls: { mode: 'passthrough' }
   *       }
   *     }
   *   ],
   *   defaults: {
   *     target: { host: 'localhost', port: 8080 },
   *     security: { ipAllowList: ['*'] }
   *   }
   * });
   * ```
   */
  constructor(settingsArg: ISmartProxyOptions) {
    super();
    
    // Set reasonable defaults for all settings
    this.settings = {
      ...settingsArg,
      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,
      httpProxyPort: settingsArg.httpProxyPort || 8443,
    };
    
    // Normalize ACME options if provided (support both email and accountEmail)
    if (this.settings.acme) {
      // Support both 'email' and 'accountEmail' fields
      if (this.settings.acme.accountEmail && !this.settings.acme.email) {
        this.settings.acme.email = this.settings.acme.accountEmail;
      }
      
      // Set reasonable defaults for commonly used fields
      this.settings.acme = {
        enabled: this.settings.acme.enabled !== false, // Enable by default if acme object exists
        port: this.settings.acme.port || 80,
        email: this.settings.acme.email,
        useProduction: this.settings.acme.useProduction || false,
        renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
        autoRenew: this.settings.acme.autoRenew !== false, // Enable by default
        certificateStore: this.settings.acme.certificateStore || './certs',
        skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
        renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours || 24,
        routeForwards: this.settings.acme.routeForwards || [],
        ...this.settings.acme // Preserve any additional fields
      };
    }
    
    // Initialize component managers
    this.timeoutManager = new TimeoutManager(this);
    this.securityManager = new SecurityManager(this);
    this.connectionManager = new ConnectionManager(this);
    
    // Create the route manager with SharedRouteManager API
    // Create a logger adapter to match ILogger interface
    const loggerAdapter = {
      debug: (message: string, data?: any) => logger.log('debug', message, data),
      info: (message: string, data?: any) => logger.log('info', message, data),
      warn: (message: string, data?: any) => logger.log('warn', message, data),
      error: (message: string, data?: any) => logger.log('error', message, data)
    };
    
    this.routeManager = new RouteManager({
      logger: loggerAdapter,
      enableDetailedLogging: this.settings.enableDetailedLogging,
      routes: this.settings.routes
    });

    
    // Create other required components
    this.tlsManager = new TlsManager(this);
    this.httpProxyBridge = new HttpProxyBridge(this);
    
    // Initialize connection handler with route support
    this.routeConnectionHandler = new RouteConnectionHandler(this);

    // Initialize port manager
    this.portManager = new PortManager(this);
    
    // Initialize NFTablesManager
    this.nftablesManager = new NFTablesManager(this);
    
    // Initialize route update mutex for synchronization
    this.routeUpdateLock = new Mutex();
    
    // Initialize ACME state manager
    this.acmeStateManager = new AcmeStateManager();
    
    // Initialize metrics collector with reference to this SmartProxy instance
    this.metricsCollector = new MetricsCollector(this);
  }
  
  /**
   * The settings for the SmartProxy
   */
  public settings: ISmartProxyOptions;
  
  /**
   * Helper method to create and configure certificate manager
   * This ensures consistent setup including the required ACME callback
   */
  private async createCertificateManager(
    routes: IRouteConfig[],
    certStore: string = './certs',
    acmeOptions?: any,
    initialState?: { challengeRouteActive?: boolean }
  ): Promise<SmartCertManager> {
    const certManager = new SmartCertManager(routes, certStore, acmeOptions, initialState);
    
    // Always set up the route update callback for ACME challenges
    certManager.setUpdateRoutesCallback(async (routes) => {
      await this.updateRoutes(routes);
    });
    
    // Connect with HttpProxy if available
    if (this.httpProxyBridge.getHttpProxy()) {
      certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
    }
    
    // Set the ACME state manager
    certManager.setAcmeStateManager(this.acmeStateManager);
    
    // Pass down the global ACME config if available
    if (this.settings.acme) {
      certManager.setGlobalAcmeDefaults(this.settings.acme);
    }
    
    await certManager.initialize();
    return certManager;
  }

  /**
   * Initialize certificate manager
   */
  private async initializeCertificateManager(): Promise<void> {
    // Extract global ACME options if any routes use auto certificates
    const autoRoutes = this.settings.routes.filter(r => 
      r.action.tls?.certificate === 'auto'
    );
    
    if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) {
      logger.log('info', 'No routes require certificate management', { component: 'certificate-manager' });
      return;
    }
    
    // Prepare ACME options with priority:
    // 1. Use top-level ACME config if available
    // 2. Fall back to first auto route's ACME config
    // 3. Otherwise use undefined
    let acmeOptions: { email?: string; useProduction?: boolean; port?: number } | undefined;
    
    if (this.settings.acme?.email) {
      // Use top-level ACME config
      acmeOptions = {
        email: this.settings.acme.email,
        useProduction: this.settings.acme.useProduction || false,
        port: this.settings.acme.port || 80
      };
      logger.log('info', `Using top-level ACME configuration with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
    } else if (autoRoutes.length > 0) {
      // Check for route-level ACME config
      const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email);
      if (routeWithAcme?.action.tls?.acme) {
        const routeAcme = routeWithAcme.action.tls.acme;
        acmeOptions = {
          email: routeAcme.email,
          useProduction: routeAcme.useProduction || false,
          port: routeAcme.challengePort || 80
        };
        logger.log('info', `Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
      }
    }
    
    // Validate we have required configuration
    if (autoRoutes.length > 0 && !acmeOptions?.email) {
      throw new Error(
        'ACME email is required for automatic certificate provisioning. ' +
        'Please provide email in either:\n' +
        '1. Top-level "acme" configuration\n' +
        '2. Individual route\'s "tls.acme" configuration'
      );
    }
    
    // Use the helper method to create and configure the certificate manager
    this.certManager = await this.createCertificateManager(
      this.settings.routes,
      this.settings.acme?.certificateStore || './certs',
      acmeOptions
    );
  }
  
  /**
   * Check if we have routes with static certificates
   */
  private hasStaticCertRoutes(): boolean {
    return this.settings.routes.some(r => 
      r.action.tls?.certificate && 
      r.action.tls.certificate !== 'auto'
    );
  }
  
  /**
   * Start the proxy server with support for both configuration types
   */
  public async start() {
    // Don't start if already shutting down
    if (this.isShuttingDown) {
      logger.log('warn', "Cannot start SmartProxy while it's in the shutdown process");
      return;
    }

    // Validate the route configuration
    const configWarnings = this.routeManager.validateConfiguration();
    
    // Also validate ACME configuration
    const acmeWarnings = this.validateAcmeConfiguration();
    const allWarnings = [...configWarnings, ...acmeWarnings];
    
    if (allWarnings.length > 0) {
      logger.log('warn', `${allWarnings.length} configuration warnings found`, { count: allWarnings.length });
      for (const warning of allWarnings) {
        logger.log('warn', `${warning}`);
      }
    }

    // Get listening ports from RouteManager
    const listeningPorts = this.routeManager.getListeningPorts();

    // Initialize port usage tracking
    this.portUsageMap = this.updatePortUsageMap(this.settings.routes);
    
    // Log port usage for startup
    logger.log('info', `SmartProxy starting with ${listeningPorts.length} ports: ${listeningPorts.join(', ')}`, {
      portCount: listeningPorts.length,
      ports: listeningPorts,
      component: 'smart-proxy'
    });

    // Provision NFTables rules for routes that use NFTables
    for (const route of this.settings.routes) {
      if (route.action.forwardingEngine === 'nftables') {
        await this.nftablesManager.provisionRoute(route);
      }
    }

    // Initialize and start HttpProxy if needed - before port binding
    if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
      await this.httpProxyBridge.initialize();
      await this.httpProxyBridge.start();
    }

    // Start port listeners using the PortManager BEFORE initializing certificate manager
    // This ensures all required ports are bound and ready when adding ACME challenge routes
    await this.portManager.addPorts(listeningPorts);
    
    // Initialize certificate manager AFTER port binding is complete
    // This ensures the ACME challenge port is already bound and ready when needed
    await this.initializeCertificateManager();
    
    // Connect certificate manager with HttpProxy if both are available
    if (this.certManager && this.httpProxyBridge.getHttpProxy()) {
      this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
    }

    // Now that ports are listening, provision any required certificates
    if (this.certManager) {
      logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
      await this.certManager.provisionAllCertificates();
    }
    
    // Start the metrics collector now that all components are initialized
    this.metricsCollector.start();

    // 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 httpProxyConnections = 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) {
          httpProxyConnections++;
        }

        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
      logger.log('info', 'Connection statistics', {
        activeConnections: connectionRecords.size,
        tls: {
          total: tlsConnections,
          completed: completedTlsHandshakes,
          pending: pendingTlsHandshakes
        },
        nonTls: nonTlsConnections,
        keepAlive: keepAliveConnections,
        httpProxy: httpProxyConnections,
        longestRunning: {
          incoming: plugins.prettyMs(maxIncoming),
          outgoing: plugins.prettyMs(maxOutgoing)
        },
        terminationStats: {
          incoming: terminationStats.incoming,
          outgoing: terminationStats.outgoing
        },
        component: 'connection-manager'
      });
    }, this.settings.inactivityCheckInterval || 60000);

    // Make sure the interval doesn't keep the process alive
    if (this.connectionLogger.unref) {
      this.connectionLogger.unref();
    }
  }
  
  /**
   * Extract domain configurations from routes for certificate provisioning
   *
   * Note: This method has been removed as we now work directly with routes
   */
  
  /**
   * Stop the proxy server
   */
  public async stop() {
    logger.log('info', 'SmartProxy shutting down...');
    this.isShuttingDown = true;
    this.portManager.setShuttingDown(true);
    
    // Stop certificate manager
    if (this.certManager) {
      await this.certManager.stop();
      logger.log('info', 'Certificate manager stopped');
    }
    
    // Stop NFTablesManager
    await this.nftablesManager.stop();
    logger.log('info', 'NFTablesManager stopped');

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

    // Stop all port listeners
    await this.portManager.closeAll();
    logger.log('info', 'All servers closed. Cleaning up active connections...');

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

    // Stop HttpProxy
    await this.httpProxyBridge.stop();
    
    // Clear ACME state manager
    this.acmeStateManager.clear();
    
    // Stop metrics collector
    this.metricsCollector.stop();

    logger.log('info', 'SmartProxy shutdown complete.');
  }
  
  /**
   * Updates the domain configurations for the proxy
   *
   * Note: This legacy method has been removed. Use updateRoutes instead.
   */
  public async updateDomainConfigs(): Promise<void> {
    logger.log('warn', 'Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
    throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
  }
  
  /**
   * Verify the challenge route has been properly removed from routes
   */
  private async verifyChallengeRouteRemoved(): Promise<void> {
    const maxRetries = 10;
    const retryDelay = 100; // milliseconds
    
    for (let i = 0; i < maxRetries; i++) {
      // Check if the challenge route is still in the active routes
      const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge');
      
      if (!challengeRouteExists) {
        try {
          logger.log('info', 'Challenge route successfully removed from routes');
        } catch (error) {
          // Silently handle logging errors
          console.log('[INFO] Challenge route successfully removed from routes');
        }
        return;
      }
      
      // Wait before retrying
      await plugins.smartdelay.delayFor(retryDelay);
    }
    
    const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
    try {
      logger.log('error', error);
    } catch (logError) {
      // Silently handle logging errors
      console.log(`[ERROR] ${error}`);
    }
    throw new Error(error);
  }
  
  /**
   * Update routes with new configuration
   *
   * This method replaces the current route configuration with the provided routes.
   * It also provisions certificates for routes that require TLS termination and have
   * `certificate: 'auto'` set in their TLS configuration.
   *
   * @param newRoutes Array of route configurations to use
   *
   * Example:
   * ```ts
   * proxy.updateRoutes([
   *   {
   *     match: { ports: 443, domains: 'secure.example.com' },
   *     action: {
   *       type: 'forward',
   *       target: { host: '10.0.0.1', port: 8443 },
   *       tls: { mode: 'terminate', certificate: 'auto' }
   *     }
   *   }
   * ]);
   * ```
   */
  public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
    return this.routeUpdateLock.runExclusive(async () => {
      try {
        logger.log('info', `Updating routes (${newRoutes.length} routes)`, { 
          routeCount: newRoutes.length, 
          component: 'route-manager' 
        });
      } catch (error) {
        // Silently handle logging errors
        console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
      }

      // Track port usage before and after updates
      const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
      const newPortUsage = this.updatePortUsageMap(newRoutes);
      
      // Get the lists of currently listening ports and new ports needed
      const currentPorts = new Set(this.portManager.getListeningPorts());
      const newPortsSet = new Set(newPortUsage.keys());
      
      // Log the port usage for debugging
      try {
        logger.log('debug', `Current listening ports: ${Array.from(currentPorts).join(', ')}`, {
          ports: Array.from(currentPorts),
          component: 'smart-proxy'
        });
        
        logger.log('debug', `Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`, {
          ports: Array.from(newPortsSet),
          component: 'smart-proxy'
        });
      } catch (error) {
        // Silently handle logging errors
        console.log(`[DEBUG] Current listening ports: ${Array.from(currentPorts).join(', ')}`);
        console.log(`[DEBUG] Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`);
      }
      
      // Find orphaned ports - ports that no longer have any routes
      const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
      
      // Find new ports that need binding (only ports that we aren't already listening on)
      const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
      
      // Check for ACME challenge port to give it special handling
      const acmePort = this.settings.acme?.port || 80;
      const acmePortNeeded = newPortsSet.has(acmePort);
      const acmePortListed = newBindingPorts.includes(acmePort);
      
      if (acmePortNeeded && acmePortListed) {
        try {
          logger.log('info', `Adding ACME challenge port ${acmePort} to routes`, { 
            port: acmePort,
            component: 'smart-proxy'
          });
        } catch (error) {
          // Silently handle logging errors
          console.log(`[INFO] Adding ACME challenge port ${acmePort} to routes`);
        }
      }

      // Get existing routes that use NFTables and update them
      const oldNfTablesRoutes = this.settings.routes.filter(
        r => r.action.forwardingEngine === 'nftables'
      );
      
      const newNfTablesRoutes = newRoutes.filter(
        r => r.action.forwardingEngine === 'nftables'
      );
      
      // Update existing NFTables routes
      for (const oldRoute of oldNfTablesRoutes) {
        const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
        
        if (!newRoute) {
          // Route was removed
          await this.nftablesManager.deprovisionRoute(oldRoute);
        } else {
          // Route was updated
          await this.nftablesManager.updateRoute(oldRoute, newRoute);
        }
      }
      
      // Add new NFTables routes
      for (const newRoute of newNfTablesRoutes) {
        const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
        
        if (!oldRoute) {
          // New route
          await this.nftablesManager.provisionRoute(newRoute);
        }
      }

      // Update routes in RouteManager
      this.routeManager.updateRoutes(newRoutes);

      // Release orphaned ports first to free resources
      if (orphanedPorts.length > 0) {
        try {
          logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, { 
            ports: orphanedPorts,
            component: 'smart-proxy'
          });
        } catch (error) {
          // Silently handle logging errors
          console.log(`[INFO] Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`);
        }
        await this.portManager.removePorts(orphanedPorts);
      }
      
      // Add new ports if needed
      if (newBindingPorts.length > 0) {
        try {
          logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, { 
            ports: newBindingPorts,
            component: 'smart-proxy'
          });
        } catch (error) {
          // Silently handle logging errors
          console.log(`[INFO] Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
        }
        
        // Handle port binding with improved error recovery
        try {
          await this.portManager.addPorts(newBindingPorts);
        } catch (error) {
          // Special handling for port binding errors
          // This provides better diagnostics for ACME challenge port conflicts
          if ((error as any).code === 'EADDRINUSE') {
            const port = (error as any).port || newBindingPorts[0];
            const isAcmePort = port === acmePort;
            
            if (isAcmePort) {
              try {
                logger.log('warn', `Could not bind to ACME challenge port ${port}. It may be in use by another application.`, {
                  port,
                  component: 'smart-proxy'
                });
              } catch (logError) {
                console.log(`[WARN] Could not bind to ACME challenge port ${port}. It may be in use by another application.`);
              }
              
              // Re-throw with more helpful message
              throw new Error(
                `ACME challenge port ${port} is already in use by another application. ` +
                `Configure a different port in settings.acme.port (e.g., 8080) or free up port ${port}.`
              );
            }
          }
          
          // Re-throw the original error for other cases
          throw error;
        }
      }
      
      // Update settings with the new routes
      this.settings.routes = newRoutes;
      
      // Save the new port usage map for future reference
      this.portUsageMap = newPortUsage;

      // If HttpProxy is initialized, resync the configurations
      if (this.httpProxyBridge.getHttpProxy()) {
        await this.httpProxyBridge.syncRoutesToHttpProxy(newRoutes);
      }

      // Update certificate manager with new routes
      if (this.certManager) {
        const existingAcmeOptions = this.certManager.getAcmeOptions();
        const existingState = this.certManager.getState();
        
        // Store global state before stopping
        this.globalChallengeRouteActive = existingState.challengeRouteActive;
        
        // Only stop the cert manager if absolutely necessary
        // First check if there's an ACME route on the same port already
        const acmePort = existingAcmeOptions?.port || 80;
        const acmePortInUse = newPortUsage.has(acmePort) && newPortUsage.get(acmePort)!.size > 0;
        
        try {
          logger.log('debug', `ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`, {
            port: acmePort,
            inUse: acmePortInUse,
            component: 'smart-proxy'
          });
        } catch (error) {
          // Silently handle logging errors
          console.log(`[DEBUG] ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`);
        }
        
        await this.certManager.stop();
        
        // Verify the challenge route has been properly removed
        await this.verifyChallengeRouteRemoved();
        
        // Create new certificate manager with preserved state
        this.certManager = await this.createCertificateManager(
          newRoutes,
          './certs',
          existingAcmeOptions,
          { challengeRouteActive: this.globalChallengeRouteActive }
        );
      }
    });
  }
  
  /**
   * Manually provision a certificate for a route
   */
  public async provisionCertificate(routeName: string): Promise<void> {
    if (!this.certManager) {
      throw new Error('Certificate manager not initialized');
    }
    
    const route = this.settings.routes.find(r => r.name === routeName);
    if (!route) {
      throw new Error(`Route ${routeName} not found`);
    }
    
    await this.certManager.provisionCertificate(route);
  }

  /**
   * Update the port usage map based on the provided routes
   * 
   * This tracks which ports are used by which routes, allowing us to
   * detect when a port is no longer needed and can be released.
   */
  private updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
    // Reset the usage map
    const portUsage = new Map<number, Set<string>>();
    
    for (const route of routes) {
      // Get the ports for this route
      const portsConfig = Array.isArray(route.match.ports) 
        ? route.match.ports 
        : [route.match.ports];
      
      // Expand port range objects to individual port numbers
      const expandedPorts: number[] = [];
      for (const portConfig of portsConfig) {
        if (typeof portConfig === 'number') {
          expandedPorts.push(portConfig);
        } else if (typeof portConfig === 'object' && 'from' in portConfig && 'to' in portConfig) {
          // Expand the port range
          for (let p = portConfig.from; p <= portConfig.to; p++) {
            expandedPorts.push(p);
          }
        }
      }
      
      // Use route name if available, otherwise generate a unique ID
      const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
      
      // Add each port to the usage map
      for (const port of expandedPorts) {
        if (!portUsage.has(port)) {
          portUsage.set(port, new Set());
        }
        portUsage.get(port)!.add(routeName);
      }
    }
    
    // Log port usage for debugging
    for (const [port, routes] of portUsage.entries()) {
      try {
        logger.log('debug', `Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`, {
          port,
          routeCount: routes.size,
          component: 'smart-proxy'
        });
      } catch (error) {
        // Silently handle logging errors
        console.log(`[DEBUG] Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`);
      }
    }
    
    return portUsage;
  }
  
  /**
   * Find ports that have no routes in the new configuration
   */
  private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
    const orphanedPorts: number[] = [];
    
    for (const [port, routes] of oldUsage.entries()) {
      if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
        orphanedPorts.push(port);
        try {
          logger.log('info', `Port ${port} no longer has any associated routes, will be released`, {
            port,
            component: 'smart-proxy'
          });
        } catch (error) {
          // Silently handle logging errors
          console.log(`[INFO] Port ${port} no longer has any associated routes, will be released`);
        }
      }
    }
    
    return orphanedPorts;
  }
  
  /**
   * Force renewal of a certificate
   */
  public async renewCertificate(routeName: string): Promise<void> {
    if (!this.certManager) {
      throw new Error('Certificate manager not initialized');
    }
    
    await this.certManager.renewCertificate(routeName);
  }
  
  /**
   * Get certificate status for a route
   */
  public getCertificateStatus(routeName: string): ICertStatus | undefined {
    if (!this.certManager) {
      return undefined;
    }
    
    return this.certManager.getCertificateStatus(routeName);
  }
  
  /**
   * Get proxy metrics with clean API
   * 
   * @returns IMetrics interface with grouped metrics methods
   */
  public getMetrics(): IMetrics {
    return this.metricsCollector;
  }
  
  /**
   * 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('*')) {
      logger.log('warn', `Wildcard domains like "${domain}" are not supported for automatic ACME certificates`, { domain, component: 'certificate-manager' });
      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)) {
      logger.log('warn', `Domain "${domain}" has invalid format for certificate issuance`, { domain, component: 'certificate-manager' });
      return false;
    }
    
    return true;
  }
  
  /**
   * Add a new listening port without changing the route configuration
   *
   * This allows you to add a port listener without updating routes.
   * Useful for preparing to listen on a port before adding routes for it.
   *
   * @param port The port to start listening on
   * @returns Promise that resolves when the port is listening
   */
  public async addListeningPort(port: number): Promise<void> {
    return this.portManager.addPort(port);
  }

  /**
   * Stop listening on a specific port without changing the route configuration
   *
   * This allows you to stop a port listener without updating routes.
   * Useful for temporary maintenance or port changes.
   *
   * @param port The port to stop listening on
   * @returns Promise that resolves when the port is closed
   */
  public async removeListeningPort(port: number): Promise<void> {
    return this.portManager.removePort(port);
  }

  /**
   * Get a list of all ports currently being listened on
   *
   * @returns Array of port numbers
   */
  public getListeningPorts(): number[] {
    return this.portManager.getListeningPorts();
  }

  /**
   * 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 httpProxyConnections = 0;
    
    // Analyze active connections
    for (const record of connectionRecords.values()) {
      if (record.isTLS) tlsConnections++;
      else nonTlsConnections++;
      if (record.hasKeepAlive) keepAliveConnections++;
      if (record.usingNetworkProxy) httpProxyConnections++;
    }
    
    return {
      activeConnections: connectionRecords.size,
      tlsConnections,
      nonTlsConnections,
      keepAliveConnections,
      httpProxyConnections,
      terminationStats,
      acmeEnabled: !!this.certManager,
      port80HandlerPort: this.certManager ? 80 : null,
      routes: this.routeManager.getListeningPorts().length,
      listeningPorts: this.portManager.getListeningPorts(),
      activePorts: this.portManager.getListeningPorts().length
    };
  }
  
  /**
   * Get a list of eligible domains for ACME certificates
   */
  public getEligibleDomainsForCertificates(): string[] {
    const domains: string[] = [];
    
    // Get domains from routes
    const routes = this.settings.routes || [];
    
    for (const route of routes) {
      if (!route.match.domains) continue;
      
      // Skip routes without TLS termination or auto certificates
      if (route.action.type !== 'forward' || 
          !route.action.tls || 
          route.action.tls.mode === 'passthrough' || 
          route.action.tls.certificate !== 'auto') continue;
      
      const routeDomains = Array.isArray(route.match.domains) 
        ? route.match.domains 
        : [route.match.domains];
      
      // Skip domains that can't be used with ACME
      const eligibleDomains = routeDomains.filter(domain => 
        !domain.includes('*') && this.isValidDomain(domain)
      );
      
      domains.push(...eligibleDomains);
    }
    
    // Legacy mode is no longer supported
    
    return domains;
  }
  
  /**
   * Get NFTables status
   */
  public async getNfTablesStatus(): Promise<Record<string, any>> {
    return this.nftablesManager.getStatus();
  }
  
  /**
   * Validate ACME configuration
   */
  private validateAcmeConfiguration(): string[] {
    const warnings: string[] = [];
    
    // Check for routes with certificate: 'auto'
    const autoRoutes = this.settings.routes.filter(r => 
      r.action.tls?.certificate === 'auto'
    );
    
    if (autoRoutes.length === 0) {
      return warnings;
    }
    
    // Check if we have ACME email configuration
    const hasTopLevelEmail = this.settings.acme?.email;
    const routesWithEmail = autoRoutes.filter(r => r.action.tls?.acme?.email);
    
    if (!hasTopLevelEmail && routesWithEmail.length === 0) {
      warnings.push(
        'Routes with certificate: "auto" require ACME email configuration. ' +
        'Add email to either top-level "acme" config or individual route\'s "tls.acme" config.'
      );
    }
    
    // Check for port 80 availability for challenges
    if (autoRoutes.length > 0) {
      const challengePort = this.settings.acme?.port || 80;
      const portsInUse = this.routeManager.getListeningPorts();
      
      if (!portsInUse.includes(challengePort)) {
        warnings.push(
          `Port ${challengePort} is not configured for any routes but is needed for ACME challenges. ` +
          `Add a route listening on port ${challengePort} or ensure it's accessible for HTTP-01 challenges.`
        );
      }
    }
    
    // Check for mismatched environments
    if (this.settings.acme?.useProduction) {
      const stagingRoutes = autoRoutes.filter(r => 
        r.action.tls?.acme?.useProduction === false
      );
      if (stagingRoutes.length > 0) {
        warnings.push(
          'Top-level ACME uses production but some routes use staging. ' +
          'Consider aligning environments to avoid certificate issues.'
        );
      }
    }
    
    // Check for wildcard domains with auto certificates
    for (const route of autoRoutes) {
      const domains = Array.isArray(route.match.domains) 
        ? route.match.domains 
        : [route.match.domains];
      
      const wildcardDomains = domains.filter(d => d?.includes('*'));
      if (wildcardDomains.length > 0) {
        warnings.push(
          `Route "${route.name}" has wildcard domain(s) ${wildcardDomains.join(', ')} ` +
          'with certificate: "auto". Wildcard certificates require DNS-01 challenges, ' +
          'which are not currently supported. Use static certificates instead.'
        );
      }
    }
    
    return warnings;
  }

}