/**
 * Reverse Proxy for Onebox
 *
 * Delegates to Caddy (running as Docker service) for production-grade reverse proxy
 * with native SNI support, HTTP/2, WebSocket proxying, and zero-downtime configuration updates.
 *
 * Routes use Docker service names (e.g., onebox-hello-world:80) for container-to-container
 * communication within the Docker overlay network.
 */

import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';
import { CaddyManager } from './caddy.ts';

interface IProxyRoute {
  domain: string;
  targetHost: string;
  targetPort: number;
  serviceId: number;
  serviceName?: string;
}

export class OneboxReverseProxy {
  private oneboxRef: any;
  private database: OneboxDatabase;
  private caddy: CaddyManager;
  private routes: Map<string, IProxyRoute> = new Map();
  private httpPort = 8080;  // Default to dev ports (will be overridden if production)
  private httpsPort = 8443;

  constructor(oneboxRef: any) {
    this.oneboxRef = oneboxRef;
    this.database = oneboxRef.database;
    this.caddy = new CaddyManager({
      httpPort: this.httpPort,
      httpsPort: this.httpsPort,
    });
  }

  /**
   * Initialize reverse proxy - Caddy runs as Docker service, no setup needed
   */
  async init(): Promise<void> {
    logger.info('Reverse proxy initialized (Caddy Docker service)');
  }

  /**
   * Start the HTTP/HTTPS reverse proxy server
   * Caddy handles both HTTP and HTTPS on the configured ports
   */
  async startHttp(port?: number): Promise<void> {
    if (port) {
      this.httpPort = port;
      this.caddy.setPorts(this.httpPort, this.httpsPort);
    }

    try {
      // Start Caddy (handles both HTTP and HTTPS)
      await this.caddy.start();
      logger.success(`Reverse proxy started on port ${this.httpPort} (Caddy Docker service)`);
    } catch (error) {
      logger.error(`Failed to start reverse proxy: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Start HTTPS - Caddy already handles HTTPS when started
   * This method exists for interface compatibility
   */
  async startHttps(port?: number): Promise<void> {
    if (port) {
      this.httpsPort = port;
      this.caddy.setPorts(this.httpPort, this.httpsPort);
    }
    // Caddy handles both HTTP and HTTPS together
    // If already running, just log and optionally reload with new port
    const status = this.caddy.getStatus();
    if (status.running) {
      logger.info(`HTTPS already running on port ${this.httpsPort} via Caddy`);
    } else {
      await this.caddy.start();
    }
  }

  /**
   * Stop the reverse proxy
   */
  async stop(): Promise<void> {
    await this.caddy.stop();
    logger.info('Reverse proxy stopped');
  }

  /**
   * Add a route for a service
   * Uses Docker service name for upstream (Caddy runs in same Docker network)
   */
  async addRoute(serviceId: number, domain: string, targetPort: number): Promise<void> {
    try {
      // Get service info from database
      const service = this.database.getServiceByID(serviceId);
      if (!service) {
        throw new Error(`Service not found: ${serviceId}`);
      }

      // Use Docker service name as upstream target
      // Caddy runs on the same Docker network, so it can resolve service names directly
      const serviceName = `onebox-${service.name}`;
      const targetHost = serviceName;

      const route: IProxyRoute = {
        domain,
        targetHost,
        targetPort,
        serviceId,
        serviceName,
      };

      this.routes.set(domain, route);

      // Add route to Caddy using Docker service name
      const upstream = `${targetHost}:${targetPort}`;
      await this.caddy.addRoute(domain, upstream);

      logger.success(`Added proxy route: ${domain} -> ${upstream}`);
    } catch (error) {
      logger.error(`Failed to add route for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Remove a route
   */
  removeRoute(domain: string): void {
    if (this.routes.delete(domain)) {
      // Remove from Caddy (async but we don't wait)
      this.caddy.removeRoute(domain).catch((error) => {
        logger.error(`Failed to remove Caddy route for ${domain}: ${getErrorMessage(error)}`);
      });
      logger.success(`Removed proxy route: ${domain}`);
    } else {
      logger.warn(`Route not found: ${domain}`);
    }
  }

  /**
   * Get all routes
   */
  getRoutes(): IProxyRoute[] {
    return Array.from(this.routes.values());
  }

  /**
   * Reload routes from database
   */
  async reloadRoutes(): Promise<void> {
    try {
      logger.info('Reloading proxy routes...');

      // Clear local and Caddy routes
      this.routes.clear();
      this.caddy.clear();

      const services = this.database.getAllServices();

      for (const service of services) {
        // Route by domain if running (containerID is the service ID for Swarm services)
        if (service.domain && service.status === 'running' && service.containerID) {
          await this.addRoute(service.id!, service.domain, service.port);
        }
      }

      logger.success(`Loaded ${this.routes.size} proxy routes`);
    } catch (error) {
      logger.error(`Failed to reload routes: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Add TLS certificate for a domain
   * Sends PEM content to Caddy via Admin API
   */
  async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
    if (!certPem || !keyPem) {
      logger.warn(`Cannot add certificate for ${domain}: missing PEM content`);
      return;
    }

    await this.caddy.addCertificate(domain, certPem, keyPem);
  }

  /**
   * Remove TLS certificate for a domain
   */
  removeCertificate(domain: string): void {
    this.caddy.removeCertificate(domain).catch((error) => {
      logger.error(`Failed to remove certificate for ${domain}: ${getErrorMessage(error)}`);
    });
  }

  /**
   * Reload TLS certificates from database
   */
  async reloadCertificates(): Promise<void> {
    try {
      logger.info('Reloading TLS certificates from database...');

      const certificates = this.database.getAllSSLCertificates();

      for (const cert of certificates) {
        // Use fullchainPem for the cert (includes intermediates) and keyPem for the key
        if (cert.domain && cert.fullchainPem && cert.keyPem) {
          await this.caddy.addCertificate(cert.domain, cert.fullchainPem, cert.keyPem);
        } else {
          logger.warn(`Skipping certificate for ${cert.domain}: missing PEM content`);
        }
      }

      logger.success(`Loaded ${this.caddy.getCertificates().length} TLS certificates`);
    } catch (error) {
      logger.error(`Failed to reload certificates: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get status of reverse proxy
   */
  getStatus() {
    const caddyStatus = this.caddy.getStatus();
    return {
      http: {
        running: caddyStatus.running,
        port: caddyStatus.httpPort,
      },
      https: {
        running: caddyStatus.running,
        port: caddyStatus.httpsPort,
        certificates: caddyStatus.certificates,
      },
      routes: caddyStatus.routes,
      backend: 'caddy-docker',
    };
  }
}
