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

let tempDir: string | null = null;

function createTestApp() {
  tempDir = mkdtempSync(join(tmpdir(), "mock-task-api-http-"));
  const db = reseedDatabase(join(tempDir, "test.sqlite"));
  return createApp(db, { parseJson: false });
}

async function sendRequest(
  app: ReturnType<typeof createApp>,
  options: {
    method: string;
    url: string;
    query?: Record<string, string>;
    body?: unknown;
    headers?: Record<string, string>;
  },
) {
  const request = httpMocks.createRequest({
    method: options.method,
    url: options.url,
    query: options.query,
    body: options.body,
    headers: options.headers,
  });
  const response = httpMocks.createResponse({ eventEmitter: EventEmitter });

  await new Promise<void>((resolve) => {
    response.on("end", () => resolve());
    app.handle(request, response);
  });

  const contentType = String(response.getHeader("content-type") ?? "");
  return {
    status: response.statusCode,
    headers: response._getHeaders(),
    body: contentType.includes("application/json")
      ? response._getJSONData()
      : response._getData(),
  };
}

afterEach(() => {
  if (tempDir) {
    rmSync(tempDir, { recursive: true, force: true });
    tempDir = null;
  }
});

describe("mock task API", () => {
  it("serves a health endpoint", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/health",
    });

    expect(response.status).toBe(200);
    expect(response.body.status).toBe("ok");
  });

  it("lists organizations", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/orgs",
    });

    expect(response.status).toBe(200);
    expect(Array.isArray(response.body.organizations)).toBe(true);
    expect(response.body.organizations).toHaveLength(3);
  });

  it("filters tasks by org and status", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/tasks",
      query: {
        orgId: "org_acme",
        status: "blocked",
      },
    });

    expect(response.status).toBe(200);
    expect(response.body.tasks).toHaveLength(1);
    expect(response.body.tasks[0]?.id).toBe("task_1003");
  });

  it("creates a task through the HTTP contract", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "POST",
      url: "/tasks",
      body: {
        orgId: "org_acme",
        projectId: "proj_acme_ops",
        title: "Run API smoke test",
        priority: "low",
      },
      headers: {
        "content-type": "application/json",
      },
    });

    expect(response.status).toBe(201);
    expect(response.body.task.title).toBe("Run API smoke test");
  });

  it("updates a task through the HTTP contract", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "PATCH",
      url: "/tasks/task_1001",
      body: {
        status: "done",
        assigneeUserId: null,
      },
      headers: {
        "content-type": "application/json",
      },
    });

    expect(response.status).toBe(200);
    expect(response.body.task.status).toBe("done");
    expect(response.body.task.assigneeUserId).toBeNull();
  });

  it("returns 400 for invalid create payloads", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "POST",
      url: "/tasks",
      body: {
        orgId: "org_acme",
        projectId: "proj_acme_ops",
      },
      headers: {
        "content-type": "application/json",
      },
    });

    expect(response.status).toBe(400);
    expect(response.body.error).toBe("Validation failed.");
    expect(response.body.details).toBeDefined();
  });

  it("returns 404 when updating a missing task", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "PATCH",
      url: "/tasks/task_missing",
      body: {
        status: "done",
      },
      headers: {
        "content-type": "application/json",
      },
    });

    expect(response.status).toBe(404);
    expect(response.body.error).toBe("Task not found.");
  });

  it("returns 404 for unknown routes", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/unknown",
    });

    expect(response.status).toBe(404);
    expect(response.body.error).toBe("Route not found.");
  });

  it("returns a valid openapi document at the import path", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/openapi.json",
    });
    const paths = response.body.paths as Record<string, unknown>;

    expect(response.status).toBe(200);
    expect(response.body.openapi).toBe("3.0.0");
    expect(paths["/tasks"]).toBeDefined();
    expect(paths["/orgs/{orgId}/tasks"]).toBeDefined();
  });

  it("serves the same generated document on all json spec aliases", async () => {
    const app = createTestApp();

    const openApiResponse = await sendRequest(app, {
      method: "GET",
      url: "/openapi.json",
    });
    const swaggerResponse = await sendRequest(app, {
      method: "GET",
      url: "/swagger.json",
    });

    expect(openApiResponse.body).toEqual(swaggerResponse.body);
    expect(openApiResponse.body).toEqual(openApiDocument);
  });

  it("serves swagger ui at /api-docs", async () => {
    const app = createTestApp();

    const response = await sendRequest(app, {
      method: "GET",
      url: "/api-docs",
    });

    expect(response.status).toBe(301);
  });
});
