import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import express, { Router } from "express";
import type { Server } from "node:http";

import { generateAgentOpenApi } from "../../src/contracts/openapi.js";
import { buildChatRouter } from "../../src/routes/chat.js";
import { buildAgentPublicRouter } from "../../src/routes/publicApi.js";

describe("Agent public REST router", () => {
  let baseUrl = "";
  let server: Server;

  beforeAll(async () => {
    const chatRouter = Router();
    chatRouter.use(express.json());
    chatRouter.post("/sessions/:session_id/runs", (req, res) => {
      res.json({ method: req.method, path: req.path, body: req.body });
    });
    chatRouter.delete("/sessions/:session_id/runs/current", (req, res) => {
      res.json({ method: req.method, path: req.path, body: req.body });
    });
    chatRouter.get("/auth-failure", (_req, res) => {
      res.status(503).json({ message: "authentication service unavailable" });
    });

    const app = express();
    app.use(express.json());
    app.use("/api/v1/agent", buildAgentPublicRouter(chatRouter));
    server = app.listen(0);
    await new Promise<void>((resolve) => server.once("listening", resolve));
    const address = server.address();
    if (!address || typeof address === "string") {
      throw new Error("test server did not expose a TCP port");
    }
    baseUrl = `http://127.0.0.1:${address.port}`;
  });

  afterAll(() => {
    server.close();
  });

  test("matches the published OpenAPI method and path set", () => {
    type RouterLayer = {
      route?: {
        methods: Record<string, boolean>;
        path: string;
      };
    };
    const router = buildChatRouter(
      undefined as never,
      undefined as never,
      undefined as never,
      undefined as never,
      undefined as never,
      undefined as never,
      undefined as never,
      undefined as never,
    );
    const layers = (router as unknown as { stack: RouterLayer[] }).stack;
    const runtimeOperations = layers
      .flatMap((layer) => {
        if (!layer.route) {
          return [];
        }
        const path = `/api/v1/agent${layer.route.path.replace(
          /:([^/]+)/g,
          "{$1}",
        )}`;
        return Object.entries(layer.route.methods)
          .filter(([, enabled]) => enabled)
          .map(([method]) => `${method.toUpperCase()} ${path}`);
      })
      .sort();
    const document = generateAgentOpenApi();
    const contractOperations = Object.entries(document.paths)
      .flatMap(([path, pathItem]) =>
        Object.entries(pathItem ?? {})
          .filter(([, operation]) => {
            return (
              operation &&
              typeof operation === "object" &&
              "responses" in operation
            );
          })
          .map(([method]) => `${method.toUpperCase()} ${path}`),
      )
      .sort();

    expect(runtimeOperations).toEqual(contractOperations);
  });

  test("serves session runs without rewriting the REST request", async () => {
    const response = await fetch(
      `${baseUrl}/api/v1/agent/sessions/session-1/runs`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message: "hello" }),
      },
    );

    expect(await response.json()).toEqual({
      method: "POST",
      path: "/sessions/session-1/runs",
      body: { message: "hello" },
    });
  });

  test("serves DELETE current run without rewriting the HTTP method", async () => {
    const response = await fetch(
      `${baseUrl}/api/v1/agent/sessions/session-2/runs/current`,
      { method: "DELETE" },
    );

    expect(await response.json()).toEqual({
      method: "DELETE",
      path: "/sessions/session-2/runs/current",
      body: {},
    });
  });

  test("wraps authentication middleware failures as Problem Details", async () => {
    const response = await fetch(`${baseUrl}/api/v1/agent/auth-failure`);
    const body = await response.json();

    expect(response.status).toBe(503);
    expect(response.headers.get("content-type")).toContain(
      "application/problem+json",
    );
    expect(body).toMatchObject({
      status: 503,
      code: "dependency_unavailable",
      detail: "authentication service unavailable",
    });
  });
});
