import { describe, expect, it } from "vitest";
import {
  applyShard,
  buildGraphDirName,
  buildRuntimeGraphs,
  buildTaskQueue,
  claimNextBatch,
  collectSuites,
  resolveRuntimeConfigs,
} from "./planning.mjs";

function makeConfig(name, extras = {}) {
  return {
    name,
    suites: extras.suites || {},
    testkit: {
      dependsOn: extras.dependsOn || [],
    },
    ...extras,
  };
}

describe("runner-planning", () => {
  it("orders runtime configs by dependency and detects cycles", () => {
    const api = makeConfig("api");
    const frontend = makeConfig("frontend", { dependsOn: ["api"] });
    const configMap = new Map([
      ["api", api],
      ["frontend", frontend],
    ]);

    expect(resolveRuntimeConfigs(frontend, configMap).map((config) => config.name)).toEqual([
      "api",
      "frontend",
    ]);

    api.testkit.dependsOn = ["frontend"];
    expect(() => resolveRuntimeConfigs(frontend, configMap)).toThrow("Dependency cycle");
  });

  it("collects suites with user-facing type selection", () => {
    const config = makeConfig("api", {
      suites: {
        integration: [
          { name: "health", files: ["tests/api/integration/health.int.testkit.ts"] },
        ],
        e2e: [
          {
            name: "auth",
            framework: "playwright",
            files: [
              "tests/frontend/e2e/auth.pw.testkit.ts",
              "tests/frontend/e2e/signup.pw.testkit.ts",
            ],
          },
        ],
      },
    });

    expect(collectSuites(config, ["int"], [], [])[0]).toMatchObject({
      name: "health",
      type: "integration",
      displayType: "int",
      framework: "k6",
      weight: 1,
    });

    expect(collectSuites(config, ["pw"], [], [])[0]).toMatchObject({
      name: "auth",
      displayType: "pw",
      framework: "playwright",
      weight: 2,
    });

    expect(
      collectSuites(
        config,
        ["all"],
        [],
        ["tests/frontend/e2e/signup.pw.testkit.ts"]
      )[0]
    ).toMatchObject({
      name: "auth",
      files: ["tests/frontend/e2e/signup.pw.testkit.ts"],
    });
  });

  it("keeps skipped files visible while removing them from runnable work", () => {
    const config = makeConfig("api", {
      suites: {
        integration: [
          {
            name: "billing",
            files: [
              "__testkit__/billing/a.int.testkit.ts",
              "__testkit__/billing/b.int.testkit.ts",
            ],
          },
        ],
      },
      testkit: {
        dependsOn: [],
        skip: {
          fileReasonByPath: new Map([
            ["__testkit__/billing/a.int.testkit.ts", "Billing is stubbed"],
          ]),
          suites: [],
        },
      },
    });

    expect(collectSuites(config, ["int"], [], [])).toEqual([
      expect.objectContaining({
        name: "billing",
        files: ["__testkit__/billing/b.int.testkit.ts"],
        skippedFiles: [
          {
            path: "__testkit__/billing/a.int.testkit.ts",
            reason: "Billing is stubbed",
          },
        ],
        totalFileCount: 2,
      }),
    ]);

    expect(collectSuites(config, ["int"], [], [], { ignoreSkipRules: true })).toEqual([
      expect.objectContaining({
        name: "billing",
        files: [
          "__testkit__/billing/a.int.testkit.ts",
          "__testkit__/billing/b.int.testkit.ts",
        ],
        skippedFiles: [],
        totalFileCount: 2,
      }),
    ]);
  });

  it("applies shards, builds graphs, queues tasks, and claims batches", () => {
    const api = makeConfig("api");
    const frontend = makeConfig("frontend");
    const plans = [
      {
        config: api,
        skipped: false,
        runtimeConfigs: [api],
        runtimeNames: ["api"],
        runtimeKey: "api",
        suites: [
          {
            name: "health",
            type: "integration",
            framework: "k6",
            files: ["a.js", "b.js"],
            orderIndex: 0,
            weight: 2,
            maxFileConcurrency: 2,
          },
        ],
      },
      {
        config: frontend,
        skipped: false,
        runtimeConfigs: [api, frontend],
        runtimeNames: ["api", "frontend"],
        runtimeKey: "api|frontend",
        suites: [
          {
            name: "auth",
            type: "e2e",
            framework: "playwright",
            files: ["auth.spec.js", "signup.spec.js"],
            orderIndex: 0,
            weight: 2,
            maxFileConcurrency: 1,
          },
        ],
      },
    ];

    expect(applyShard(["a", "b", "c", "d"], { index: 2, total: 2 })).toEqual(["b", "d"]);

    const graphs = buildRuntimeGraphs(plans);
    expect(graphs).toHaveLength(1);
    expect(plans[0].assignedGraphKey).toBe("api|frontend");
    expect(buildGraphDirName(["api", "frontend"])).toBe("api__frontend");

    const queue = buildTaskQueue(plans, graphs, { files: {} });
    expect(queue).toHaveLength(4);

    const firstBatch = claimNextBatch(queue, "api|frontend");
    expect(firstBatch.tasks).toHaveLength(1);
    expect(firstBatch.framework).toBe("playwright");

    const secondBatch = claimNextBatch(queue, "api|frontend");
    expect(secondBatch.tasks).toHaveLength(1);
    expect(secondBatch.framework).toBe("playwright");

    const thirdBatch = claimNextBatch(queue, "api|frontend");
    expect(thirdBatch.tasks).toHaveLength(2);
    expect(thirdBatch.framework).toBe("k6");
  });

  it("allows Playwright suites to opt into bounded multi-file batches", () => {
    const frontend = makeConfig("frontend");
    const plans = [
      {
        config: frontend,
        skipped: false,
        runtimeConfigs: [frontend],
        runtimeNames: ["frontend"],
        runtimeKey: "frontend",
        suites: [
          {
            name: "auth",
            type: "e2e",
            framework: "playwright",
            files: ["a.spec.js", "b.spec.js", "c.spec.js"],
            orderIndex: 0,
            weight: 3,
            maxFileConcurrency: 2,
          },
        ],
      },
    ];

    const graphs = buildRuntimeGraphs(plans);
    const queue = buildTaskQueue(plans, graphs, { files: {} });

    const firstBatch = claimNextBatch(queue, "frontend");
    expect(firstBatch.tasks.map((task) => task.file)).toEqual(["a.spec.js", "b.spec.js"]);
    expect(firstBatch.tasks).toHaveLength(2);
    expect(firstBatch.framework).toBe("playwright");

    const secondBatch = claimNextBatch(queue, "frontend");
    expect(secondBatch.tasks).toHaveLength(1);
    expect(secondBatch.framework).toBe("playwright");
  });
});
