import { SystemRole } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    isChargeabilityActualBooking: actual.isChargeabilityActualBooking,
    isChargeabilityRelevantProject: actual.isChargeabilityRelevantProject,
    listAssignmentBookings: vi.fn(),
    recomputeResourceValueScores: vi.fn(),
  };
});

vi.mock("../router/blueprint-validation.js", () => ({
  assertBlueprintDynamicFields: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("../lib/anonymization.js", () => ({
  anonymizeResource: vi.fn((r: Record<string, unknown>) => r),
  anonymizeResources: vi.fn((rs: unknown[]) => rs),
  anonymizeSearchMatches: vi.fn((rs: unknown[]) => rs),
  getAnonymizationDirectory: vi.fn().mockResolvedValue(null),
  resolveResourceIdsByDisplayedEids: vi.fn().mockResolvedValue(new Map()),
}));

import { resourceRouter } from "../router/resource.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(resourceRouter);

function createManagerCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "manager@example.com", name: "Manager", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "mgr_1",
      systemRole: SystemRole.MANAGER,
      permissionOverrides: null,
    },
  });
}

function createProtectedCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "user@example.com", name: "User", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_1",
      systemRole: SystemRole.USER,
      permissionOverrides: null,
    },
  });
}

const sampleResource = {
  id: "res_1",
  eid: "E-001",
  displayName: "Alice",
  email: "alice@example.com",
  chapter: "CGI",
  lcrCents: 5000,
  ucrCents: 9000,
  currency: "EUR",
  chargeabilityTarget: 80,
  availability: {
    monday: 8,
    tuesday: 8,
    wednesday: 8,
    thursday: 8,
    friday: 8,
  },
  skills: [],
  dynamicFields: {},
  blueprintId: null,
  blueprint: null,
  isActive: true,
  createdAt: new Date("2026-03-01"),
  updatedAt: new Date("2026-03-01"),
  roleId: null,
  portfolioUrl: null,
  postalCode: null,
  federalState: null,
  valueScore: null,
  valueScoreBreakdown: null,
  valueScoreUpdatedAt: null,
  userId: null,
  resourceRoles: [],
  areaRole: null,
  countryId: null,
  metroCityId: null,
  orgUnitId: null,
  managementLevelGroupId: null,
  managementLevelId: null,
  resourceType: null,
  chgResponsibility: null,
  rolledOff: false,
  departed: false,
  enterpriseId: null,
  clientUnitId: null,
  fte: 1,
};

describe("resource router CRUD", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  // ─── listStaff ────────────────────────────────────────────────────────────

  describe("listStaff", () => {
    it("returns paginated results with total count for staff callers", async () => {
      const db = {
        resource: {
          findMany: vi.fn().mockResolvedValue([sampleResource]),
          count: vi.fn().mockResolvedValue(1),
        },
      };

      const caller = createManagerCaller(db);
      const result = await caller.listStaff({ limit: 50 });

      expect(result.resources).toHaveLength(1);
      expect(result.resources[0]?.displayName).toBe("Alice");
      expect(db.resource.findMany).toHaveBeenCalled();
    });

    it("applies search filter for staff callers", async () => {
      const db = {
        resource: {
          findMany: vi.fn().mockResolvedValue([]),
          count: vi.fn().mockResolvedValue(0),
        },
      };

      const caller = createManagerCaller(db);
      await caller.listStaff({ search: "Alice", limit: 50 });

      expect(db.resource.findMany).toHaveBeenCalled();
    });
  });

  // ─── getById ──────────────────────────────────────────────────────────────

  describe("getById", () => {
    it("returns correct resource", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
          findUnique: vi.fn().mockResolvedValue({ ...sampleResource, userId: "user_1" }),
          findMany: vi.fn().mockResolvedValue([]),
        },
        systemSettings: {
          findUnique: vi.fn().mockResolvedValue(null),
        },
      };

      const caller = createProtectedCaller(db);
      const result = await caller.getById({ id: "res_1" });

      expect(result.id).toBe("res_1");
      expect(result.displayName).toBe("Alice");
    });

    it("throws NOT_FOUND when resource does not exist", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
          findUnique: vi.fn().mockResolvedValue(null),
          findMany: vi.fn().mockResolvedValue([]),
        },
        systemSettings: {
          findUnique: vi.fn().mockResolvedValue(null),
        },
      };

      const caller = createProtectedCaller(db);
      await expect(caller.getById({ id: "missing" })).rejects.toThrow(
        expect.objectContaining({ code: "NOT_FOUND" }),
      );
    });

    it("sets isOwnedByCurrentUser when userId matches", async () => {
      const ownedResource = { ...sampleResource, userId: "user_1" };
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
          findUnique: vi.fn().mockResolvedValue(ownedResource),
          findMany: vi.fn().mockResolvedValue([]),
        },
        systemSettings: {
          findUnique: vi.fn().mockResolvedValue(null),
        },
      };

      const caller = createProtectedCaller(db);
      const result = await caller.getById({ id: "res_1" });

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

    it("rejects foreign resources for regular users", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_own" }),
          findUnique: vi.fn().mockResolvedValue(sampleResource),
          findMany: vi.fn().mockResolvedValue([]),
        },
      };

      const caller = createProtectedCaller(db);
      await expect(caller.getById({ id: "res_1" })).rejects.toThrow(
        expect.objectContaining({ code: "FORBIDDEN" }),
      );
    });
  });

  // ─── create ───────────────────────────────────────────────────────────────

  describe("create", () => {
    it("creates a resource and returns it", async () => {
      const created = { ...sampleResource, id: "res_new", resourceRoles: [] };
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue(null),
          create: vi.fn().mockResolvedValue(created),
        },
        auditLog: { create: vi.fn().mockResolvedValue({}) },
      };

      const caller = createManagerCaller(db);
      const result = await caller.create({
        eid: "E-NEW",
        displayName: "New Resource",
        email: "new@example.com",
        lcrCents: 4000,
        ucrCents: 8000,
      });

      expect(result.id).toBe("res_new");
      expect(db.resource.create).toHaveBeenCalled();
      expect(db.auditLog.create).toHaveBeenCalled();
    });

    it("throws CONFLICT on duplicate eid or email", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue(sampleResource),
          create: vi.fn(),
        },
        auditLog: { create: vi.fn() },
      };

      const caller = createManagerCaller(db);
      await expect(
        caller.create({
          eid: "E-001",
          displayName: "Duplicate",
          email: "alice@example.com",
          lcrCents: 5000,
          ucrCents: 9000,
        }),
      ).rejects.toThrow(
        expect.objectContaining({ code: "CONFLICT" }),
      );
    });

    it("blocks USER role from creating resources", async () => {
      const db = {
        resource: { findFirst: vi.fn(), create: vi.fn() },
        auditLog: { create: vi.fn() },
      };

      const caller = createProtectedCaller(db);
      await expect(
        caller.create({
          eid: "E-002",
          displayName: "Blocked",
          email: "blocked@example.com",
          lcrCents: 4000,
          ucrCents: 8000,
        }),
      ).rejects.toThrow(
        expect.objectContaining({ code: "FORBIDDEN" }),
      );
    });
  });

  // ─── update ───────────────────────────────────────────────────────────────

  describe("update", () => {
    it("updates resource fields", async () => {
      const updated = { ...sampleResource, displayName: "Alice Updated" };
      const db = {
        resource: {
          findUnique: vi.fn().mockResolvedValue(sampleResource),
          update: vi.fn().mockResolvedValue(updated),
        },
        resourceRole: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) },
        auditLog: { create: vi.fn().mockResolvedValue({}) },
      };

      const caller = createManagerCaller(db);
      const result = await caller.update({
        id: "res_1",
        data: { displayName: "Alice Updated" },
      });

      expect(result.displayName).toBe("Alice Updated");
      expect(db.resource.update).toHaveBeenCalledWith(
        expect.objectContaining({ where: { id: "res_1" } }),
      );
    });

    it("throws NOT_FOUND when resource does not exist", async () => {
      const db = {
        resource: {
          findUnique: vi.fn().mockResolvedValue(null),
          update: vi.fn(),
        },
        auditLog: { create: vi.fn() },
      };

      const caller = createManagerCaller(db);
      await expect(
        caller.update({ id: "missing", data: { displayName: "X" } }),
      ).rejects.toThrow(
        expect.objectContaining({ code: "NOT_FOUND" }),
      );
    });
  });

  // ─── deactivate ───────────────────────────────────────────────────────────

  describe("deactivate", () => {
    it("sets isActive to false", async () => {
      const deactivated = { ...sampleResource, isActive: false };
      const db = {
        resource: {
          update: vi.fn().mockResolvedValue(deactivated),
        },
        auditLog: { create: vi.fn().mockResolvedValue({}) },
      };

      const caller = createManagerCaller(db);
      const result = await caller.deactivate({ id: "res_1" });

      expect(result.isActive).toBe(false);
      expect(db.resource.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: "res_1" },
          data: { isActive: false },
        }),
      );
    });
  });

  // ─── getHoverCard ─────────────────────────────────────────────────────────

  describe("getHoverCard", () => {
    it("returns expected shape with key fields", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
          findUnique: vi.fn().mockResolvedValue({
            id: "res_1",
            displayName: "Alice",
            eid: "E-001",
            email: "alice@example.com",
            chapter: "CGI",
            lcrCents: 5000,
            ucrCents: 9000,
            currency: "EUR",
            chargeabilityTarget: 80,
            skills: [],
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            isActive: true,
            areaRole: null,
            country: null,
            managementLevel: null,
            resourceType: null,
          }),
        },
        systemSettings: {
          findUnique: vi.fn().mockResolvedValue(null),
        },
      };

      const caller = createProtectedCaller(db);
      const result = await caller.getHoverCard({ id: "res_1" });

      expect(result).toMatchObject({
        id: "res_1",
        displayName: "Alice",
        chapter: "CGI",
        lcrCents: 5000,
        isActive: true,
      });
    });

    it("throws NOT_FOUND for missing resource", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
          findUnique: vi.fn().mockResolvedValue(null),
        },
        systemSettings: { findUnique: vi.fn().mockResolvedValue(null) },
      };

      const caller = createProtectedCaller(db);
      await expect(caller.getHoverCard({ id: "missing" })).rejects.toThrow(
        expect.objectContaining({ code: "NOT_FOUND" }),
      );
    });

    it("rejects foreign hover-card access for regular users", async () => {
      const db = {
        resource: {
          findFirst: vi.fn().mockResolvedValue({ id: "res_own" }),
          findUnique: vi.fn().mockResolvedValue({
            id: "res_1",
            displayName: "Alice",
            eid: "E-001",
            email: "alice@example.com",
            chapter: "CGI",
            lcrCents: 5000,
            ucrCents: 9000,
            currency: "EUR",
            chargeabilityTarget: 80,
            skills: [],
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            isActive: true,
            areaRole: null,
            country: null,
            managementLevel: null,
            resourceType: null,
          }),
        },
      };

      const caller = createProtectedCaller(db);
      await expect(caller.getHoverCard({ id: "res_1" })).rejects.toThrow(
        expect.objectContaining({ code: "FORBIDDEN" }),
      );
    });
  });

  // ─── importSkillMatrix ────────────────────────────────────────────────────

  describe("importSkillMatrix", () => {
    it("imports skills for the current user resource", async () => {
      const updatedResource = {
        ...sampleResource,
        skills: [{ skill: "Maya", proficiency: 4 }],
        skillMatrixUpdatedAt: new Date(),
      };
      const db = {
        user: {
          findUnique: vi.fn().mockResolvedValue({
            id: "user_1",
            email: "user@example.com",
            resource: { id: "res_1" },
          }),
        },
        resource: {
          update: vi.fn().mockResolvedValue(updatedResource),
        },
      };

      const caller = createProtectedCaller(db);
      const result = await caller.importSkillMatrix({
        skills: [{ skill: "Maya", proficiency: 4 }],
      });

      expect(db.resource.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: "res_1" },
        }),
      );
    });

    it("throws NOT_FOUND when user has no linked resource", async () => {
      const db = {
        user: {
          findUnique: vi.fn().mockResolvedValue({
            id: "user_1",
            email: "user@example.com",
            resource: null,
          }),
        },
      };

      const caller = createProtectedCaller(db);
      await expect(
        caller.importSkillMatrix({
          skills: [{ skill: "Nuke", proficiency: 3 }],
        }),
      ).rejects.toThrow("No resource linked to your account");
    });
  });
});
