import { describe, expect, it } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createApp } from "../src/app";
import { reseedDatabase } from "../src/database";
import { openApiDocument } from "../src/openapi";

const swaggerAliasPaths = new Set(["/openapi.json", "/swagger.json"]);

function normalizeOpenApiPath(path: string) {
  return path.replace(/:([^/]+)/g, "{$1}");
}

function getDocumentedOperations() {
  return Object.entries(openApiDocument.paths).flatMap(([path, pathItem]) =>
    Object.keys(pathItem as Record<string, unknown>).map(
      (method) => `${method.toUpperCase()} ${normalizeOpenApiPath(path)}`,
    ),
  );
}

function getRuntimeOperations() {
  const tempDir = mkdtempSync(join(tmpdir(), "mock-task-api-routes-"));

  try {
    const db = reseedDatabase(join(tempDir, "test.sqlite"));
    const app = createApp(db, { parseJson: false });
    const stack = (app as { router: { stack: Array<{ route?: unknown }> } }).router.stack;

    return stack.flatMap((layer) => {
      if (!layer.route) {
        return [];
      }

      const route = layer.route as { path: string; methods: Record<string, boolean> };
      if (swaggerAliasPaths.has(route.path)) {
        return [];
      }

      return Object.entries(route.methods)
        .filter(([, enabled]) => enabled)
        .map(([method]) => `${method.toUpperCase()} ${normalizeOpenApiPath(route.path)}`);
    });
  } finally {
    rmSync(tempDir, { recursive: true, force: true });
  }
}

describe("tsoa swagger document", () => {
  it("matches all runtime business routes registered in express", () => {
    const documentedOperations = getDocumentedOperations();
    const runtimeOperations = getRuntimeOperations();

    expect(runtimeOperations.sort()).toEqual(documentedOperations.sort());
  });

  it("documents the expected business endpoints generated from controllers", () => {
    const operations = getDocumentedOperations();

    expect(operations.sort()).toEqual([
      "GET /health",
      "GET /orgs",
      "GET /orgs/{orgId}",
      "GET /orgs/{orgId}/projects",
      "GET /orgs/{orgId}/tasks",
      "GET /users",
      "GET /projects",
      "GET /tasks",
      "POST /tasks",
      "GET /tasks/{taskId}",
      "PATCH /tasks/{taskId}",
    ].sort());
  });

  it("does not include swagger hosting aliases as business operations", () => {
    expect(openApiDocument.paths["/openapi.json"]).toBeUndefined();
    expect(openApiDocument.paths["/swagger.json"]).toBeUndefined();
    expect(openApiDocument.paths["/api-docs"]).toBeUndefined();
  });

  it("uses tsoa-generated schemas for the task mutation payloads", () => {
    const schemas = openApiDocument.components.schemas as Record<string, Record<string, unknown>>;

    expect(schemas.CreateTaskRequest).toBeDefined();
    expect(schemas.UpdateTaskRequest).toBeDefined();
  });
});
