import { describe, expect, it, vi } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { RecordingConsent } from "@repo/core-testing";
import {
  ConsentContextError,
  ConsentProvider,
  useConsent,
} from "@/react/index";

function makeWrapper(consent: RecordingConsent) {
  return function Wrapper({ children }: { children: React.ReactNode }) {
    return <ConsentProvider value={consent}>{children}</ConsentProvider>;
  };
}

describe("ConsentProvider / useConsent", () => {
  it("returns the injected IConsent instance", () => {
    const recording = new RecordingConsent();
    const { result } = renderHook(() => useConsent(), {
      wrapper: makeWrapper(recording),
    });
    expect(result.current).toBe(recording);
  });

  it("propagates grant() to the injected IConsent", async () => {
    const recording = new RecordingConsent();
    const { result } = renderHook(() => useConsent(), {
      wrapper: makeWrapper(recording),
    });

    await act(async () => {
      await result.current.grant("analytics");
    });

    expect(recording.grants).toContainEqual({
      category: "analytics",
      meta: undefined,
    });
  });

  it("propagates withdraw() to the injected IConsent", async () => {
    const recording = new RecordingConsent();
    await recording.grant("marketing");

    const { result } = renderHook(() => useConsent(), {
      wrapper: makeWrapper(recording),
    });

    await act(async () => {
      await result.current.withdraw("marketing");
    });

    expect(recording.withdrawals).toContain("marketing");
  });

  it("isGranted() reflects the injected instance state", async () => {
    const recording = new RecordingConsent();
    await recording.grant("functional");

    const { result } = renderHook(() => useConsent(), {
      wrapper: makeWrapper(recording),
    });

    expect(result.current.isGranted("functional")).toBe(true);
    expect(result.current.isGranted("marketing")).toBe(false);
  });

  it("getCategories() returns categories from the injected IConsent", async () => {
    const recording = new RecordingConsent();
    await recording.grant("necessary");

    const { result } = renderHook(() => useConsent(), {
      wrapper: makeWrapper(recording),
    });

    const cats = result.current.getCategories();
    expect(cats).toHaveLength(1);
    expect(cats[0]).toMatchObject({ category: "necessary", state: "granted" });
  });
});

describe("useConsent without provider", () => {
  it("throws ConsentContextError when called outside a provider", () => {
    const spy = vi.spyOn(console, "error").mockImplementation(() => {});
    try {
      expect(() => renderHook(() => useConsent())).toThrow(ConsentContextError);
    } finally {
      spy.mockRestore();
    }
  });
});
