import { describe, it, expect, beforeEach } from "vitest";
import { TRPCError } from "@trpc/server";
import { RecordingConsent } from "@repo/core-testing/instrumentation";
import { consentRouter } from "@/consent.router";
import type { ConsentRouterContext } from "@/consent.router";
import type { IConsent } from "@/consent.interface";

function makeContext(
  consent: RecordingConsent,
  userId?: string,
): ConsentRouterContext {
  return {
    userId,
    consentFactory: async () => consent,
  };
}

describe("consentRouter — procedure surface", () => {
  it("exposes grant, withdraw, isGranted, getCategories procedures", () => {
    const names = Object.keys(consentRouter._def.procedures);
    expect(names).toContain("grant");
    expect(names).toContain("withdraw");
    expect(names).toContain("isGranted");
    expect(names).toContain("getCategories");
  });
});

describe("consentRouter — response shapes", () => {
  let consent: RecordingConsent;
  let caller: ReturnType<typeof consentRouter.createCaller>;

  beforeEach(() => {
    consent = new RecordingConsent();
    caller = consentRouter.createCaller(makeContext(consent, "user-1"));
  });

  it("grant returns { success: true } and records the grant", async () => {
    const result = await caller.grant({ category: "analytics" });
    expect(result).toEqual({ success: true });
    expect(consent.grants).toHaveLength(1);
    expect(consent.grants[0]!.category).toBe("analytics");
  });

  it("grant forwards meta to the consent impl", async () => {
    await caller.grant({
      category: "marketing",
      meta: {
        bannerVersion: "v2",
        policyVersion: "2026-01",
        method: "banner-accept",
      },
    });
    expect(consent.grants[0]!.meta).toEqual({
      bannerVersion: "v2",
      policyVersion: "2026-01",
      method: "banner-accept",
    });
  });

  it("withdraw returns { success: true } and records the withdrawal", async () => {
    await caller.grant({ category: "marketing" });
    const result = await caller.withdraw({ category: "marketing" });
    expect(result).toEqual({ success: true });
    expect(consent.withdrawals).toHaveLength(1);
    expect(consent.withdrawals[0]).toBe("marketing");
  });

  it("isGranted returns { granted: false } before any grant", async () => {
    const result = await caller.isGranted({ category: "analytics" });
    expect(result).toEqual({ granted: false });
  });

  it("isGranted returns { granted: true } after grant", async () => {
    await caller.grant({ category: "analytics" });
    const result = await caller.isGranted({ category: "analytics" });
    expect(result).toEqual({ granted: true });
  });

  it("getCategories returns { categories: [] } initially", async () => {
    const result = await caller.getCategories({});
    expect(result).toEqual({ categories: [] });
  });

  it("getCategories returns all granted categories", async () => {
    await caller.grant({ category: "necessary" });
    await caller.grant({ category: "analytics" });
    const { categories } = await caller.getCategories({});
    expect(categories).toHaveLength(2);
    const names = categories.map((c) => c.category).sort();
    expect(names).toEqual(["analytics", "necessary"]);
  });

  it("consent round-trip: grant → isGranted → withdraw → isGranted", async () => {
    await caller.grant({ category: "functional" });
    expect((await caller.isGranted({ category: "functional" })).granted).toBe(
      true,
    );
    await caller.withdraw({ category: "functional" });
    expect((await caller.isGranted({ category: "functional" })).granted).toBe(
      false,
    );
  });
});

describe("consentRouter — auth checks", () => {
  it("grant → UNAUTHORIZED when userId is absent", async () => {
    const caller = consentRouter.createCaller(
      makeContext(new RecordingConsent()),
    );
    await expect(caller.grant({ category: "analytics" })).rejects.toMatchObject(
      {
        code: "UNAUTHORIZED",
      },
    );
  });

  it("withdraw → UNAUTHORIZED when userId is absent", async () => {
    const caller = consentRouter.createCaller(
      makeContext(new RecordingConsent()),
    );
    await expect(
      caller.withdraw({ category: "analytics" }),
    ).rejects.toMatchObject({
      code: "UNAUTHORIZED",
    });
  });

  it("isGranted → UNAUTHORIZED when userId is absent", async () => {
    const caller = consentRouter.createCaller(
      makeContext(new RecordingConsent()),
    );
    await expect(
      caller.isGranted({ category: "analytics" }),
    ).rejects.toMatchObject({
      code: "UNAUTHORIZED",
    });
  });

  it("getCategories → UNAUTHORIZED when userId is absent", async () => {
    const caller = consentRouter.createCaller(
      makeContext(new RecordingConsent()),
    );
    await expect(caller.getCategories({})).rejects.toMatchObject({
      code: "UNAUTHORIZED",
    });
  });

  it("rejected error is a TRPCError instance", async () => {
    const caller = consentRouter.createCaller(
      makeContext(new RecordingConsent()),
    );
    await expect(
      caller.grant({ category: "analytics" }),
    ).rejects.toBeInstanceOf(TRPCError);
  });
});

describe("consentRouter — error passthrough", () => {
  it("propagates unmapped errors as INTERNAL_SERVER_ERROR", async () => {
    const brokenConsent: IConsent = {
      isGranted: () => false,
      grant: async () => {
        throw new Error("storage failure");
      },
      withdraw: async () => {},
      getCategories: () => [],
    };
    const caller = consentRouter.createCaller({
      userId: "user-1",
      consentFactory: async () => brokenConsent,
    });
    await expect(caller.grant({ category: "analytics" })).rejects.toMatchObject(
      {
        code: "INTERNAL_SERVER_ERROR",
      },
    );
  });
});
