import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { clearAdmissionCache, hostEligible, type SpineFetcher } from "./admission";

describe("admission client", () => {
  const previousObey = process.env.OVERDECK_SPINE_OBEY;
  const previousConfigDir = process.env.OVERDECK_CONFIG_DIR;

  afterEach(() => {
    clearAdmissionCache();
    if (previousObey === undefined) delete process.env.OVERDECK_SPINE_OBEY;
    else process.env.OVERDECK_SPINE_OBEY = previousObey;
    if (previousConfigDir === undefined) delete process.env.OVERDECK_CONFIG_DIR;
    else process.env.OVERDECK_CONFIG_DIR = previousConfigDir;
  });

  test("fails open when obey flag is unset", async () => {
    delete process.env.OVERDECK_SPINE_OBEY;
    await expect(hostEligible("debian1", "bun")).resolves.toEqual({
      eligible: true,
      reason: "controller-absent",
    });
  });

  test("propagates controller denials and scopes by command", async () => {
    process.env.OVERDECK_SPINE_OBEY = "1";
    const dir = mkdtempSync(join(tmpdir(), "spine-admission-"));
    process.env.OVERDECK_CONFIG_DIR = dir;
    writeFileSync(join(dir, "token"), "secret-token", { mode: 0o600 });

    let calls = 0;
    const fetcher: SpineFetcher = async (input) => {
      calls += 1;
      const url = new URL(String(input));
      if (url.searchParams.get("command") === "playwright") {
        return Response.json({ eligible: false, reason: "command-quarantined" });
      }
      return Response.json({ eligible: true, reason: "eligible" });
    };

    await expect(hostEligible("debian1", "playwright", { fetcher })).resolves.toEqual({
      eligible: false,
      reason: "command-quarantined",
    });
    await expect(hostEligible("debian1", "bun", { fetcher })).resolves.toEqual({
      eligible: true,
      reason: "eligible",
    });
    expect(calls).toBe(2);
  });

  test("caches verdicts for up to five seconds", async () => {
    process.env.OVERDECK_SPINE_OBEY = "1";
    const dir = mkdtempSync(join(tmpdir(), "spine-admission-cache-"));
    process.env.OVERDECK_CONFIG_DIR = dir;
    writeFileSync(join(dir, "token"), "secret-token", { mode: 0o600 });

    let calls = 0;
    let now = 1_000;
    const fetcher: SpineFetcher = async () => {
      calls += 1;
      return Response.json({ eligible: true, reason: "eligible" });
    };

    const opts = { fetcher, now: () => now };
    await hostEligible("debian1", "bun", opts);
    now += 4_000;
    await hostEligible("debian1", "bun", opts);
    expect(calls).toBe(1);

    now += 2_000;
    await hostEligible("debian1", "bun", opts);
    expect(calls).toBe(2);
  });

  test("fails open when controller is unreachable", async () => {
    process.env.OVERDECK_SPINE_OBEY = "1";
    const dir = mkdtempSync(join(tmpdir(), "spine-admission-down-"));
    process.env.OVERDECK_CONFIG_DIR = dir;
    writeFileSync(join(dir, "token"), "secret-token", { mode: 0o600 });

    const fetcher: SpineFetcher = async () => {
      throw new Error("connection refused");
    };
    await expect(hostEligible("debian1", "bun", { fetcher })).resolves.toEqual({
      eligible: true,
      reason: "controller-absent",
    });
  });
});
