import "reflect-metadata";
import type { Request, Response, NextFunction } from "express";
import type { ServerResponse } from "node:http";
import { NestFactory } from "@nestjs/core";
import { Logger } from "@nestjs/common";
import {
  createProxyMiddleware,
  fixRequestBody,
  type Options,
} from "http-proxy-middleware";
import {
  GATEWAY_UNAVAILABLE,
  UNAUTHORIZED,
} from "@agent-client/shared";
import { AppModule } from "./app.module";
import { AnthropicProvider } from "./providers/anthropic.provider";

async function bootstrap() {
  const port = Number(process.env.PORT ?? 4290);
  const logger = new Logger("Gateway");
  const gatewayToken = process.env.GATEWAY_TOKEN;
  if (!gatewayToken) {
    throw new Error("GATEWAY_TOKEN is required");
  }

  // bodyParser:false keeps the request stream intact for the proxy.
  const app = await NestFactory.create(AppModule, { bodyParser: false });
  const provider = app.get(AnthropicProvider);

  const isHealth = (req: Request) => req.path === "/health";

  // Auth — Express-level, runs before the proxy.
  app.use((req: Request, res: Response, next: NextFunction) => {
    if (isHealth(req)) return next();
    const supplied =
      pickHeader(req, "x-gateway-token") ??
      pickHeader(req, "x-api-key") ??
      extractBearer(pickHeader(req, "authorization"));
    if (!supplied || supplied !== gatewayToken) {
      res.statusCode = 401;
      res.setHeader("content-type", "application/json");
      res.end(JSON.stringify({ error: UNAUTHORIZED }));
      return;
    }
    next();
  });

  const proxyOptions: Options = {
    target: provider.baseUrl,
    changeOrigin: true,
    ws: false,
    selfHandleResponse: false,
    on: {
      proxyReq: (proxyReq, req, _res) => {
        provider.applyToProxyReq(proxyReq);
        // No-op when bodyParser is off; harmless if a parser ever runs.
        fixRequestBody(proxyReq, req);
        logger.debug(`→ ${req.method} ${req.url}`);
      },
      proxyRes: (proxyRes, req, _res) => {
        logger.debug(`← ${req.method} ${req.url} ${proxyRes.statusCode}`);
      },
      error: (err, _req, res) => {
        logger.error(`proxy error: ${err.message}`);
        if (!("writeHead" in res) || typeof res.writeHead !== "function") return;
        const sres = res as ServerResponse;
        sres.writeHead(502, { "content-type": "application/json" });
        sres.end(JSON.stringify({ error: GATEWAY_UNAVAILABLE }));
      },
    },
  };

  const proxyMw = createProxyMiddleware(proxyOptions);
  app.use((req: Request, res: Response, next: NextFunction) => {
    if (isHealth(req)) return next();
    return proxyMw(req, res, next);
  });

  await app.listen(port);
  logger.log(`gateway up on :${port} → ${provider.baseUrl}`);
}

function pickHeader(req: Request, name: string): string | undefined {
  const v = req.headers[name];
  return Array.isArray(v) ? v[0] : v;
}

function extractBearer(authz: string | undefined): string | undefined {
  return authz?.match(/^Bearer\s+(.+)$/i)?.[1];
}

bootstrap().catch((err) => {
  console.error(err);
  process.exit(1);
});
