import { describe, it, expect, beforeEach } from "vitest";
import { router } from "@repo/core-shared/trpc/init";
import { appRouter } from "./root";
import { createDsrRouter } from "@repo/core-dsr";
import type { DsrBinding } from "@repo/core-dsr";
import type { DsrTrpcUser } from "@repo/core-dsr";
import { consentRouter } from "@repo/core-consent";
import type { ConsentFactory } from "@repo/core-consent";
import {
  RecordingDataExport,
  RecordingDataDelete,
  RecordingDataRectify,
  RecordingProcessingRestriction,
  RecordingConsent,
} from "@repo/core-testing/instrumentation";

// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------

function makeDsrBinding() {
  return {
    dataExport: new RecordingDataExport(),
    dataDelete: new RecordingDataDelete(),
    dataRectify: new RecordingDataRectify(),
    processingRestriction: new RecordingProcessingRestriction(),
  };
}

type DsrTestBinding = ReturnType<typeof makeDsrBinding>;

function makeIntegrationRouter(dsrBinding: DsrTestBinding) {
  return router({
    dsr: createDsrRouter(dsrBinding as unknown as DsrBinding),
    consent: consentRouter,
  });
}

type IntegrationRouter = ReturnType<typeof makeIntegrationRouter>;

function makeCaller(
  testRouter: IntegrationRouter,
  userId: string,
  consentFactory: ConsentFactory,
  roles: string[] = ["user"],
) {
  return testRouter.createCaller({
    user: { id: userId, roles } as DsrTrpcUser,
    userId,
    consentFactory,
  } as Record<string, unknown>);
}

function makeUnauthCaller(
  testRouter: IntegrationRouter,
  consentFactory: ConsentFactory,
) {
  return testRouter.createCaller({
    consentFactory,
  } as Record<string, unknown>);
}

// ---------------------------------------------------------------------------
// Structure tests — appRouter composition
// ---------------------------------------------------------------------------

describe("appRouter composition", () => {
  it("exposes auth, blog, marketingPages, navigation, media routers", () => {
    const procedures = appRouter._def.procedures;
    const keys = Object.keys(procedures);
    expect(keys.some((k) => k.startsWith("auth."))).toBe(true);
    expect(keys.some((k) => k.startsWith("blog."))).toBe(true);
    expect(keys.some((k) => k.startsWith("marketingPages."))).toBe(true);
    expect(keys.some((k) => k.startsWith("navigation."))).toBe(true);
    expect(keys.some((k) => k.startsWith("media."))).toBe(true);
  });

  it("blog router has expected procedures", () => {
    const procedures = appRouter._def.procedures;
    expect(procedures).toHaveProperty("blog.articleBySlug");
    expect(procedures).toHaveProperty("blog.listArticles");
  });

  it("exposes dsr and consent routers", () => {
    const keys = Object.keys(appRouter._def.procedures);
    expect(keys.some((k) => k.startsWith("dsr."))).toBe(true);
    expect(keys.some((k) => k.startsWith("consent."))).toBe(true);
  });

  it("dsr router exposes all four procedures", () => {
    const procedures = appRouter._def.procedures;
    expect(procedures).toHaveProperty("dsr.export");
    expect(procedures).toHaveProperty("dsr.delete");
    expect(procedures).toHaveProperty("dsr.rectify");
    expect(procedures).toHaveProperty("dsr.restrict");
  });

  it("consent router exposes all four procedures", () => {
    const procedures = appRouter._def.procedures;
    expect(procedures).toHaveProperty("consent.grant");
    expect(procedures).toHaveProperty("consent.withdraw");
    expect(procedures).toHaveProperty("consent.isGranted");
    expect(procedures).toHaveProperty("consent.getCategories");
  });
});

// ---------------------------------------------------------------------------
// Integration tests — dsr procedures
// ---------------------------------------------------------------------------

describe("dsr.export — integration", () => {
  let binding: DsrTestBinding;
  let consent: RecordingConsent;
  let caller: ReturnType<typeof makeCaller>;

  beforeEach(() => {
    binding = makeDsrBinding();
    consent = new RecordingConsent();
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    caller = makeCaller(testRouter, "alice", factory);
  });

  it("resolves with UserDataBundle body for authenticated user", async () => {
    const result = await caller.dsr.export({
      subjectId: "alice",
      format: "json",
    });
    expect(result.subjectId).toBe("alice");
    expect(result.format).toBe("json");
    expect(binding.dataExport.calls).toHaveLength(1);
  });

  it("resolves with json-ld format", async () => {
    const result = await caller.dsr.export({
      subjectId: "alice",
      format: "json-ld",
    });
    expect(result.format).toBe("json-ld");
  });

  it("throws UNAUTHORIZED when unauthenticated", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const unauthCaller = makeUnauthCaller(testRouter, factory);
    await expect(
      unauthCaller.dsr.export({ subjectId: "alice", format: "json" }),
    ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
  });
});

describe("dsr.delete — integration", () => {
  let binding: DsrTestBinding;
  let consent: RecordingConsent;

  beforeEach(() => {
    binding = makeDsrBinding();
    consent = new RecordingConsent();
  });

  it("resolves with DeletionCertificate for soft mode (authenticated user)", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const caller = makeCaller(testRouter, "alice", factory);
    const result = await caller.dsr.delete({
      subjectId: "alice",
      mode: "soft",
    });
    expect(result.subjectId).toBe("alice");
    expect(result.mode).toBe("soft");
    expect(binding.dataDelete.calls).toHaveLength(1);
  });

  it("resolves with DeletionCertificate for cascade-hard mode (admin)", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const adminCaller = makeCaller(testRouter, "admin", factory, ["admin"]);
    const result = await adminCaller.dsr.delete({
      subjectId: "alice",
      mode: "cascade-hard",
    });
    expect(result.mode).toBe("cascade-hard");
  });

  it("throws FORBIDDEN for cascade-hard when user lacks admin role", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const caller = makeCaller(testRouter, "alice", factory, ["user"]);
    await expect(
      caller.dsr.delete({ subjectId: "alice", mode: "cascade-hard" }),
    ).rejects.toMatchObject({ code: "FORBIDDEN" });
  });

  it("throws UNAUTHORIZED when unauthenticated", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const unauthCaller = makeUnauthCaller(testRouter, factory);
    await expect(
      unauthCaller.dsr.delete({ subjectId: "alice", mode: "soft" }),
    ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
  });
});

describe("dsr.rectify — integration", () => {
  let binding: DsrTestBinding;
  let consent: RecordingConsent;
  let caller: ReturnType<typeof makeCaller>;

  beforeEach(() => {
    binding = makeDsrBinding();
    consent = new RecordingConsent();
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    caller = makeCaller(testRouter, "alice", factory);
  });

  it("resolves with { ok: true } for authenticated user", async () => {
    const result = await caller.dsr.rectify({
      subjectId: "alice",
      collection: "users",
      field: "name",
      value: "Alice Updated",
    });
    expect(result).toEqual({ ok: true });
    expect(binding.dataRectify.calls[0]).toMatchObject({
      subjectId: "alice",
      collection: "users",
      field: "name",
    });
  });

  it("throws UNAUTHORIZED when unauthenticated", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const unauthCaller = makeUnauthCaller(testRouter, factory);
    await expect(
      unauthCaller.dsr.rectify({
        subjectId: "alice",
        collection: "users",
        field: "name",
        value: "x",
      }),
    ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
  });
});

describe("dsr.restrict — integration", () => {
  let binding: DsrTestBinding;
  let consent: RecordingConsent;
  let caller: ReturnType<typeof makeCaller>;

  beforeEach(() => {
    binding = makeDsrBinding();
    consent = new RecordingConsent();
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    caller = makeCaller(testRouter, "alice", factory);
  });

  it("resolves with { ok: true } when granting restriction", async () => {
    const result = await caller.dsr.restrict({
      subjectId: "alice",
      granted: true,
    });
    expect(result).toEqual({ ok: true });
    expect(binding.processingRestriction.sets[0]).toMatchObject({
      subjectId: "alice",
      granted: true,
    });
  });

  it("resolves with { ok: true } when lifting restriction", async () => {
    const result = await caller.dsr.restrict({
      subjectId: "alice",
      granted: false,
    });
    expect(result).toEqual({ ok: true });
    expect(binding.processingRestriction.sets[0]?.granted).toBe(false);
  });

  it("throws UNAUTHORIZED when unauthenticated", async () => {
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    const unauthCaller = makeUnauthCaller(testRouter, factory);
    await expect(
      unauthCaller.dsr.restrict({ subjectId: "alice", granted: true }),
    ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
  });
});

// ---------------------------------------------------------------------------
// Integration tests — consent procedures
// ---------------------------------------------------------------------------

describe("consent — integration", () => {
  let consent: RecordingConsent;
  let caller: ReturnType<typeof makeCaller>;

  beforeEach(() => {
    const binding = makeDsrBinding();
    consent = new RecordingConsent();
    const factory: ConsentFactory = async () => consent;
    const testRouter = makeIntegrationRouter(binding);
    caller = makeCaller(testRouter, "user-1", factory);
  });

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

    it("throws UNAUTHORIZED when userId is absent", async () => {
      const c = new RecordingConsent();
      const factory: ConsentFactory = async () => c;
      const testRouter = makeIntegrationRouter(makeDsrBinding());
      const unauthCaller = makeUnauthCaller(testRouter, factory);
      await expect(
        unauthCaller.consent.grant({ category: "analytics" }),
      ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
    });
  });

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

    it("throws UNAUTHORIZED when userId is absent", async () => {
      const c = new RecordingConsent();
      const factory: ConsentFactory = async () => c;
      const testRouter = makeIntegrationRouter(makeDsrBinding());
      const unauthCaller = makeUnauthCaller(testRouter, factory);
      await expect(
        unauthCaller.consent.withdraw({ category: "analytics" }),
      ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
    });
  });

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

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

    it("throws UNAUTHORIZED when userId is absent", async () => {
      const c = new RecordingConsent();
      const factory: ConsentFactory = async () => c;
      const testRouter = makeIntegrationRouter(makeDsrBinding());
      const unauthCaller = makeUnauthCaller(testRouter, factory);
      await expect(
        unauthCaller.consent.isGranted({ category: "analytics" }),
      ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
    });
  });

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

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

    it("throws UNAUTHORIZED when userId is absent", async () => {
      const c = new RecordingConsent();
      const factory: ConsentFactory = async () => c;
      const testRouter = makeIntegrationRouter(makeDsrBinding());
      const unauthCaller = makeUnauthCaller(testRouter, factory);
      await expect(
        unauthCaller.consent.getCategories({}),
      ).rejects.toMatchObject({ code: "UNAUTHORIZED" });
    });
  });
});
