import { describe, expect, test } from "bun:test";
import { pathToFileURL } from "node:url";
import { join } from "node:path";
import { defineAuthz } from "../src/registry.ts";
import { mergeCatalogs } from "../src/catalog.ts";
import { getAuthzCatalog, hasAuthzCatalog, setAuthzCatalog } from "../src/client.ts";

const CLIENT_URL = pathToFileURL(join(import.meta.dir, "..", "src", "client.ts")).href;

describe("authz process-wide catalog singleton", () => {
  // `catalog` is module-level state, and bun test does NOT isolate module
  // instances between test files run in the same `bun test` invocation (a
  // single import in one file is visible to every other file in the run). So
  // "before any setAuthzCatalog call anywhere in the whole suite" cannot be
  // observed reliably in-process — a fresh subprocess is the only way to
  // guarantee the catalog genuinely has never been set.
  test("getAuthzCatalog throws a setup error before setAuthzCatalog is ever called, in a fresh process", async () => {
    const proc = Bun.spawn({
      cmd: [
        "bun",
        "-e",
        `const mod = await import(${JSON.stringify(CLIENT_URL)});
         if (mod.hasAuthzCatalog()) { console.log("UNEXPECTED_HAS_CATALOG"); process.exit(1); }
         try {
           mod.getAuthzCatalog();
           console.log("UNEXPECTED_NO_THROW");
           process.exit(1);
         } catch (e) {
           console.log("THREW:" + (e instanceof Error ? e.message : String(e)));
         }`,
      ],
      stdout: "pipe",
      stderr: "pipe",
      cwd: join(import.meta.dir, ".."),
    });
    const [stdout, stderr, exitCode] = await Promise.all([
      new Response(proc.stdout).text(),
      new Response(proc.stderr).text(),
      proc.exited,
    ]);
    expect(stderr).toBe("");
    expect(exitCode).toBe(0);
    expect(stdout).toContain("THREW:");
    // Names the fix, like getDb()'s "No database configured. Add `db: ...`" message.
    expect(stdout).toContain("WRN-AUTHZ-SETUP");
    expect(stdout).toContain("setAuthzCatalog");
  });

  test("setAuthzCatalog/getAuthzCatalog round-trip, and hasAuthzCatalog reflects the set state", () => {
    const catalog = mergeCatalogs([
      {
        source: "client.test.ts",
        module: defineAuthz({ permissions: { "post:read": { title: "View posts" } } }),
      },
    ]);

    const returned = setAuthzCatalog(catalog);
    expect(returned).toBe(catalog);
    expect(hasAuthzCatalog()).toBe(true);
    expect(getAuthzCatalog()).toBe(catalog);
    expect(getAuthzCatalog().permissions.get("post:read")).toEqual({ title: "View posts" });
  });

  test("setAuthzCatalog overwrites a previously set catalog", () => {
    const first = mergeCatalogs([
      { source: "a.ts", module: defineAuthz({ permissions: { "a:read": {} } }) },
    ]);
    const second = mergeCatalogs([
      { source: "b.ts", module: defineAuthz({ permissions: { "b:read": {} } }) },
    ]);
    setAuthzCatalog(first);
    expect(getAuthzCatalog()).toBe(first);
    setAuthzCatalog(second);
    expect(getAuthzCatalog()).toBe(second);
    expect(getAuthzCatalog().permissions.has("a:read")).toBe(false);
    expect(getAuthzCatalog().permissions.has("b:read")).toBe(true);
  });
});
