import { beforeEach, describe, expect, it, vi } from "vitest";
import { applyProjectScenario } from "../router/scenario-apply.js";

vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));

function makeDb(overrides: {
  projectFindUnique?: ReturnType<typeof vi.fn>;
  assignmentUpdate?: ReturnType<typeof vi.fn>;
  assignmentCreate?: ReturnType<typeof vi.fn>;
  resourceFindUnique?: ReturnType<typeof vi.fn>;
}) {
  return {
    project: {
      findUnique: overrides.projectFindUnique ?? vi.fn().mockResolvedValue({ id: "project_1", name: "Test Project" }),
    },
    assignment: {
      update: overrides.assignmentUpdate ?? vi.fn().mockResolvedValue({}),
      create: overrides.assignmentCreate ?? vi.fn().mockResolvedValue({ id: "new_assignment_1" }),
    },
    resource: {
      findUnique: overrides.resourceFindUnique ?? vi.fn().mockResolvedValue({ lcrCents: 100 }),
    },
  } as never;
}

const baseChange = {
  startDate: new Date("2026-04-01T00:00:00.000Z"),
  endDate: new Date("2026-04-30T00:00:00.000Z"),
  hoursPerDay: 8,
};

describe("applyProjectScenario", () => {
  let assignmentUpdate: ReturnType<typeof vi.fn>;
  let assignmentCreate: ReturnType<typeof vi.fn>;
  let resourceFindUnique: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    assignmentUpdate = vi.fn().mockResolvedValue({});
    assignmentCreate = vi.fn().mockResolvedValue({ id: "new_assignment_1" });
    resourceFindUnique = vi.fn().mockResolvedValue({ lcrCents: 100 });
  });

  it("throws NOT_FOUND when the project does not exist", async () => {
    const db = makeDb({
      projectFindUnique: vi.fn().mockResolvedValue(null),
    });

    await expect(
      applyProjectScenario(db, { projectId: "missing_project", changes: [] }),
    ).rejects.toMatchObject({ code: "NOT_FOUND", message: "Project not found" });
  });

  it("cancels an assignment when remove:true is supplied", async () => {
    const db = makeDb({ assignmentUpdate });

    const result = await applyProjectScenario(db, {
      projectId: "project_1",
      changes: [{ ...baseChange, assignmentId: "assignment_1", remove: true }],
    });

    expect(assignmentUpdate).toHaveBeenCalledWith({
      where: { id: "assignment_1" },
      data: { status: "CANCELLED" },
    });
    // Cancels continue early without pushing into `created`, so appliedCount is 0
    expect(result.appliedCount).toBe(0);
  });

  it("updates dates and hours when assignmentId is present without remove", async () => {
    const db = makeDb({ assignmentUpdate });

    const result = await applyProjectScenario(db, {
      projectId: "project_1",
      changes: [{ ...baseChange, assignmentId: "assignment_1" }],
    });

    expect(assignmentUpdate).toHaveBeenCalledWith({
      where: { id: "assignment_1" },
      data: {
        startDate: baseChange.startDate,
        endDate: baseChange.endDate,
        hoursPerDay: baseChange.hoursPerDay,
      },
    });
    expect(result.appliedCount).toBe(1);
  });

  it("skips a change that has neither assignmentId nor resourceId", async () => {
    const db = makeDb({ assignmentCreate, resourceFindUnique });

    const result = await applyProjectScenario(db, {
      projectId: "project_1",
      changes: [{ ...baseChange }],
    });

    expect(assignmentCreate).not.toHaveBeenCalled();
    expect(result.appliedCount).toBe(0);
  });

  it("creates a new assignment when only resourceId is present and computes dailyCostCents", async () => {
    resourceFindUnique = vi.fn().mockResolvedValue({ lcrCents: 200 });
    const db = makeDb({ assignmentCreate, resourceFindUnique });

    const result = await applyProjectScenario(db, {
      projectId: "project_1",
      changes: [{ ...baseChange, resourceId: "resource_1", hoursPerDay: 6 }],
    });

    expect(resourceFindUnique).toHaveBeenCalledWith({
      where: { id: "resource_1" },
      select: { lcrCents: true },
    });

    // dailyCostCents = Math.round(lcrCents * hoursPerDay) = Math.round(200 * 6) = 1200
    expect(assignmentCreate).toHaveBeenCalledWith({
      data: expect.objectContaining({
        projectId: "project_1",
        resourceId: "resource_1",
        hoursPerDay: 6,
        dailyCostCents: 1200,
        status: "PROPOSED",
      }),
    });
    expect(result.appliedCount).toBe(1);
  });

  it("counts correctly across multiple mixed changes", async () => {
    assignmentCreate = vi.fn()
      .mockResolvedValueOnce({ id: "new_1" })
      .mockResolvedValueOnce({ id: "new_2" });
    assignmentUpdate = vi.fn().mockResolvedValue({});

    const db = makeDb({ assignmentCreate, assignmentUpdate, resourceFindUnique });

    const result = await applyProjectScenario(db, {
      projectId: "project_1",
      changes: [
        // create 1
        { ...baseChange, resourceId: "resource_1" },
        // create 2
        { ...baseChange, resourceId: "resource_2" },
        // cancel
        { ...baseChange, assignmentId: "assignment_1", remove: true },
      ],
    });

    expect(assignmentCreate).toHaveBeenCalledTimes(2);
    // Cancels continue early without pushing into `created`.
    // appliedCount = 2 creates + 0 cancel = 2.
    expect(result.appliedCount).toBe(2);
  });
});
