import fs from "fs";
import os from "os";
import path from "path";
import { afterEach, describe, expect, it } from "vitest";
import { buildCoverageGraph, buildServiceCoverageContext } from "./index.mjs";

const cleanups = [];

afterEach(() => {
  while (cleanups.length > 0) {
    cleanups.pop()();
  }
});

describe("coverage graph builder", () => {
  it("builds a Next page -> request -> API route -> server capability graph", () => {
    const productDir = createProduct();
    writeFile(
      productDir,
      "testkit.setup.ts",
      `
        import { defineTestkitSetup, nextService } from "@elench/testkit/setup";

        export default defineTestkitSetup({
          services: {
            web: nextService({
              cwd: ".",
              start: "node server.js",
              baseUrl: "http://127.0.0.1:3000",
              readyUrl: "http://127.0.0.1:3000"
            })
          }
        });
      `
    );
    writeFile(
      productDir,
      "src/app/campaigns/page.tsx",
      `
        "use client";
        import { getJson } from "@/client/http/http";

        export default async function CampaignsPage() {
          await getJson("/api/campaigns");
          return null;
        }
      `
    );
    writeFile(productDir, "src/client/http/http.ts", `export function getJson() { return null; }`);
    writeFile(
      productDir,
      "src/app/api/campaigns/route.ts",
      `
        import { listCampaigns, createCampaign } from "@/backend/server/campaigns";

        export async function GET() {
          return listCampaigns();
        }

        export async function POST() {
          return createCampaign();
        }
      `
    );
    writeFile(productDir, "src/backend/server/campaigns/index.ts", `export async function listCampaigns() {}\nexport async function createCampaign() {}`);

    const context = buildServiceCoverageContext(productDir, "web", {
      local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
    });

    expect(context.pageByRoute.get("/campaigns").node).toMatchObject({
      kind: "page_view",
      route: "/campaigns",
      filePath: "src/app/campaigns/page.tsx",
    });
    expect(context.apiRouteByKey.get("GET:/api/campaigns").node).toMatchObject({
      kind: "api_route",
      route: "/campaigns",
      method: "GET",
      path: "/api/campaigns",
      filePath: "src/app/api/campaigns/route.ts",
    });
    expect(context.graph.edges).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          kind: "requests",
          from: "page_view:web:/campaigns",
        }),
        expect.objectContaining({
          kind: "handles",
          to: "api_route:web:GET:/api/campaigns",
        }),
        expect.objectContaining({
          kind: "delegates_to",
          from: "api_route:web:GET:/api/campaigns",
        }),
      ])
    );
  });

  it("discovers server action links from pages", () => {
    const productDir = createProduct();
    writeFile(
      productDir,
      "src/app/settings/page.tsx",
      `
        import { saveSettings } from "./actions";

        export default function SettingsPage() {
          void saveSettings;
          return null;
        }
      `
    );
    writeFile(
      productDir,
      "src/app/settings/actions.ts",
      `
        "use server";
        import { updateSettings } from "@/backend/server/settings";

        export async function saveSettings() {
          return updateSettings();
        }
      `
    );
    writeFile(productDir, "src/backend/server/settings.ts", `export async function updateSettings() {}`);

    const context = buildServiceCoverageContext(productDir, "web", {
      local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
    });

    expect(context.serverActionByExportKey.get("src/app/settings/actions.ts#saveSettings").node).toMatchObject({
      kind: "server_action",
      label: "saveSettings",
    });
    expect(context.graph.edges).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          kind: "triggers",
          from: "page_view:web:/settings",
          to: "server_action:web:src/app/settings/actions.ts#saveSettings",
        }),
        expect.objectContaining({
          kind: "delegates_to",
          from: "server_action:web:src/app/settings/actions.ts#saveSettings",
        }),
      ])
    );
  });

  it("attaches convention-based test evidence to page and route nodes", () => {
    const productDir = createProduct();
    writeFile(productDir, "src/app/campaigns/page.tsx", `export default function CampaignsPage() { return null; }`);
    writeFile(productDir, "src/app/api/campaigns/route.ts", `export async function GET() { return Response.json({ ok: true }); }`);

    const graph = buildCoverageGraph({
      productDir,
      services: {
        web: {
          local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
        },
      },
      discoveryFiles: [
        {
          serviceName: "web",
          type: "e2e",
          framework: "playwright",
          suiteName: "campaigns",
          filePath: "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
        },
        {
          serviceName: "web",
          type: "integration",
          framework: "k6",
          suiteName: "campaigns",
          filePath: "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
        },
      ],
    });

    expect(graph.evidence).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          testFilePath: "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
          coveredNodeIds: ["page_view:web:/campaigns"],
        }),
        expect.objectContaining({
          testFilePath: "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
          coveredNodeIds: ["api_route:web:GET:/api/campaigns"],
        }),
      ])
    );
  });

  it("normalizes dynamic routes", () => {
    const productDir = createProduct();
    writeFile(productDir, "src/app/projects/[projectId]/page.tsx", `export default function ProjectPage() { return null; }`);
    writeFile(productDir, "src/app/api/projects/[projectId]/route.ts", `export async function GET() { return Response.json({ ok: true }); }`);

    const context = buildServiceCoverageContext(productDir, "web", {
      local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
    });

    expect(context.pageByRoute.get("/projects/[projectId]").node).toBeTruthy();
    expect(context.apiRouteByKey.get("GET:/api/projects/[projectId]").node).toBeTruthy();
  });

  it("does not exclude app/coverage source routes while still allowing top-level coverage output to be ignored", () => {
    const productDir = createProduct();
    writeFile(productDir, "app/coverage/page.tsx", `export default function CoveragePage() { return null; }`);
    writeFile(productDir, "coverage/page.tsx", `export default function OutputPage() { return null; }`);

    const context = buildServiceCoverageContext(
      productDir,
      "web",
      {
        local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
        discovery: { roots: ["app"] },
      },
      { exclude: ["coverage"] }
    );

    expect(context.pageByRoute.get("/coverage").node).toMatchObject({
      filePath: "app/coverage/page.tsx",
    });
    expect(context.graph.nodes.some((node) => node.filePath === "coverage/page.tsx")).toBe(false);
  });
});

function createProduct() {
  const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-coverage-"));
  cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
  return productDir;
}

function writeFile(productDir, relativePath, content = "export {};\n") {
  const absolutePath = path.join(productDir, relativePath);
  fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
  fs.writeFileSync(absolutePath, content);
}
