import { beforeEach, describe, expect, it, vi } from "vitest";

const cookieGetMock = vi.fn();
const cookieSetMock = vi.fn();
const updateManyMock = vi.fn();
const getSessionPepperMock = vi.fn();
const hashRuleClaimTokenMock = vi.fn();

vi.mock("next/headers", () => ({
  cookies: async () => ({
    get: cookieGetMock,
    set: cookieSetMock,
  }),
}));

vi.mock("../../lib/server/db", () => ({
  prisma: {
    publishedRule: {
      updateMany: (...args: unknown[]) => updateManyMock(...args),
    },
  },
}));

vi.mock("../../lib/server/env", () => ({
  getSessionPepper: () => getSessionPepperMock(),
}));

vi.mock("../../lib/server/hash", () => ({
  hashRuleClaimToken: (...args: unknown[]) => hashRuleClaimTokenMock(...args),
}));

vi.mock("../../lib/logger", () => ({
  logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));

import {
  claimGuestRulesForUser,
  issueGuestRuleClaimCookie,
} from "../../lib/server/guestRuleClaim";

beforeEach(() => {
  cookieGetMock.mockReset();
  cookieSetMock.mockReset();
  updateManyMock.mockReset();
  getSessionPepperMock.mockReset();
  hashRuleClaimTokenMock.mockReset();
  getSessionPepperMock.mockReturnValue("pepper");
  hashRuleClaimTokenMock.mockImplementation((token: string) => `h-${token}`);
});

describe("issueGuestRuleClaimCookie", () => {
  it("stores the token in an httpOnly cookie", async () => {
    cookieGetMock.mockReturnValue(undefined);
    await issueGuestRuleClaimCookie("token-aaaaaaaaaaaaaaaa");
    expect(cookieSetMock).toHaveBeenCalledWith(
      "cr_rule_claim",
      JSON.stringify(["token-aaaaaaaaaaaaaaaa"]),
      expect.objectContaining({
        httpOnly: true,
        path: "/",
        sameSite: "lax",
      }),
    );
  });

  it("appends without duplicating", async () => {
    cookieGetMock.mockReturnValue({
      value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]),
    });
    await issueGuestRuleClaimCookie("token-bbbbbbbbbbbbbbbb");
    expect(cookieSetMock).toHaveBeenCalledWith(
      "cr_rule_claim",
      JSON.stringify(["token-aaaaaaaaaaaaaaaa", "token-bbbbbbbbbbbbbbbb"]),
      expect.any(Object),
    );
  });
});

describe("claimGuestRulesForUser", () => {
  it("returns 0 when the cookie is empty", async () => {
    cookieGetMock.mockReturnValue(undefined);
    const claimed = await claimGuestRulesForUser("user-1");
    expect(claimed).toBe(0);
    expect(updateManyMock).not.toHaveBeenCalled();
  });

  it("claims matching ownerless rows and clears the cookie", async () => {
    cookieGetMock.mockReturnValue({
      value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]),
    });
    updateManyMock.mockResolvedValueOnce({ count: 1 });

    const claimed = await claimGuestRulesForUser("user-1");

    expect(claimed).toBe(1);
    expect(hashRuleClaimTokenMock).toHaveBeenCalledWith(
      "token-aaaaaaaaaaaaaaaa",
      "pepper",
    );
    expect(updateManyMock).toHaveBeenCalledWith({
      where: { claimTokenHash: "h-token-aaaaaaaaaaaaaaaa", userId: null },
      data: { userId: "user-1", claimTokenHash: null },
    });
    expect(cookieSetMock).toHaveBeenCalledWith(
      "cr_rule_claim",
      "",
      expect.objectContaining({ maxAge: 0 }),
    );
  });

  it("does not attach a rule that already has an owner", async () => {
    cookieGetMock.mockReturnValue({
      value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]),
    });
    updateManyMock.mockResolvedValueOnce({ count: 0 });

    const claimed = await claimGuestRulesForUser("user-1");

    expect(claimed).toBe(0);
    expect(cookieSetMock).toHaveBeenCalledWith(
      "cr_rule_claim",
      "",
      expect.objectContaining({ maxAge: 0 }),
    );
  });
});
