import { describe, expect, test } from "bun:test";
import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";

import {
  createRouteRegistrar,
  generateAgentOpenApi,
} from "../../src/contracts/openapi.js";

const KEBAB_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

describe("Agent REST OpenAPI", () => {
  test("rejects duplicate method and path registrations", () => {
    const register = createRouteRegistrar(new OpenAPIRegistry());
    const config = {
      summary: "Test operation",
      responses: { 200: { description: "Successful response" } },
    };

    register("/api/v1/agent/test", "get", config);

    expect(() => register("/api/v1/agent/test", "get", config)).toThrow(
      "Duplicate Agent OpenAPI operation: GET /api/v1/agent/test",
    );
  });

  test("publishes only unique kebab-case public operations", () => {
    const document = generateAgentOpenApi();
    const operationIds = new Set<string>();
    let operationCount = 0;

    for (const [path, pathItem] of Object.entries(document.paths)) {
      expect(path.endsWith("/")).toBe(false);
      for (const segment of path.split("/").filter(Boolean)) {
        if (!segment.startsWith("{")) {
          expect(segment).toMatch(KEBAB_SEGMENT);
        }
      }
      for (const operation of Object.values(pathItem ?? {})) {
        if (!operation || typeof operation !== "object" || !("responses" in operation)) {
          continue;
        }
        operationCount += 1;
        expect(operation.operationId).toBeTruthy();
        expect(operationIds.has(operation.operationId!)).toBe(false);
        operationIds.add(operation.operationId!);
        expect(operation.security).toEqual([{ bearerAuth: [] }]);
      }
    }

    expect(operationCount).toBe(13);
  });

  test("models runs as session subresources", () => {
    const document = generateAgentOpenApi();
    expect(document.paths["/api/v1/agent/sessions/{session_id}/runs"]?.post).toBeTruthy();
    expect(
      document.paths[
        "/api/v1/agent/sessions/{session_id}/runs/current/events"
      ]?.get,
    ).toBeTruthy();
    expect(document.paths["/api/v1/agent/chat/stream"]).toBeUndefined();
  });

  test("matches the public session runtime response shapes", () => {
    const document = generateAgentOpenApi();
    const schemas = document.components?.schemas ?? {};
    const createResponses =
      document.paths["/api/v1/agent/sessions"]?.post?.responses ?? {};
    const patchOperation =
      document.paths["/api/v1/agent/sessions/{session_id}"]?.patch;
    const patchRequestBody = patchOperation?.requestBody;
    const forkResponses =
      document.paths["/api/v1/agent/sessions/{session_id}/forks"]?.post
        ?.responses ?? {};

    expect(Object.keys(createResponses)).toContain("200");
    expect(Object.keys(createResponses)).toContain("201");
    expect(schemas.AgentSessionSummary).toMatchObject({
      required: expect.arrayContaining(["id", "created_at", "updated_at", "status"]),
    });
    expect(schemas.AgentSessionSummary).not.toMatchObject({
      required: expect.arrayContaining(["session_id"]),
    });
    expect(
      patchRequestBody && !("$ref" in patchRequestBody)
        ? patchRequestBody.content["application/json"]?.schema
        : undefined,
    ).toMatchObject({
      properties: {
        is_title_manually_edited: { type: "boolean" },
      },
    });
    expect(patchOperation?.responses["200"]).toMatchObject({
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/AgentSessionUpdate" },
        },
      },
    });
    expect(forkResponses["200"]).toMatchObject({
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/AgentSessionFork" },
        },
      },
    });
    expect(forkResponses["201"]).toBeUndefined();
  });

  test("documents runtime query parameters and error statuses", () => {
    const document = generateAgentOpenApi();
    const renderOperation =
      document.paths["/api/v1/agent/render-references/{render_ref}"]?.get;

    expect(renderOperation?.parameters).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          in: "query",
          name: "session_id",
          required: false,
        }),
      ]),
    );
    expect(Object.keys(renderOperation?.responses ?? {})).toEqual(
      expect.arrayContaining(["400", "500", "502"]),
    );
  });
});
