import * as plugins from '../../plugins.js';
import {
  createLogger,
} from './models/types.js';
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
import type {
  IHttpProxyOptions,
  ILogger
} from './models/types.js';
import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
import type { IRouteContext, IHttpRouteContext } from '../../core/models/route-context.js';
import { createBaseRouteContext } from '../../core/models/route-context.js';
import { CertificateManager } from './certificate-manager.js';
import { ConnectionPool } from './connection-pool.js';
import { RequestHandler, type IMetricsTracker } from './request-handler.js';
import { WebSocketHandler } from './websocket-handler.js';
import { HttpRouter } from '../../routing/router/index.js';
import { cleanupSocket } from '../../core/utils/socket-utils.js';
import { FunctionCache } from './function-cache.js';

/**
 * HttpProxy provides a reverse proxy with TLS termination, WebSocket support,
 * automatic certificate management, and high-performance connection pooling.
 * Handles all HTTP/HTTPS traffic including redirects, ACME challenges, and static routes.
 */
export class HttpProxy implements IMetricsTracker {
  // Provide a minimal JSON representation to avoid circular references during deep equality checks
  public toJSON(): any {
    return {};
  }
  // Configuration
  public options: IHttpProxyOptions;
  public routes: IRouteConfig[] = [];

  // Server instances (HTTP/2 with HTTP/1 fallback)
  public httpsServer: any;

  // Core components
  private certificateManager: CertificateManager;
  private connectionPool: ConnectionPool;
  private requestHandler: RequestHandler;
  private webSocketHandler: WebSocketHandler;
  private router = new HttpRouter(); // Unified HTTP router
  private routeManager: RouteManager;
  private functionCache: FunctionCache;
  
  // State tracking
  public socketMap = new plugins.lik.ObjectMap<plugins.net.Socket>();
  public activeContexts: Set<string> = new Set();
  public connectedClients: number = 0;
  public startTime: number = 0;
  public requestsServed: number = 0;
  public failedRequests: number = 0;
  
  // Tracking for SmartProxy integration
  private portProxyConnections: number = 0;
  private tlsTerminatedConnections: number = 0;
  
  // Timers
  private metricsInterval: NodeJS.Timeout;
  private connectionPoolCleanupInterval: NodeJS.Timeout;
  
  // Logger
  private logger: ILogger;

  /**
   * Creates a new HttpProxy instance
   */
  constructor(optionsArg: IHttpProxyOptions) {
    // Set default options
    this.options = {
      port: optionsArg.port,
      maxConnections: optionsArg.maxConnections || 10000,
      keepAliveTimeout: optionsArg.keepAliveTimeout || 120000, // 2 minutes 
      headersTimeout: optionsArg.headersTimeout || 60000, // 1 minute
      logLevel: optionsArg.logLevel || 'info',
      cors: optionsArg.cors || {
        allowOrigin: '*',
        allowMethods: 'GET, POST, PUT, DELETE, OPTIONS',
        allowHeaders: 'Content-Type, Authorization',
        maxAge: 86400
      },
      // Defaults for SmartProxy integration
      connectionPoolSize: optionsArg.connectionPoolSize || 50,
      portProxyIntegration: optionsArg.portProxyIntegration || false,
      // Backend protocol (http1 or http2)
      backendProtocol: optionsArg.backendProtocol || 'http1',
      // Default ACME options
      acme: {
        enabled: optionsArg.acme?.enabled || false,
        port: optionsArg.acme?.port || 80,
        accountEmail: optionsArg.acme?.accountEmail || 'admin@example.com',
        useProduction: optionsArg.acme?.useProduction || false, // Default to staging for safety
        renewThresholdDays: optionsArg.acme?.renewThresholdDays || 30,
        autoRenew: optionsArg.acme?.autoRenew !== false, // Default to true
        certificateStore: optionsArg.acme?.certificateStore || './certs',
        skipConfiguredCerts: optionsArg.acme?.skipConfiguredCerts || false
      }
    };
    
    // Initialize logger
    this.logger = createLogger(this.options.logLevel);

    // Initialize route manager
    this.routeManager = new RouteManager({
      logger: this.logger,
      enableDetailedLogging: this.options.logLevel === 'debug',
      routes: []
    });

    // Initialize function cache
    this.functionCache = new FunctionCache(this.logger, {
      maxCacheSize: this.options.functionCacheSize || 1000,
      defaultTtl: this.options.functionCacheTtl || 5000
    });

    // Initialize other components
    this.certificateManager = new CertificateManager(this.options);
    this.connectionPool = new ConnectionPool(this.options);
    this.requestHandler = new RequestHandler(
      this.options,
      this.connectionPool,
      this.routeManager,
      this.functionCache,
      this.router
    );
    this.webSocketHandler = new WebSocketHandler(
      this.options,
      this.connectionPool,
      this.routes // Pass current routes to WebSocketHandler
    );

    // Connect request handler to this metrics tracker
    this.requestHandler.setMetricsTracker(this);

    // Initialize with any provided routes
    if (this.options.routes && this.options.routes.length > 0) {
      this.updateRouteConfigs(this.options.routes);
    }
  }

  /**
   * Implements IMetricsTracker interface to increment request counters
   */
  public incrementRequestsServed(): void {
    this.requestsServed++;
  }
  
  /**
   * Implements IMetricsTracker interface to increment failed request counters
   */
  public incrementFailedRequests(): void {
    this.failedRequests++;
  }

  /**
   * Returns the port number this HttpProxy is listening on
   * Useful for SmartProxy to determine where to forward connections
   */
  public getListeningPort(): number {
    // If the server is running, get the actual listening port
    if (this.httpsServer && this.httpsServer.address()) {
      const address = this.httpsServer.address();
      if (address && typeof address === 'object' && 'port' in address) {
        return address.port;
      }
    }
    // Fallback to configured port
    return this.options.port;
  }

  /**
   * Updates the server capacity settings
   * @param maxConnections Maximum number of simultaneous connections
   * @param keepAliveTimeout Keep-alive timeout in milliseconds
   * @param connectionPoolSize Size of the connection pool per backend
   */
  public updateCapacity(maxConnections?: number, keepAliveTimeout?: number, connectionPoolSize?: number): void {
    if (maxConnections !== undefined) {
      this.options.maxConnections = maxConnections;
      this.logger.info(`Updated max connections to ${maxConnections}`);
    }
    
    if (keepAliveTimeout !== undefined) {
      this.options.keepAliveTimeout = keepAliveTimeout;
      
      if (this.httpsServer) {
        this.httpsServer.keepAliveTimeout = keepAliveTimeout;
        this.logger.info(`Updated keep-alive timeout to ${keepAliveTimeout}ms`);
      }
    }
    
    if (connectionPoolSize !== undefined) {
      this.options.connectionPoolSize = connectionPoolSize;
      this.logger.info(`Updated connection pool size to ${connectionPoolSize}`);
      
      // Clean up excess connections in the pool
      this.connectionPool.cleanupConnectionPool();
    }
  }

  /**
   * Returns current server metrics
   * Useful for SmartProxy to determine which HttpProxy to use for load balancing
   */
  public getMetrics(): any {
    return {
      activeConnections: this.connectedClients,
      totalRequests: this.requestsServed,
      failedRequests: this.failedRequests,
      portProxyConnections: this.portProxyConnections,
      tlsTerminatedConnections: this.tlsTerminatedConnections,
      connectionPoolSize: this.connectionPool.getPoolStatus(),
      uptime: Math.floor((Date.now() - this.startTime) / 1000),
      memoryUsage: process.memoryUsage(),
      activeWebSockets: this.webSocketHandler.getConnectionInfo().activeConnections,
      functionCache: this.functionCache.getStats()
    };
  }

  /**
   * Starts the proxy server
   */
  public async start(): Promise<void> {
    this.startTime = Date.now();
    
    // Create HTTP/2 server with HTTP/1 fallback
    this.httpsServer = plugins.http2.createSecureServer(
      {
        key: this.certificateManager.getDefaultCertificates().key,
        cert: this.certificateManager.getDefaultCertificates().cert,
        allowHTTP1: true,
        ALPNProtocols: ['h2', 'http/1.1']
      }
    );

    // Track raw TCP connections for metrics and limits
    this.setupConnectionTracking();

    // Handle incoming HTTP/2 streams
    this.httpsServer.on('stream', (stream: any, headers: any) => {
      this.requestHandler.handleHttp2(stream, headers);
    });
    // Handle HTTP/1.x fallback requests
    this.httpsServer.on('request', (req: any, res: any) => {
      this.requestHandler.handleRequest(req, res);
    });

    // Share server with certificate manager for dynamic contexts
    this.certificateManager.setHttpsServer(this.httpsServer);
    // Setup WebSocket support on HTTP/1 fallback
    this.webSocketHandler.initialize(this.httpsServer);
    // Start metrics logging
    this.setupMetricsCollection();
    // Start periodic connection pool cleanup
    this.connectionPoolCleanupInterval = this.connectionPool.setupPeriodicCleanup();

    // Start the server
    return new Promise((resolve) => {
      this.httpsServer.listen(this.options.port, () => {
        this.logger.info(`HttpProxy started on port ${this.options.port}`);
        resolve();
      });
    });
  }

  /**
   * Sets up tracking of TCP connections
   */
  private setupConnectionTracking(): void {
    this.httpsServer.on('connection', (connection: plugins.net.Socket) => {
      // Check if max connections reached
      if (this.socketMap.getArray().length >= this.options.maxConnections) {
        this.logger.warn(`Max connections (${this.options.maxConnections}) reached, rejecting new connection`);
        connection.destroy();
        return;
      }

      // Add connection to tracking
      this.socketMap.add(connection);
      this.connectedClients = this.socketMap.getArray().length;
      
      // Check for connection from SmartProxy by inspecting the source port
      const localPort = connection.localPort || 0;
      const remotePort = connection.remotePort || 0;
      
      // If this connection is from a SmartProxy (usually indicated by it coming from localhost)
      if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
        this.portProxyConnections++;
        this.logger.debug(`New connection from SmartProxy (local: ${localPort}, remote: ${remotePort})`);
      } else {
        this.logger.debug(`New direct connection (local: ${localPort}, remote: ${remotePort})`);
      }
      
      // Setup connection cleanup handlers
      const cleanupConnection = () => {
        if (this.socketMap.checkForObject(connection)) {
          this.socketMap.remove(connection);
          this.connectedClients = this.socketMap.getArray().length;
          
          // If this was a SmartProxy connection, decrement the counter
          if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
            this.portProxyConnections--;
          }
          
          this.logger.debug(`Connection closed. ${this.connectedClients} connections remaining`);
        }
      };
      
      connection.on('close', cleanupConnection);
      connection.on('error', (err) => {
        this.logger.debug('Connection error', err);
        cleanupConnection();
      });
      connection.on('end', cleanupConnection);
    });
    
    // Track TLS handshake completions
    this.httpsServer.on('secureConnection', (tlsSocket) => {
      this.tlsTerminatedConnections++;
      this.logger.debug('TLS handshake completed, connection secured');
    });
  }

  /**
   * Sets up metrics collection 
   */
  private setupMetricsCollection(): void {
    this.metricsInterval = setInterval(() => {
      const uptime = Math.floor((Date.now() - this.startTime) / 1000);
      const metrics = {
        uptime,
        activeConnections: this.connectedClients,
        totalRequests: this.requestsServed,
        failedRequests: this.failedRequests,
        portProxyConnections: this.portProxyConnections,
        tlsTerminatedConnections: this.tlsTerminatedConnections,
        activeWebSockets: this.webSocketHandler.getConnectionInfo().activeConnections,
        memoryUsage: process.memoryUsage(),
        activeContexts: Array.from(this.activeContexts),
        connectionPool: this.connectionPool.getPoolStatus()
      };
      
      this.logger.debug('Proxy metrics', metrics);
    }, 60000); // Log metrics every minute
    
    // Don't keep process alive just for metrics
    if (this.metricsInterval.unref) {
      this.metricsInterval.unref();
    }
  }

  /**
   * Updates the route configurations - this is the primary method for configuring HttpProxy
   * @param routes The new route configurations to use
   */
  public async updateRouteConfigs(routes: IRouteConfig[]): Promise<void> {
    this.logger.info(`Updating route configurations (${routes.length} routes)`);

    // Update routes in RouteManager, modern router, WebSocketHandler, and SecurityManager
    this.routeManager.updateRoutes(routes);
    this.router.setRoutes(routes);
    this.webSocketHandler.setRoutes(routes);
    this.requestHandler.securityManager.setRoutes(routes);
    this.routes = routes;

    // Directly update the certificate manager with the new routes
    // This will extract domains and handle certificate provisioning
    this.certificateManager.updateRoutes(routes);

    // Collect all domains and certificates for configuration
    const currentHostnames = new Set<string>();
    const certificateUpdates = new Map<string, { cert: string, key: string }>();

    // Process each route to extract domain and certificate information
    for (const route of routes) {
      // Skip non-forward routes or routes without domains
      if (route.action.type !== 'forward' || !route.match.domains) {
        continue;
      }

      // Get domains from route
      const domains = Array.isArray(route.match.domains)
        ? route.match.domains
        : [route.match.domains];

      // Process each domain
      for (const domain of domains) {
        // Skip wildcard domains for direct host configuration
        if (domain.includes('*')) {
          continue;
        }

        currentHostnames.add(domain);

        // Check if we have a static certificate for this domain
        if (route.action.tls?.certificate && route.action.tls.certificate !== 'auto') {
          certificateUpdates.set(domain, {
            cert: route.action.tls.certificate.cert,
            key: route.action.tls.certificate.key
          });
        }
      }
    }

    // Update certificate cache with any static certificates
    for (const [domain, certData] of certificateUpdates.entries()) {
      try {
        this.certificateManager.updateCertificate(
          domain,
          certData.cert,
          certData.key
        );

        this.activeContexts.add(domain);
      } catch (error) {
        this.logger.error(`Failed to add SSL context for ${domain}`, error);
      }
    }

    // Clean up removed contexts
    for (const hostname of this.activeContexts) {
      if (!currentHostnames.has(hostname)) {
        this.logger.info(`Hostname ${hostname} removed from configuration`);
        this.activeContexts.delete(hostname);
      }
    }

    // Update the router with new routes
    this.router.setRoutes(routes);

    // Update WebSocket handler with new routes
    this.webSocketHandler.setRoutes(routes);

    this.logger.info(`Route configuration updated with ${routes.length} routes`);
  }

  // Legacy methods have been removed.
  // Please use updateRouteConfigs() directly with modern route-based configuration.

  /**
   * Adds default headers to be included in all responses
   */
  public async addDefaultHeaders(headersArg: { [key: string]: string }): Promise<void> {
    this.logger.info('Adding default headers', headersArg);
    this.requestHandler.setDefaultHeaders(headersArg);
  }

  /**
   * Stops the proxy server
   */
  public async stop(): Promise<void> {
    this.logger.info('Stopping HttpProxy server');
    
    // Clear intervals
    if (this.metricsInterval) {
      clearInterval(this.metricsInterval);
    }
    
    if (this.connectionPoolCleanupInterval) {
      clearInterval(this.connectionPoolCleanupInterval);
    }
    
    // Stop WebSocket handler
    this.webSocketHandler.shutdown();
    
    // Close all tracked sockets
    const socketCleanupPromises = this.socketMap.getArray().map(socket => 
      cleanupSocket(socket, 'http-proxy-stop', { immediate: true })
    );
    await Promise.all(socketCleanupPromises);
    
    // Close all connection pool connections
    this.connectionPool.closeAllConnections();
    
    // Certificate management cleanup is handled by SmartCertManager
    
    // Close the HTTPS server
    return new Promise((resolve) => {
      this.httpsServer.close(() => {
        this.logger.info('HttpProxy server stopped successfully');
        resolve();
      });
    });
  }

  /**
   * Requests a new certificate for a domain
   * This can be used to manually trigger certificate issuance
   * @param domain The domain to request a certificate for
   * @returns A promise that resolves when the request is submitted (not when the certificate is issued)
   */
  public async requestCertificate(domain: string): Promise<boolean> {
    this.logger.warn('requestCertificate is deprecated - use SmartCertManager instead');
    return false;
  }
  
  /**
   * Update certificate for a domain
   * 
   * This method allows direct updates of certificates from external sources
   * like Port80Handler or custom certificate providers.
   * 
   * @param domain The domain to update certificate for
   * @param certificate The new certificate (public key)
   * @param privateKey The new private key
   * @param expiryDate Optional expiry date
   */
  public updateCertificate(
    domain: string,
    certificate: string,
    privateKey: string,
    expiryDate?: Date
  ): void {
    this.logger.info(`Updating certificate for ${domain}`);
    this.certificateManager.updateCertificate(domain, certificate, privateKey);
  }

  /**
   * Gets all route configurations currently in use
   */
  public getRouteConfigs(): IRouteConfig[] {
    return this.routeManager.getRoutes();
  }
}