/**
 * Reverse Proxy for Onebox
 *
 * Delegates to SmartProxy (running as Docker service) for production-grade reverse proxy
 * with TLS termination, 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 { normalizeHostname } from '../utils/domain.ts';
import { OneboxDatabase } from './database.ts';
import { SmartProxyManager } from './smartproxy.ts';

const adminUiRouteName = 'onebox-admin-ui';
const adminUiPort = 3000;

interface IProxyRoute {
  domain: string;
  targetHost: string;
  targetPort: number;
  serviceId?: number;
  serviceName?: string;
  routeType: 'service' | 'admin-ui';
}

export class OneboxReverseProxy {
  private oneboxRef: any;
  private database: OneboxDatabase;
  private smartProxy: SmartProxyManager;
  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.smartProxy = new SmartProxyManager({
      httpPort: this.httpPort,
      httpsPort: this.httpsPort,
    });
  }

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

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

    try {
      await this.smartProxy.start();
      logger.success(`Reverse proxy started on port ${this.httpPort} (SmartProxy Docker service)`);
    } catch (error) {
      logger.error(`Failed to start reverse proxy: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Start HTTPS - SmartProxy already handles HTTPS when started
   * This method exists for interface compatibility
   */
  async startHttps(port?: number): Promise<void> {
    if (port) {
      this.httpsPort = port;
      this.smartProxy.setPorts(this.httpPort, this.httpsPort);
    }
    const status = this.smartProxy.getStatus();
    if (status.running) {
      logger.info(`HTTPS already running on port ${this.httpsPort} via SmartProxy`);
    } else {
      logger.warn('Skipping HTTPS reverse proxy startup because SmartProxy is not running');
    }
  }

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

  /**
   * Add a route for a service
   * Uses Docker service name for upstream (SmartProxy 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
      // SmartProxy 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,
        routeType: 'service',
      };

      this.routes.set(domain, route);

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

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

  async addAdminUiRoute(domain: string): Promise<void> {
    const normalizedDomain = normalizeHostname(domain);
    if (!normalizedDomain) return;

    const targetHost = this.getAdminUiTargetHost();
    const route: IProxyRoute = {
      domain: normalizedDomain,
      targetHost,
      targetPort: adminUiPort,
      serviceName: adminUiRouteName,
      routeType: 'admin-ui',
    };

    this.routes.set(normalizedDomain, route);
    const upstream = `${targetHost}:${adminUiPort}`;
    await this.smartProxy.addRoute(normalizedDomain, upstream);
    logger.success(`Added Admin UI proxy route: ${normalizedDomain} -> ${upstream}`);
  }

  /**
   * Remove a route
   */
  async removeRoute(domain: string): Promise<void> {
    if (this.routes.delete(domain)) {
      await this.smartProxy.removeRoute(domain);
      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 SmartProxy routes
      this.routes.clear();
      this.smartProxy.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);
        }
      }

      const adminUiDomain = this.getAdminUiDomain();
      if (adminUiDomain) {
        await this.addAdminUiRoute(adminUiDomain);
      }

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

  private getAdminUiDomain(): string {
    return normalizeHostname(this.database.getSetting('adminUiDomain') || '');
  }

  private getAdminUiTargetHost(): string {
    const serverIP = this.database.getSetting('serverIP');
    if (!serverIP) {
      logger.warn('serverIP is not configured; Admin UI proxy route will use host.docker.internal');
    }
    return serverIP || 'host.docker.internal';
  }

  /**
   * Add TLS certificate for a domain
   * Sends PEM content to SmartProxy 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.smartProxy.addCertificate(domain, certPem, keyPem);
  }

  /**
   * Remove TLS certificate for a domain
   */
  removeCertificate(domain: string): void {
    this.smartProxy.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.smartProxy.addCertificate(cert.domain, cert.fullchainPem, cert.keyPem);
        } else {
          logger.warn(`Skipping certificate for ${cert.domain}: missing PEM content`);
        }
      }

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

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