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

// Mock resource read shared
const resolveResourceIdentifierSnapshotMock = vi.fn();
const readResourceByIdentifierDetailSnapshotMock = vi.fn();

vi.mock("../router/resource-read-shared.js", () => ({
  resolveResourceIdentifierSnapshot: (...args: unknown[]) =>
    resolveResourceIdentifierSnapshotMock(...args),
  readResourceByIdentifierDetailSnapshot: (...args: unknown[]) =>
    readResourceByIdentifierDetailSnapshotMock(...args),
  ResourceDirectoryQuerySchema: {},
  ResourceListQuerySchema: {},
}));

// Mock resource read models
vi.mock("../router/resource-read-models.js", () => ({
  mapResourceDetail: (r: unknown) => ({ ...(r as Record<string, unknown>), _mapped: true }),
}));

// Mock anonymization
const anonymizeResourceMock = vi.fn().mockImplementation((r: unknown) => r);
const getAnonymizationDirectoryMock = vi.fn().mockResolvedValue(null);

vi.mock("../lib/anonymization.js", () => ({
  anonymizeResource: (...args: unknown[]) => anonymizeResourceMock(...args),
  getAnonymizationDirectory: (...args: unknown[]) => getAnonymizationDirectoryMock(...args),
}));

// Mock resource access
vi.mock("../lib/resource-access.js", () => ({
  assertCanReadResource: vi.fn().mockResolvedValue(undefined),
}));

// Mock selects
vi.mock("../db/selects.js", () => ({
  ROLE_BRIEF_SELECT: { id: true, name: true },
}));

import { createCallerFactory, createTRPCRouter } from "../trpc.js";
import { resourceIdentifierReadProcedures } from "../router/resource-identifier-read.js";

const router = createTRPCRouter(resourceIdentifierReadProcedures);
const createCaller = createCallerFactory(router);

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.MANAGER, permissionOverrides: null },
  });
}

const mockResource = {
  id: "resource_1",
  displayName: "Test User",
  eid: "E001",
  email: "test@example.com",
  chapter: "Engineering",
  lcrCents: 5000,
  ucrCents: 7500,
  currency: "EUR",
  chargeabilityTarget: 80,
  skills: [],
  availability: { mon: 8, tue: 8, wed: 8, thu: 8, fri: 8 },
  isActive: true,
  resourceType: "INTERNAL",
  areaRole: { id: "role_1", name: "Developer" },
  country: { name: "Germany", code: "DE" },
  managementLevel: { name: "Senior" },
  userId: "user_1",
  blueprint: null,
  resourceRoles: [],
};

beforeEach(() => {
  vi.clearAllMocks();
  anonymizeResourceMock.mockImplementation((r: unknown) => r);
  getAnonymizationDirectoryMock.mockResolvedValue(null);
});

// ─── resolveByIdentifier ──────────────────────────────────────────────────────

describe("resourceIdentifierRead.resolveByIdentifier", () => {
  it("returns resource when resolved successfully", async () => {
    resolveResourceIdentifierSnapshotMock.mockResolvedValue(mockResource);
    const caller = createProtectedCaller({});

    const result = await caller.resolveByIdentifier({ identifier: "E001" });

    expect(result).toEqual(mockResource);
  });

  it("throws NOT_FOUND when result has 'error' key", async () => {
    resolveResourceIdentifierSnapshotMock.mockResolvedValue({ error: "Resource not found: E999" });
    const caller = createProtectedCaller({});

    await expect(caller.resolveByIdentifier({ identifier: "E999" })).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "Resource not found",
    });
  });

  it("passes identifier to resolveResourceIdentifierSnapshot", async () => {
    resolveResourceIdentifierSnapshotMock.mockResolvedValue(mockResource);
    const caller = createProtectedCaller({});

    await caller.resolveByIdentifier({ identifier: "Test User" });

    expect(resolveResourceIdentifierSnapshotMock).toHaveBeenCalledWith(
      expect.objectContaining({ dbUser: expect.any(Object) }),
      "Test User",
      expect.any(String),
    );
  });
});

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

describe("resourceIdentifierRead.getHoverCard", () => {
  it("returns selected fields for a found resource", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(mockResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getHoverCard({ id: "resource_1" });

    expect(result).toMatchObject({
      id: mockResource.id,
      displayName: mockResource.displayName,
      eid: mockResource.eid,
      chapter: mockResource.chapter,
      lcrCents: mockResource.lcrCents,
      ucrCents: mockResource.ucrCents,
      currency: mockResource.currency,
      chargeabilityTarget: mockResource.chargeabilityTarget,
      isActive: mockResource.isActive,
      resourceType: mockResource.resourceType,
      areaRole: mockResource.areaRole,
      country: mockResource.country,
      managementLevel: mockResource.managementLevel,
    });
  });

  it("throws NOT_FOUND when findUnique returns null", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };
    const caller = createProtectedCaller(db);

    await expect(caller.getHoverCard({ id: "nonexistent" })).rejects.toMatchObject({
      code: "NOT_FOUND",
    });
  });

  it("anonymizes the resource before returning", async () => {
    const anonymized = { ...mockResource, displayName: "Anonymous", eid: "ANON" };
    anonymizeResourceMock.mockReturnValue(anonymized);

    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(mockResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getHoverCard({ id: "resource_1" });

    expect(anonymizeResourceMock).toHaveBeenCalledWith(mockResource, null);
    expect(result.displayName).toBe("Anonymous");
    expect(result.eid).toBe("ANON");
  });
});

// ─── getByIdentifierDetail ────────────────────────────────────────────────────

describe("resourceIdentifierRead.getByIdentifierDetail", () => {
  it("maps resource detail when found (adds _mapped: true)", async () => {
    readResourceByIdentifierDetailSnapshotMock.mockResolvedValue(mockResource);
    const caller = createProtectedCaller({});

    const result = await caller.getByIdentifierDetail({ identifier: "E001" });

    expect(result).toMatchObject({ ...mockResource, _mapped: true });
  });

  it("returns error result directly when has 'error' key", async () => {
    const errorResult = { error: "Resource not found: E999", suggestions: [] };
    readResourceByIdentifierDetailSnapshotMock.mockResolvedValue(errorResult);
    const caller = createProtectedCaller({});

    const result = await caller.getByIdentifierDetail({ identifier: "E999" });

    expect(result).toEqual(errorResult);
  });
});

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

describe("resourceIdentifierRead.getById", () => {
  it("returns resource with isOwnedByCurrentUser=true when userId matches", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(mockResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getById({ id: "resource_1" });

    expect(result).toMatchObject({ ...mockResource, isOwnedByCurrentUser: true });
  });

  it("returns isOwnedByCurrentUser=false when userId doesn't match", async () => {
    const otherResource = { ...mockResource, userId: "other_user" };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(otherResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getById({ id: "resource_1" });

    expect(result).toMatchObject({ isOwnedByCurrentUser: false });
  });

  it("throws NOT_FOUND when not found", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };
    const caller = createProtectedCaller(db);

    await expect(caller.getById({ id: "nonexistent" })).rejects.toMatchObject({
      code: "NOT_FOUND",
    });
  });
});

// ─── getByEid ─────────────────────────────────────────────────────────────────

describe("resourceIdentifierRead.getByEid", () => {
  it("returns resource when found by eid", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(mockResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getByEid({ eid: "E001" });

    expect(result).toEqual(mockResource);
    expect(db.resource.findUnique).toHaveBeenCalledWith({ where: { eid: "E001" } });
  });

  it("falls back to anonymization directory alias when eid not found directly", async () => {
    const aliasMap = new Map([["e001-alias", "resource_1"]]);
    const directory = { byAliasEid: aliasMap, byResourceId: new Map() };
    getAnonymizationDirectoryMock.mockResolvedValue(directory);

    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(mockResource),
      },
    };
    const caller = createProtectedCaller(db);

    const result = await caller.getByEid({ eid: "E001-Alias" });

    expect(result).toEqual(mockResource);
    expect(db.resource.findUnique).toHaveBeenNthCalledWith(2, { where: { id: "resource_1" } });
  });

  it("throws NOT_FOUND when both lookups fail", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };
    const caller = createProtectedCaller(db);

    await expect(caller.getByEid({ eid: "UNKNOWN" })).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "Resource not found",
    });
  });
});

// ─── getByIdentifier ──────────────────────────────────────────────────────────

describe("resourceIdentifierRead.getByIdentifier", () => {
  it("delegates to resolveResourceIdentifierSnapshot", async () => {
    resolveResourceIdentifierSnapshotMock.mockResolvedValue(mockResource);
    const caller = createProtectedCaller({});

    await caller.getByIdentifier({ identifier: "E001" });

    expect(resolveResourceIdentifierSnapshotMock).toHaveBeenCalledWith(
      expect.objectContaining({ dbUser: expect.any(Object) }),
      "E001",
    );
  });

  it("returns result directly", async () => {
    const errorResult = { error: "Resource not found: E999" };
    resolveResourceIdentifierSnapshotMock.mockResolvedValue(errorResult);
    const caller = createProtectedCaller({});

    const result = await caller.getByIdentifier({ identifier: "E999" });

    expect(result).toEqual(errorResult);
  });
});
