import * as plugins from '../plugins.js';
import { logger } from '../logger.js';
import { ProxyCertDoc } from '../db/index.js';
import { RemoteIngressManager, type IRemoteIngressFirewallConfig } from './classes.remoteingress-manager.js';
import { TunnelManager } from './classes.tunnel-manager.js';
import type { IDcRouterRouteConfig, IRemoteIngressHubSettings, TRemoteIngressHubSettingsUpdate } from '../../ts_interfaces/data/remoteingress.js';
import type { DcRouter } from '../classes.dcrouter.js';

/**
 * Generation-guarded lifecycle for the RemoteIngress tunnel hub: serialized
 * start/stop/restart of the Rust hub, edge mutations, route/firewall pushes,
 * and hub-settings updates that may require a SmartProxy restart.
 */
export class RemoteIngressHubLifecycle {
  private lifecycleChain: Promise<void> = Promise.resolve();
  private stopping = false;
  private generation = 0;

  constructor(private dcRouterRef: DcRouter) {}

  public async setup(): Promise<void> {
    const remoteIngressManager = this.dcRouterRef.remoteIngressManager;
    if (!remoteIngressManager) {
      return;
    }

    const hubSettings = remoteIngressManager.getHubSettings();
    if (!hubSettings.enabled) {
      logger.log('info', 'Remote Ingress hub is disabled in DB settings');
      return;
    }

    logger.log('info', 'Setting up Remote Ingress hub...');
    this.stopping = false;
    const generation = ++this.generation;

    const firewallConfig = await this.dcRouterRef.securityPolicyManager?.compileRemoteIngressFirewall();
    if (!this.isGenerationCurrent(generation, remoteIngressManager)) {
      return;
    }
    remoteIngressManager.setFirewallConfig(firewallConfig);

    // Pass current bootstrap routes so the manager can derive edge ports initially.
    // Once RouteConfigManager applies the full DB set, the onRoutesApplied callback
    // will push the complete merged routes here.
    remoteIngressManager.setRoutes(this.dcRouterRef.getRemoteIngressBootstrapRoutes() as any[]);

    // If ConfigManagers finished before us, re-apply routes
    // so the callback delivers the full DB set to our newly-created remoteIngressManager.
    if (this.dcRouterRef.routeConfigManager) {
      await this.dcRouterRef.routeConfigManager.applyRoutes();
    }
    if (!this.isGenerationCurrent(generation, remoteIngressManager)) {
      return;
    }

    await this.queueTask(async () => {
      await this.startTunnelHubLocked(generation);
    });
    if (!this.isGenerationCurrent(generation, remoteIngressManager)) {
      return;
    }

    const edgeCount = remoteIngressManager.getAllEdges().length;
    logger.log('info', `Remote Ingress hub started on port ${hubSettings.tunnelPort} with ${edgeCount} registered edge(s)`);
  }

  public async stop(): Promise<void> {
    this.stopping = true;
    this.generation++;
    await this.queueTask(async () => {
      const currentTunnelManager = this.dcRouterRef.tunnelManager;
      if (currentTunnelManager) {
        await currentTunnelManager.stop();
        if (this.dcRouterRef.tunnelManager === currentTunnelManager) {
          this.dcRouterRef.tunnelManager = undefined;
        }
      }
    });
  }

  public async mutateEdges<T>(
    mutation: (manager: RemoteIngressManager) => Promise<T>,
    syncAllowedEdges = true,
  ): Promise<T> {
    return await this.queueTask(async () => {
      if (this.stopping) {
        throw new Error('RemoteIngress is stopping');
      }
      const manager = this.dcRouterRef.remoteIngressManager;
      if (!manager) {
        throw new Error('RemoteIngress not configured');
      }
      const result = await mutation(manager);
      if (syncAllowedEdges && this.dcRouterRef.tunnelManager) {
        await this.dcRouterRef.tunnelManager.syncAllowedEdges();
      }
      return result;
    });
  }

  public async updateRoutes(routes: IDcRouterRouteConfig[]): Promise<void> {
    await this.queueTask(async () => {
      if (this.stopping) return;
      if (this.dcRouterRef.remoteIngressManager) {
        this.dcRouterRef.remoteIngressManager.setRoutes(routes);
      }
      if (this.dcRouterRef.tunnelManager) {
        await this.dcRouterRef.tunnelManager.syncAllowedEdges();
      }
    });
  }

  public async applyFirewallConfig(firewallConfig: IRemoteIngressFirewallConfig | undefined): Promise<void> {
    await this.queueTask(async () => {
      if (this.stopping) return;
      if (this.dcRouterRef.remoteIngressManager) {
        this.dcRouterRef.remoteIngressManager.setFirewallConfig(firewallConfig);
      }
      if (this.dcRouterRef.tunnelManager) {
        await this.dcRouterRef.tunnelManager.syncAllowedEdges();
      }
    });
  }

  public async updateHubSettings(
    updates: TRemoteIngressHubSettingsUpdate,
    updatedBy: string,
  ): Promise<IRemoteIngressHubSettings> {
    const manager = this.dcRouterRef.remoteIngressManager;
    if (!manager) {
      throw new Error('RemoteIngress is not configured');
    }

    const previousSettings = manager.getHubSettings();
    const settings = await manager.updateHubSettings(updates, updatedBy);
    const enabledChanged = previousSettings.enabled !== settings.enabled;

    if (!settings.enabled) {
      await this.queueTask(async () => {
        await this.stopTunnelHubLocked();
      });
    }

    if (enabledChanged) {
      await this.dcRouterRef.restartSmartProxyForRemoteIngressSettings();
    }

    if (settings.enabled) {
      await this.queueTask(async () => {
        await this.restartTunnelHubLocked();
      });
    }

    return settings;
  }

  private isGenerationCurrent(generation: number, manager: RemoteIngressManager): boolean {
    return !this.stopping
      && generation === this.generation
      && this.dcRouterRef.remoteIngressManager === manager;
  }

  private queueTask<T>(task: () => Promise<T>): Promise<T> {
    const run = this.lifecycleChain.then(task);
    this.lifecycleChain = run.then(() => undefined, () => undefined);
    return run;
  }

  private async stopTunnelHubLocked(): Promise<void> {
    this.generation++;
    const currentTunnelManager = this.dcRouterRef.tunnelManager;
    if (currentTunnelManager) {
      await currentTunnelManager.stop();
      if (this.dcRouterRef.tunnelManager === currentTunnelManager) {
        this.dcRouterRef.tunnelManager = undefined;
      }
    }
  }

  private async restartTunnelHubLocked(): Promise<void> {
    const generation = ++this.generation;
    const hubSettings = this.dcRouterRef.remoteIngressManager?.getHubSettings();
    if (!this.dcRouterRef.remoteIngressManager || !hubSettings?.enabled || this.stopping) {
      return;
    }

    const currentTunnelManager = this.dcRouterRef.tunnelManager;
    if (currentTunnelManager) {
      await currentTunnelManager.stop();
      if (this.dcRouterRef.tunnelManager === currentTunnelManager) {
        this.dcRouterRef.tunnelManager = undefined;
      }
    }

    if (this.stopping || generation !== this.generation) {
      return;
    }
    await this.startTunnelHubLocked(generation);
  }

  private async startTunnelHubLocked(generation: number): Promise<void> {
    const manager = this.dcRouterRef.remoteIngressManager;
    const hubSettings = manager?.getHubSettings();
    if (!manager || !hubSettings?.enabled || this.stopping || generation !== this.generation) {
      return;
    }

    const firewallConfig = await this.dcRouterRef.securityPolicyManager?.compileRemoteIngressFirewall();
    if (this.stopping || generation !== this.generation || this.dcRouterRef.remoteIngressManager !== manager) {
      return;
    }
    manager.setFirewallConfig(firewallConfig);

    const tlsConfig = await this.resolveTlsConfig(hubSettings.hubDomain);
    if (this.stopping || generation !== this.generation || this.dcRouterRef.remoteIngressManager !== manager) {
      return;
    }

    const tunnelManager = new TunnelManager(manager, {
      tunnelPort: hubSettings.tunnelPort,
      targetHost: '127.0.0.1',
      tls: tlsConfig,
      performance: manager.getHubPerformanceConfig(),
    });
    try {
      await tunnelManager.start();
    } catch (err) {
      await tunnelManager.stop().catch(() => {});
      throw err;
    }

    if (this.stopping || generation !== this.generation || this.dcRouterRef.remoteIngressManager !== manager) {
      await tunnelManager.stop().catch((err) => {
        logger.log('warn', `Failed to stop stale RemoteIngress tunnel hub: ${(err as Error).message}`);
      });
      return;
    }
    this.dcRouterRef.tunnelManager = tunnelManager;
  }

  private async resolveTlsConfig(
    hubDomain?: string,
  ): Promise<{ certPem: string; keyPem: string } | undefined> {
    // Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
    let tlsConfig: { certPem: string; keyPem: string } | undefined;

    // Priority 1: Explicit cert/key file paths
    const explicitTls = this.dcRouterRef.options.remoteIngressConfig?.tls;
    if (explicitTls?.certPath && explicitTls?.keyPath) {
      try {
        const certPem = plugins.fs.readFileSync(explicitTls.certPath, 'utf8');
        const keyPem = plugins.fs.readFileSync(explicitTls.keyPath, 'utf8');
        tlsConfig = { certPem, keyPem };
        logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
      } catch (err: unknown) {
        logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${(err as Error).message}`);
      }
    }

    // Priority 2: Existing cert from SmartProxy cert store for hubDomain
    if (!tlsConfig && hubDomain) {
      try {
        const stored = await ProxyCertDoc.findByDomain(hubDomain);
        if (stored?.publicKey && stored?.privateKey) {
          tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
          logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${hubDomain}`);
        }
      } catch { /* no stored cert, fall through */ }
    }

    if (!tlsConfig) {
      logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
    }

    return tlsConfig;
  }
}
