import { describe, expect, it, vi } from "vitest";
import { readProjectScenarioBaseline } from "../router/scenario-baseline.js";

vi.mock("../lib/resource-capacity.js", () => ({
  calculateEffectiveAvailableHours: vi.fn().mockReturnValue(160),
  calculateEffectiveBookedHours: vi.fn().mockReturnValue(0),
  loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()),
}));

const DEFAULT_AVAILABILITY = {
  monday: 8,
  tuesday: 8,
  wednesday: 8,
  thursday: 8,
  friday: 8,
  saturday: 0,
  sunday: 0,
};

const baseProject = {
  id: "project_1",
  name: "Test Project",
  shortCode: "TP-001",
  startDate: new Date("2026-04-01T00:00:00.000Z"),
  endDate: new Date("2026-04-30T00:00:00.000Z"),
  budgetCents: 100_000_00,
  orderType: "CHARGEABLE",
};

function makeResource(overrides: Record<string, unknown> = {}) {
  return {
    id: "resource_1",
    displayName: "Alice Tester",
    eid: "EMP-001",
    lcrCents: 10000,
    availability: DEFAULT_AVAILABILITY,
    countryId: "DE",
    federalState: null,
    metroCityId: null,
    country: { code: "DE" },
    metroCity: null,
    chargeabilityTarget: 80,
    skills: [],
    ...overrides,
  };
}

function makeAssignment(overrides: Record<string, unknown> = {}) {
  return {
    id: "assignment_1",
    resourceId: "resource_1",
    projectId: "project_1",
    hoursPerDay: 8,
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-10T00:00:00.000Z"),
    status: "ACTIVE",
    roleId: "role_1",
    role: "Developer",
    resource: makeResource(),
    roleEntity: { id: "role_1", name: "Developer", color: "#0000ff" },
    ...overrides,
  };
}

function makeDemand(overrides: Record<string, unknown> = {}) {
  return {
    id: "demand_1",
    roleId: "role_1",
    role: "Designer",
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-30T00:00:00.000Z"),
    hoursPerDay: 8,
    headcount: 2,
    status: "OPEN",
    roleEntity: { id: "role_1", name: "Designer", color: "#ff0000" },
    ...overrides,
  };
}

const UNSET = Symbol("unset");

function makeDb(overrides: {
  project?: unknown;
  assignments?: unknown[];
  demands?: unknown[];
} = {}) {
  const projectValue = "project" in overrides ? overrides.project : baseProject;
  return {
    project: {
      findUnique: vi.fn().mockResolvedValue(projectValue),
    },
    assignment: {
      findMany: vi.fn().mockResolvedValue(overrides.assignments ?? []),
    },
    demandRequirement: {
      findMany: vi.fn().mockResolvedValue(overrides.demands ?? []),
    },
  };
}

void UNSET;

describe("readProjectScenarioBaseline", () => {
  it("throws NOT_FOUND when project does not exist", async () => {
    const db = makeDb({ project: null });

    await expect(
      readProjectScenarioBaseline(db as never, "project_missing"),
    ).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "Project not found",
    });
  });

  it("returns zeroed totals when no assignments and no demands exist", async () => {
    const db = makeDb({ assignments: [], demands: [] });

    const result = await readProjectScenarioBaseline(db as never, "project_1");

    expect(result.totalCostCents).toBe(0);
    expect(result.totalHours).toBe(0);
    expect(result.assignments).toEqual([]);
    expect(result.demands).toEqual([]);
  });

  it("calculates assignment costCents from lcrCents and effective hours", async () => {
    // calculateEffectiveBookedHours is mocked to return 0, but calculateAllocation
    // is not mocked — the assignment has a resourceId, so it will use
    // calculateEffectiveBookedHours. Re-mock it to return a known value.
    const { calculateEffectiveBookedHours } = await import("../lib/resource-capacity.js");
    vi.mocked(calculateEffectiveBookedHours).mockReturnValue(20);

    const assignment = makeAssignment({ resource: makeResource({ lcrCents: 5000 }) });
    const db = makeDb({ assignments: [assignment], demands: [] });

    const result = await readProjectScenarioBaseline(db as never, "project_1");

    expect(result.assignments).toHaveLength(1);
    expect(result.assignments[0].costCents).toBeGreaterThan(0);
    // 20 hours * 5000 cents/hour = 100_000
    expect(result.assignments[0].costCents).toBe(100_000);

    // Reset mock to default
    vi.mocked(calculateEffectiveBookedHours).mockReturnValue(0);
  });

  it("filters out CANCELLED assignments", async () => {
    const cancelledAssignment = makeAssignment({ status: "CANCELLED" });
    // The WHERE clause in the real DB call filters CANCELLED; we simulate
    // this by having the mock return no assignments (mimicking the db filter).
    const db = makeDb({ assignments: [], demands: [] });
    // Verify the findMany was called with the correct filter
    db.assignment.findMany.mockResolvedValue([]);

    const result = await readProjectScenarioBaseline(db as never, "project_1");

    expect(db.assignment.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          status: { not: "CANCELLED" },
        }),
      }),
    );
    expect(result.assignments).toHaveLength(0);

    // Also test that if somehow a cancelled assignment slips through, the function
    // still processes it (the filter is at DB level). Validate the WHERE contract.
    void cancelledAssignment; // used above for context
  });

  it("maps demand requirements with headcount and roleName", async () => {
    const demand = makeDemand({
      headcount: 3,
      roleEntity: { id: "role_2", name: "QA Engineer", color: "#00ff00" },
    });
    const db = makeDb({ assignments: [], demands: [demand] });

    const result = await readProjectScenarioBaseline(db as never, "project_1");

    expect(result.demands).toHaveLength(1);
    expect(result.demands[0].roleName).toBe("QA Engineer");
    expect(result.demands[0].headcount).toBe(3);
  });

  it("totalCostCents is the sum of all assignment costCents", async () => {
    const { calculateEffectiveBookedHours } = await import("../lib/resource-capacity.js");
    // Return 10 hours for each call
    vi.mocked(calculateEffectiveBookedHours).mockReturnValue(10);

    const assignment1 = makeAssignment({
      id: "assignment_1",
      resource: makeResource({ id: "resource_1", lcrCents: 3000 }),
      resourceId: "resource_1",
    });
    const assignment2 = makeAssignment({
      id: "assignment_2",
      resource: makeResource({ id: "resource_2", lcrCents: 4000 }),
      resourceId: "resource_2",
      roleEntity: { id: "role_1", name: "Developer", color: "#0000ff" },
    });
    const db = makeDb({ assignments: [assignment1, assignment2], demands: [] });

    const result = await readProjectScenarioBaseline(db as never, "project_1");

    // 10 hours * 3000 = 30_000 + 10 hours * 4000 = 40_000 → total = 70_000
    expect(result.assignments).toHaveLength(2);
    const sumOfIndividual = result.assignments.reduce((sum, a) => sum + a.costCents, 0);
    expect(result.totalCostCents).toBe(sumOfIndividual);
    expect(result.totalCostCents).toBe(70_000);

    // Reset mock
    vi.mocked(calculateEffectiveBookedHours).mockReturnValue(0);
  });
});
