import fs from "fs";
import os from "os";
import path from "path";
import { afterEach, describe, expect, it } from "vitest";
import { discoverProject, discoverSuites } from "./discovery.mjs";

const cleanups = [];

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

describe("filesystem-discovery", () => {
  it("discovers colocated __testkit__ suites by service root", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "src/api/routes/__testkit__/auth/me.int.testkit.ts");
    writeFile(productDir, "src/api/routes/__testkit__/health/ready.int.testkit.ts");
    writeFile(productDir, "src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts");
    writeFile(productDir, "frontend/app/__testkit__/homepage/homepage.pw.testkit.ts");

    const project = discoverProject(productDir, {
      api: {
        local: {
          cwd: ".",
        },
      },
      frontend: {
        local: {
          cwd: "frontend",
        },
      },
    });

    const suites = project.suitesByService;
    expect(suites.api.integration).toEqual([
      {
        name: "auth",
        files: ["src/api/routes/__testkit__/auth/me.int.testkit.ts"],
        framework: "k6",
      },
      {
        name: "health",
        files: ["src/api/routes/__testkit__/health/ready.int.testkit.ts"],
        framework: "k6",
      },
    ]);
    expect(suites.api.scenario).toEqual([
      {
        name: "journeys",
        files: ["src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts"],
        framework: "k6",
      },
    ]);
    expect(suites.frontend.e2e).toEqual([
      {
        name: "homepage",
        files: ["frontend/app/__testkit__/homepage/homepage.pw.testkit.ts"],
        framework: "playwright",
      },
    ]);
    expect(project.files).toEqual([
      {
        serviceName: "api",
        type: "integration",
        framework: "k6",
        suiteName: "auth",
        suitePath: ["src", "api", "routes", "auth"],
        filePath: "src/api/routes/__testkit__/auth/me.int.testkit.ts",
      },
      {
        serviceName: "api",
        type: "integration",
        framework: "k6",
        suiteName: "health",
        suitePath: ["src", "api", "routes", "health"],
        filePath: "src/api/routes/__testkit__/health/ready.int.testkit.ts",
      },
      {
        serviceName: "api",
        type: "scenario",
        framework: "k6",
        suiteName: "journeys",
        suitePath: ["src", "api", "routes", "journeys"],
        filePath: "src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts",
      },
      {
        serviceName: "frontend",
        type: "e2e",
        framework: "playwright",
        suiteName: "homepage",
        suitePath: ["app", "homepage"],
        filePath: "frontend/app/__testkit__/homepage/homepage.pw.testkit.ts",
      },
    ]);
    expect(project.diagnostics).toEqual([]);
  });

  it("infers the suite from the directory that owns __testkit__", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "src/services/search/__testkit__/query/validation.int.testkit.ts");

    const project = discoverProject(productDir, {
      api: {
        local: {
          cwd: ".",
        },
      },
    });
    expect(project.services.api).toMatchObject({
      name: "api",
      inferredLocalCwd: ".",
    });
    expect(project.suitesByService.api.integration[0]).toMatchObject({
      name: "query",
      files: ["src/services/search/__testkit__/query/validation.int.testkit.ts"],
      framework: "k6",
    });
  });

  it("prefers the deepest matching service root", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "frontend/app/__testkit__/billing/lifecycle.pw.testkit.ts");

    const project = discoverProject(productDir, {
      app: {
        local: {
          cwd: ".",
        },
      },
      frontend: {
        local: {
          cwd: "frontend",
        },
      },
    });

    expect(project.suitesByService.app).toBeUndefined();
    expect(project.suitesByService.frontend.e2e[0]).toMatchObject({
      name: "billing",
      files: ["frontend/app/__testkit__/billing/lifecycle.pw.testkit.ts"],
      framework: "playwright",
    });
  });

  it("fails on legacy files outside __testkit__", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "tests/api/integration/health.int.testkit.ts");

    expect(() =>
      discoverSuites(productDir, {
        api: {
          local: {
            cwd: ".",
          },
        },
      })
    ).toThrow("Legacy test files outside __testkit__");
  });

  it("reports legacy files in non-strict mode", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "src/api/routes/__testkit__/health/ready.int.testkit.ts");
    writeFile(productDir, "tests/api/integration/health.int.testkit.ts");

    const project = discoverProject(
      productDir,
      {
        api: {
          local: {
            cwd: ".",
          },
        },
      },
      {
        strict: false,
      }
    );

    expect(project.suitesByService.api.integration).toEqual([
      {
        name: "health",
        files: ["src/api/routes/__testkit__/health/ready.int.testkit.ts"],
        framework: "k6",
      },
    ]);
    expect(project.diagnostics).toEqual([
      {
        code: "legacy_path",
        severity: "error",
        message: "Legacy test file outside __testkit__: tests/api/integration/health.int.testkit.ts",
        path: "tests/api/integration/health.int.testkit.ts",
      },
    ]);
  });

  it("does not treat nested coverage source directories as generated output", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "app/coverage/__testkit__/coverage-map.pw.testkit.ts");

    const project = discoverProject(productDir, {
      web: {
        local: {
          cwd: ".",
        },
        discovery: {
          roots: ["app"],
        },
      },
    });

    expect(project.files).toEqual([
      {
        serviceName: "web",
        type: "e2e",
        framework: "playwright",
        suiteName: "coverage",
        suitePath: ["coverage"],
        filePath: "app/coverage/__testkit__/coverage-map.pw.testkit.ts",
      },
    ]);
  });

  it("supports repo-level and service-level discovery excludes", () => {
    const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
    cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));

    writeFile(productDir, "coverage/__testkit__/noise/noise.pw.testkit.ts");
    writeFile(productDir, "app/generated/__testkit__/noise/noise.pw.testkit.ts");
    writeFile(productDir, "app/coverage/__testkit__/coverage-map.pw.testkit.ts");

    const project = discoverProject(
      productDir,
      {
        web: {
          local: {
            cwd: ".",
          },
          discovery: {
            roots: ["app"],
            exclude: ["app/generated"],
          },
        },
      },
      {
        discovery: {
          exclude: ["coverage"],
        },
      }
    );

    expect(project.files.map((entry) => entry.filePath)).toEqual([
      "app/coverage/__testkit__/coverage-map.pw.testkit.ts",
    ]);
  });

});

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