import { describe, expect, it, vi } from "vitest";
import type { Authorizer, AuthorizerVerdict } from "#src/authority/authorizer";
import { encloseInDelegationEnvelope } from "#src/authority/delegation-envelope";
import type { PromptPermissionDetails } from "#src/authority/permission-prompter";
import type { PermissionQuery } from "#src/service";
import { makeAuthorizerLog } from "#test/helpers/authorizer-log-fixtures";
import { makePromptDetails } from "#test/helpers/prompt-details-fixtures";

function makeQuery(): PermissionQuery {
  return { checkPermission: vi.fn(), getToolPermission: vi.fn() };
}

/** Build details whose gate-computed surface is `accessIntentSurface`. */
function makeDetails(
  accessIntentSurface: string | undefined,
  displaySurface?: string | null,
  toolName?: string,
 ): PromptPermissionDetails {
  return makePromptDetails({
    surface: displaySurface,
    toolName,
    accessIntent:
      accessIntentSurface === undefined
        ? undefined
        : {
            surface: accessIntentSurface,
            matchValues: ["/some/value"],
            boundaryValue: null,
          },
  });
}

/** A link whose fixed verdict the envelope may cap. */
function makeLink(verdict: AuthorizerVerdict): Authorizer["authorize"] {
  return vi.fn<Authorizer["authorize"]>().mockResolvedValue(verdict);
}

describe("encloseInDelegationEnvelope", () => {
  const query = makeQuery();
  const log = makeAuthorizerLog();

  describe("caps an allow verdict on an excluded surface to defer", () => {
    it("downgrades an allow on external_directory", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
      const verdict = await enclosed(
        makeDetails("external_directory"),
        query,
        log,
      );
      expect(verdict).toEqual({ kind: "defer" });
    });

    it.each(["write", "edit", "bash", "custom-tool"] as const)(
      "downgrades an allow on external_directory for %s",
      async (toolName) => {
        const enclosed = encloseInDelegationEnvelope(
          makeLink({ kind: "allow" }),
        );
        const verdict = await enclosed(
          makeDetails("external_directory", undefined, toolName),
          query,
          log,
        );
        expect(verdict).toEqual({ kind: "defer" });
      },
    );

    it("downgrades an allow on the path surface", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
      const verdict = await enclosed(makeDetails("path"), query, log);
      expect(verdict).toEqual({ kind: "defer" });
    });

    it("downgrades an allow when the surface is undetermined (fail-safe)", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
      const verdict = await enclosed(makeDetails(undefined, null), query, log);
      expect(verdict).toEqual({ kind: "defer" });
    });
  });

  describe("passes verdicts through unchanged", () => {
    it("keeps an allow on a non-excluded surface (bash)", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
      const verdict = await enclosed(makeDetails("bash"), query, log);
      expect(verdict).toEqual({ kind: "allow" });
    });

    it("keeps an allow on a per-tool surface (read)", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
      const verdict = await enclosed(makeDetails("read"), query, log);
      expect(verdict).toEqual({ kind: "allow" });
    });

    it.each(["read", "find", "grep", "ls"] as const)(
      "keeps an allow on external_directory for the built-in read-only tool %s",
      async (toolName) => {
        const enclosed = encloseInDelegationEnvelope(
          makeLink({ kind: "allow" }),
        );
        const verdict = await enclosed(
          makeDetails("external_directory", undefined, toolName),
          query,
          log,
        );
        expect(verdict).toEqual({ kind: "allow" });
      },
    );

    it("never caps a deny, even on an excluded surface", async () => {
      const enclosed = encloseInDelegationEnvelope(
        makeLink({ kind: "deny", reason: "wrong path" }),
      );
      const verdict = await enclosed(
        makeDetails("external_directory"),
        query,
        log,
      );
      expect(verdict).toEqual({ kind: "deny", reason: "wrong path" });
    });

    it("never caps a defer", async () => {
      const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "defer" }));
      const verdict = await enclosed(makeDetails("path"), query, log);
      expect(verdict).toEqual({ kind: "defer" });
    });
  });

  it("prefers the gate-computed accessIntent surface over the display surface", async () => {
    // accessIntent.surface (external_directory) is authoritative even when the
    // display-surface override says otherwise.
    const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" }));
    const verdict = await enclosed(
      makeDetails("external_directory", "bash"),
      query,
      log,
    );
    expect(verdict).toEqual({ kind: "defer" });
  });

  it("forwards details, the injected query, and the review-log seam to the wrapped link", async () => {
    const link = makeLink({ kind: "defer" });
    const enclosed = encloseInDelegationEnvelope(link);
    const details = makeDetails("bash");
    await enclosed(details, query, log);
    expect(link).toHaveBeenCalledWith(details, query, log);
  });
});
