import { describe, it, expect, vi, beforeEach } from "vitest";
import type { SanitizedConfig } from "payload";
import { PayloadProcessingRestriction } from "@/payload-processing-restriction";
import { RecordingAuditLog } from "@repo/core-testing/instrumentation";

type MockPayload = {
  find: ReturnType<typeof vi.fn>;
  update: ReturnType<typeof vi.fn>;
};

function makeMockPayload(): MockPayload {
  return {
    find: vi.fn(),
    update: vi.fn().mockResolvedValue({}),
  };
}

const emptyConfig = { collections: [] } as unknown as SanitizedConfig;

describe("PayloadProcessingRestriction", () => {
  let auditLog: RecordingAuditLog;
  let mockPayload: MockPayload;
  let mockGetPayload: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    auditLog = new RecordingAuditLog();
    mockPayload = makeMockPayload();
    mockGetPayload = vi.fn().mockResolvedValue(mockPayload);
  });

  describe("setRestriction", () => {
    it("sets processingRestrictedAt when granted=true", async () => {
      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      await restriction.setRestriction("alice", true);

      expect(mockPayload.update).toHaveBeenCalledWith(
        expect.objectContaining({
          collection: "users",
          id: "alice",
          data: expect.objectContaining({
            processingRestrictedAt: expect.any(String),
          }),
          overrideAccess: true,
        }),
      );
    });

    it("clears processingRestrictedAt when granted=false", async () => {
      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      await restriction.setRestriction("alice", false);

      expect(mockPayload.update).toHaveBeenCalledWith(
        expect.objectContaining({
          data: { processingRestrictedAt: null },
        }),
      );
    });

    it("emits RESTRICT audit entry when granting restriction", async () => {
      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      await restriction.setRestriction("alice", true);

      expect(auditLog.recorded).toHaveLength(1);
      const entry = auditLog.recorded[0]!;
      expect(entry.action).toBe("RESTRICT");
      expect(entry.actorId).toBe("alice");
      expect(entry.changedFields).toContain("processingRestrictedAt");
    });

    it("emits UNRESTRICT audit entry when lifting restriction", async () => {
      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      await restriction.setRestriction("alice", false);

      expect(auditLog.recorded[0]!.action).toBe("UNRESTRICT");
    });
  });

  describe("isRestricted", () => {
    it("returns true when processingRestrictedAt is set", async () => {
      mockPayload.find.mockResolvedValue({
        docs: [
          { id: "alice", processingRestrictedAt: "2024-01-01T00:00:00.000Z" },
        ],
      });

      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      expect(await restriction.isRestricted("alice")).toBe(true);
    });

    it("returns false when processingRestrictedAt is null", async () => {
      mockPayload.find.mockResolvedValue({
        docs: [{ id: "alice", processingRestrictedAt: null }],
      });

      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      expect(await restriction.isRestricted("alice")).toBe(false);
    });

    it("returns false when user record not found", async () => {
      mockPayload.find.mockResolvedValue({ docs: [] });

      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      expect(await restriction.isRestricted("ghost")).toBe(false);
    });

    it("restriction flag honored: setRestriction(true) then isRestricted returns true", async () => {
      // setRestriction updates the DB; simulate the updated state via find mock
      mockPayload.find.mockResolvedValue({
        docs: [
          { id: "alice", processingRestrictedAt: new Date().toISOString() },
        ],
      });

      const restriction = new PayloadProcessingRestriction(
        emptyConfig,
        auditLog,
        mockGetPayload,
      );
      await restriction.setRestriction("alice", true);
      const result = await restriction.isRestricted("alice");

      expect(result).toBe(true);
    });
  });
});
