import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import type { ApprovalRequest, ThreadId, TurnId } from "@best-agent/protocol";
import { LocalApprovalStore } from "./approval-store.js";

const storageRoots: string[] = [];

afterEach(() => {
  while (storageRoots.length > 0) {
    rmSync(storageRoots.pop() as string, { force: true, recursive: true });
  }
});

describe("approval store", () => {
  it("isolates grants by kind and thread and deletes thread grants by thread", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-approval-store-`);
    storageRoots.push(storageRoot);

    const store = new LocalApprovalStore({ storageRoot });
    const threadA = "thread_a" as ThreadId;
    const threadB = "thread_b" as ThreadId;
    try {
      const workspaceGrant = await store.saveGrant({
        action: "node --version",
        group: "shell",
        kind: "shell",
        scope: "workspace",
      });
      const workspaceMcpGrant = await store.saveGrant({
        action: "node --version",
        group: "shell",
        kind: "mcp",
        scope: "workspace",
      });
      const threadGrantA = await store.saveGrant({
        action: "node --version",
        group: "shell",
        kind: "shell",
        scope: "thread",
        threadId: threadA,
      });
      const threadGrantB = await store.saveGrant({
        action: "node --version",
        group: "shell",
        kind: "shell",
        scope: "thread",
        threadId: threadB,
      });

      expect(await store.findGrant("shell", "shell", "node --version")).toMatchObject({
        id: workspaceGrant.id,
        kind: "shell",
        scope: "workspace",
      });
      expect(await store.findGrant("shell", "mcp", "node --version")).toMatchObject({
        id: workspaceMcpGrant.id,
        kind: "mcp",
        scope: "workspace",
      });
      expect(
        await store.findGrant("shell", "shell", "node --version", { threadId: threadA }),
      ).toMatchObject({
        id: threadGrantA.id,
        kind: "shell",
        scope: "thread",
        threadId: threadA,
      });
      expect(
        await store.findGrant("shell", "shell", "node --version", { threadId: threadB }),
      ).toMatchObject({
        id: threadGrantB.id,
        kind: "shell",
        scope: "thread",
        threadId: threadB,
      });

      await store.deleteGrantsForThread(threadA);
      expect(
        await store.findGrant("shell", "shell", "node --version", { threadId: threadA }),
      ).toMatchObject({
        id: workspaceGrant.id,
        kind: "shell",
        scope: "workspace",
      });
      expect(
        await store.findGrant("shell", "mcp", "node --version", { threadId: threadA }),
      ).toMatchObject({
        id: workspaceMcpGrant.id,
        kind: "mcp",
        scope: "workspace",
      });
      expect(
        await store.findGrant("shell", "shell", "node --version", { threadId: threadB }),
      ).toMatchObject({
        id: threadGrantB.id,
        kind: "shell",
        scope: "thread",
        threadId: threadB,
      });
      expect(await store.listGrants()).toEqual(
        expect.arrayContaining([
          expect.objectContaining({ id: workspaceGrant.id, kind: "shell", scope: "workspace" }),
          expect.objectContaining({ id: workspaceMcpGrant.id, kind: "mcp", scope: "workspace" }),
          expect.objectContaining({
            id: threadGrantB.id,
            kind: "shell",
            scope: "thread",
            threadId: threadB,
          }),
        ]),
      );
    } finally {
      store.close();
    }
  });

  it("persists approval review records and settles them by approval outcome", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-approval-store-`);
    storageRoots.push(storageRoot);

    const store = new LocalApprovalStore({ storageRoot });
    const threadId = "thread_reviews" as ThreadId;
    const turnId = "turn_reviews" as TurnId;
    const approval: ApprovalRequest = {
      action: "node --version",
      availableScopes: ["once", "thread", "workspace"],
      guardianAssessment: {
        evidence: ["Command only requests version output."],
        rationale: "Safe read-only probe.",
        riskLevel: "low",
        riskScore: 20,
      },
      group: "shell",
      id: "approval_reviews" as ApprovalRequest["id"],
      kind: "shell",
      riskClass: "high",
      reviewMode: "guardian",
      sandboxProfile: "no-network",
      requestedAt: new Date().toISOString(),
      threadId,
      toolCallId: "item_tool" as ApprovalRequest["toolCallId"],
      turnId,
    };

    try {
      const pending = await store.saveReviewRequest(approval);
      expect(pending).toMatchObject({
        action: "node --version",
        id: approval.id,
        guardianAssessment: approval.guardianAssessment,
        riskClass: "high",
        reviewMode: "guardian",
        sandboxProfile: "no-network",
        status: "pending",
        threadId,
        turnId,
      });

      expect(await store.listReviews(threadId)).toEqual([
        expect.objectContaining({
          id: approval.id,
          status: "pending",
        }),
      ]);

      const approved = await store.settleReview(approval.id, {
        decision: "allow",
        scope: "thread",
        status: "approved",
      });
      expect(approved).toMatchObject({
        decision: "allow",
        guardianAssessment: approval.guardianAssessment,
        id: approval.id,
        reviewMode: "guardian",
        sandboxProfile: "no-network",
        scope: "thread",
        status: "approved",
      });

      const expiredApproval: ApprovalRequest = {
        ...approval,
        id: "approval_expired" as ApprovalRequest["id"],
        riskClass: "medium",
        reviewMode: "direct",
        sandboxProfile: "none",
        requestedAt: new Date(Date.now() + 1).toISOString(),
        toolCallId: "item_tool_2" as ApprovalRequest["toolCallId"],
      };
      await store.saveReviewRequest(expiredApproval);
      const expired = await store.settleReview(expiredApproval.id, {
        status: "expired",
      });
      expect(expired).toMatchObject({
        id: expiredApproval.id,
        reviewMode: "direct",
        sandboxProfile: "none",
        status: "expired",
      });
      expect(expired).not.toHaveProperty("decision");

      await store.deleteReviewsForThread(threadId);
      expect(await store.listReviews(threadId)).toEqual([]);
    } finally {
      store.close();
    }
  });
});
