import { describe, expect, test } from "vitest";

import type { Authorizer } from "#src/authority/authorizer";
import { AuthorizerRegistry } from "#src/authority/authorizer-registry";

const noopLink: Authorizer["authorize"] = () =>
  Promise.resolve({ kind: "defer" });

describe("AuthorizerRegistry", () => {
  describe("register", () => {
    test("stores a link so get() returns it", () => {
      const registry = new AuthorizerRegistry();
      registry.register("model-judge", noopLink);
      expect(registry.get("model-judge")).toBe(noopLink);
    });

    test("returns a disposer that removes the link", () => {
      const registry = new AuthorizerRegistry();
      const dispose = registry.register("model-judge", noopLink);
      dispose();
      expect(registry.get("model-judge")).toBeUndefined();
    });

    test("throws when a link is already registered for the same name", () => {
      const registry = new AuthorizerRegistry();
      registry.register("model-judge", noopLink);
      expect(() =>
        registry.register("model-judge", () =>
          Promise.resolve({ kind: "defer" }),
        ),
      ).toThrow("model-judge");
    });

    test("allows registering different names independently", () => {
      const registry = new AuthorizerRegistry();
      const linkA: Authorizer["authorize"] = () =>
        Promise.resolve({ kind: "allow" });
      const linkB: Authorizer["authorize"] = () =>
        Promise.resolve({ kind: "deny" });
      registry.register("judge-a", linkA);
      registry.register("judge-b", linkB);
      expect(registry.get("judge-a")).toBe(linkA);
      expect(registry.get("judge-b")).toBe(linkB);
    });
  });

  describe("disposer identity guard", () => {
    test("stale disposer does not evict a later registration", () => {
      const registry = new AuthorizerRegistry();
      const first: Authorizer["authorize"] = () =>
        Promise.resolve({ kind: "defer" });
      const second: Authorizer["authorize"] = () =>
        Promise.resolve({ kind: "allow" });

      const disposeFirst = registry.register("model-judge", first);
      disposeFirst(); // removes first

      registry.register("model-judge", second); // second registration is valid
      disposeFirst(); // stale disposer again — must not remove second

      expect(registry.get("model-judge")).toBe(second);
    });
  });

  describe("get", () => {
    test("returns undefined for an unregistered name", () => {
      const registry = new AuthorizerRegistry();
      expect(registry.get("unknown")).toBeUndefined();
    });
  });
});
