import express, { Request, Response } from 'express';
import { Repository } from '../db/repository.js';
import { logger } from '../logger.js';
import { loadConfig } from '../config.js';
import { ConfigManager } from '../config-manager.js';
import { requireAdmin } from './auth.js';
import { registerShutdownHook } from './shutdown.js';
import { mountGateway, type GatewayMountHandle } from './gateway-mount.js';
import { readGatewayConfig } from '../gateway/config.js';
import { createAdminGatewayStatusRouter } from './admin-gateway-status-api.js';
import { resolveListenPort } from '../server/config.js';

/**
 * Phase 3c — same-process AAO Gateway mount + the bridge `/health` fallback +
 * the admin gateway status endpoint, extracted verbatim from createCoreServer
 * (bridge/server.ts). The registration order of the three blocks (gateway
 * gate BEFORE `/health` BEFORE status) is behavior-relevant and preserved.
 */
export function mountBridgeGateway(
  app: express.Express,
  deps: {
    repo: Repository;
    configManager?: ConfigManager;
    /** Shared prom-client registry from setupMetricsSubsystem (null when metrics are disabled). */
    sharedPromRegistry: import('prom-client').Registry | null;
    authActive: boolean;
    listenPort?: number;
  },
): GatewayMountHandle | null {
  const { repo, configManager, sharedPromRegistry, authActive, listenPort } = deps;

  // Phase 3c — same-process AAO Gateway mount. Always installs the
  // dynamic 404 gate; the gateway sub-app only comes alive when
  // `gateway.enabled: true` in config.yaml (or after an admin flips it
  // ON via Settings → Gateway Server). Pure no-op when no ConfigManager
  // is available (tests / scripts run without one).
  let gatewayMount: GatewayMountHandle | null = null;
  if (configManager) {
    gatewayMount = mountGateway({
      app,
      configManager,
      repo,
      // Per CRITICAL-2: the gateway now owns its own BackendStatusRegistry
      // over gateway.backends[] rather than reusing the worker registry
      // (which probes provider.workers[] — different IDs). Same-host
      // double-probe is intentional and cheap.
      // Reuse the worker's prom-client registry so gateway counters
      // appear on the same /metrics endpoint (no port collision).
      promRegistry: sharedPromRegistry,
      metricsPrefix: 'aao_gateway',
    });
    // Apply the boot-time config so a server starting with
    // `gateway.enabled: true` brings the gateway up immediately —
    // no need to re-save config from the UI.
    const bootGateway = readGatewayConfig(configManager.getConfig());
    gatewayMount.applyConfig(bootGateway).catch((e) => {
      logger.warn(`[bridge-gateway] initial applyConfig threw: ${e instanceof Error ? e.message : String(e)}`);
    });
    // Drain in-flight gateway streams on process shutdown so SIGTERM
    // doesn't strand SSE clients with a half-written response.
    registerShutdownHook('bridge-gateway', async () => {
      try { await gatewayMount!.stop(); } catch { /* noop */ }
    });
  } else {
    logger.info('[bridge-gateway] not mounted (no ConfigManager — hot reload unavailable)');
  }

  // CRITICAL-3 fix: bridge `/health` fallback. Registered AFTER the
  // gateway gate so when gateway.enabled=true the gate dispatches
  // `/health` into the gateway sub-app (LiteLLM-compat
  // `healthy_endpoints` / `unhealthy_endpoints` JSON shape).  When the
  // gateway is off, the gate falls through to here and the bridge
  // answers with the legacy `{status:'ok'}` shape ops scripts rely on.
  app.get('/health', (_req: Request, res: Response) => {
    res.json({ status: 'ok' });
  });

  // Admin status endpoint for the Gateway Server UI. Read-only — the
  // form drives state changes through PUT /api/config (the
  // config-changed listener inside mountGateway picks them up).
  // Requires admin when auth is active; in auth-off dev mode the
  // endpoint is open like the rest of /api/admin/* health/status reads.
  {
    // Prefer the explicit listenPort threaded through CoreServerOptions
    // (startCoreServer always sets it) over the PORT env-var guess so
    // the status endpoint matches what the bridge actually bound.
    // env-var fallback is kept for callers that bypass startCoreServer.
    const actualPort = listenPort ?? resolveListenPort(process.env['PORT'], loadConfig().server?.port);
    const statusRouter = createAdminGatewayStatusRouter({
      mount: gatewayMount,
      configManager: configManager ?? null,
      workerPort: actualPort,
    });
    if (authActive) {
      app.use('/api/admin/gateway/status', requireAdmin, statusRouter);
    } else {
      app.use('/api/admin/gateway/status', statusRouter);
    }
  }

  return gatewayMount;
}
