import {
  AllocationStatus,
  EstimateExportFormat,
  EstimateStatus,
  EstimateVersionStatus,
} from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import {
  approveEstimateVersion,
  createEstimate,
  createEstimateExport,
  createEstimatePlanningHandoff,
  createEstimateRevision,
  listEstimates,
  submitEstimateVersion,
} from "../index.js";

describe("estimate use-cases", () => {
  it("creates an estimate with an initial version payload", async () => {
    const create = vi.fn().mockResolvedValue({ id: "est_1", versions: [{ id: "ver_1" }] });
    const db = {
      estimate: {
        create,
      },
    };

    const result = await createEstimate(db as never, {
      name: "CGI Estimate",
      baseCurrency: "EUR",
      status: EstimateStatus.DRAFT,
      assumptions: [
        {
          category: "commercial",
          key: "pricingStructure",
          label: "Pricing Structure",
          valueType: "string",
          value: "fixed-bid",
          sortOrder: 0,
        },
      ],
      scopeItems: [],
      demandLines: [],
      resourceSnapshots: [],
      metrics: [],
    });

    expect(result).toEqual({ id: "est_1", versions: [{ id: "ver_1" }] });
    expect(create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          name: "CGI Estimate",
          latestVersionNumber: 1,
          versions: {
            create: expect.objectContaining({
              versionNumber: 1,
              assumptions: {
                create: [
                  expect.objectContaining({
                    key: "pricingStructure",
                    value: "fixed-bid",
                  }),
                ],
              },
            }),
          },
        }),
      }),
    );
  });

  it("lists estimates with optional query filters", async () => {
    const findMany = vi.fn().mockResolvedValue([]);
    const db = {
      estimate: {
        findMany,
      },
    };

    await listEstimates(db as never, {
      status: EstimateStatus.DRAFT,
      query: "cgi",
    });

    expect(findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          status: EstimateStatus.DRAFT,
          OR: expect.any(Array),
        }),
      }),
    );
  });

  it("submits the working estimate version and moves the estimate into review", async () => {
    const estimateId = "est_1";
    const workingVersionId = "ver_working";
    const existing = {
      id: estimateId,
      status: EstimateStatus.DRAFT,
      latestVersionNumber: 1,
      versions: [
        {
          id: workingVersionId,
          versionNumber: 1,
          status: EstimateVersionStatus.WORKING,
          lockedAt: null,
          assumptions: [],
          scopeItems: [],
          demandLines: [],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
          projectSnapshot: {},
        },
        {
          id: "ver_old_submit",
          versionNumber: 0,
          status: EstimateVersionStatus.SUBMITTED,
          lockedAt: new Date("2026-03-01"),
          assumptions: [],
          scopeItems: [],
          demandLines: [],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
          projectSnapshot: {},
        },
      ],
    };
    const refreshed = {
      ...existing,
      status: EstimateStatus.IN_REVIEW,
      versions: [
        {
          ...existing.versions[0],
          status: EstimateVersionStatus.SUBMITTED,
          lockedAt: new Date("2026-03-13"),
        },
        {
          ...existing.versions[1],
          status: EstimateVersionStatus.SUPERSEDED,
        },
      ],
    };
    const findUnique = vi.fn().mockResolvedValueOnce(existing).mockResolvedValueOnce(refreshed);
    const updateMany = vi.fn().mockResolvedValue({ count: 1 });
    const updateVersion = vi.fn().mockResolvedValue({});
    const updateEstimate = vi.fn().mockResolvedValue({});
    const db = {
      estimate: {
        findUnique,
        update: updateEstimate,
      },
      estimateVersion: {
        updateMany,
        update: updateVersion,
      },
      $transaction: vi.fn(async (callback) =>
        callback({
          estimateVersion: {
            updateMany,
            update: updateVersion,
          },
          estimate: {
            update: updateEstimate,
          },
        }),
      ),
    };

    const result = await submitEstimateVersion(db as never, { estimateId });

    expect(result.status).toBe(EstimateStatus.IN_REVIEW);
    expect(updateMany).toHaveBeenCalledWith({
      where: { id: { in: ["ver_old_submit"] } },
      data: { status: EstimateVersionStatus.SUPERSEDED },
    });
    expect(updateVersion).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: workingVersionId },
        data: expect.objectContaining({
          status: EstimateVersionStatus.SUBMITTED,
          lockedAt: expect.any(Date),
        }),
      }),
    );
    expect(updateEstimate).toHaveBeenCalledWith({
      where: { id: estimateId },
      data: { status: EstimateStatus.IN_REVIEW },
    });
  });

  it("creates a new working revision cloned from the selected locked version", async () => {
    const estimateId = "est_2";
    const sourceScopeId = "scope_old";
    const existing = {
      id: estimateId,
      status: EstimateStatus.APPROVED,
      latestVersionNumber: 2,
      versions: [
        {
          id: "ver_approved",
          versionNumber: 2,
          label: "Approved",
          status: EstimateVersionStatus.APPROVED,
          lockedAt: new Date("2026-03-13"),
          notes: "approved baseline",
          projectSnapshot: { shortCode: "PRJ" },
          assumptions: [
            {
              id: "assumption_1",
              category: "commercial",
              key: "pricing",
              label: "Pricing",
              valueType: "string",
              value: "fixed",
              sortOrder: 0,
              notes: null,
            },
          ],
          scopeItems: [
            {
              id: sourceScopeId,
              sequenceNo: 1,
              scopeType: "SHOT",
              packageCode: null,
              name: "Shot 010",
              description: null,
              scene: null,
              page: null,
              location: null,
              assumptionCategory: null,
              technicalSpec: {},
              frameCount: null,
              itemCount: null,
              unitMode: null,
              internalComments: null,
              externalComments: null,
              sortOrder: 0,
              metadata: {},
            },
          ],
          demandLines: [
            {
              id: "line_1",
              scopeItemId: sourceScopeId,
              roleId: null,
              resourceId: null,
              lineType: "LABOR",
              name: "Comp",
              chapter: null,
              hours: 40,
              days: null,
              fte: null,
              rateSource: null,
              costRateCents: 5000,
              billRateCents: 8000,
              currency: "EUR",
              costTotalCents: 200000,
              priceTotalCents: 320000,
              monthlySpread: {},
              staffingAttributes: {},
              metadata: {},
            },
          ],
          resourceSnapshots: [
            {
              id: "snap_1",
              resourceId: null,
              sourceEid: null,
              displayName: "Alex Artist",
              chapter: null,
              roleId: null,
              currency: "EUR",
              lcrCents: 5000,
              ucrCents: 8000,
              fte: null,
              location: null,
              country: null,
              level: null,
              workType: null,
              attributes: {},
            },
          ],
          metrics: [
            {
              id: "metric_1",
              key: "total_hours",
              label: "Total Hours",
              metricGroup: "summary",
              valueDecimal: 40,
              valueCents: null,
              currency: null,
              metadata: {},
            },
          ],
          exports: [],
        },
      ],
    };
    const refreshed = {
      ...existing,
      status: EstimateStatus.DRAFT,
      latestVersionNumber: 3,
      versions: [
        {
          ...existing.versions[0],
        },
        {
          id: "ver_new",
          versionNumber: 3,
          label: "Revision 3",
          status: EstimateVersionStatus.WORKING,
          lockedAt: null,
          notes: "Revision created from v2",
          projectSnapshot: { shortCode: "PRJ" },
          assumptions: [],
          scopeItems: [],
          demandLines: [],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
        },
      ],
    };
    const findUnique = vi.fn().mockResolvedValueOnce(existing).mockResolvedValueOnce(refreshed);
    const createVersion = vi.fn().mockResolvedValue({ id: "ver_new" });
    const createAssumptions = vi.fn().mockResolvedValue({ count: 1 });
    const createScopeItem = vi.fn().mockResolvedValue({ id: "scope_new" });
    const createDemandLines = vi.fn().mockResolvedValue({ count: 1 });
    const createSnapshots = vi.fn().mockResolvedValue({ count: 1 });
    const createMetrics = vi.fn().mockResolvedValue({ count: 1 });
    const updateEstimate = vi.fn().mockResolvedValue({});
    const db = {
      estimate: {
        findUnique,
        update: updateEstimate,
      },
      estimateVersion: {
        create: createVersion,
      },
      estimateAssumption: {
        createMany: createAssumptions,
      },
      scopeItem: {
        create: createScopeItem,
      },
      estimateDemandLine: {
        createMany: createDemandLines,
      },
      resourceCostSnapshot: {
        createMany: createSnapshots,
      },
      estimateMetric: {
        createMany: createMetrics,
      },
      $transaction: vi.fn(async (callback) =>
        callback({
          estimateVersion: {
            create: createVersion,
          },
          estimateAssumption: {
            createMany: createAssumptions,
          },
          scopeItem: {
            create: createScopeItem,
          },
          estimateDemandLine: {
            createMany: createDemandLines,
          },
          resourceCostSnapshot: {
            createMany: createSnapshots,
          },
          estimateMetric: {
            createMany: createMetrics,
          },
          estimate: {
            update: updateEstimate,
          },
        }),
      ),
    };

    const result = await createEstimateRevision(db as never, { estimateId });

    expect(result.latestVersionNumber).toBe(3);
    expect(createVersion).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          estimateId,
          versionNumber: 3,
          status: EstimateVersionStatus.WORKING,
        }),
      }),
    );
    expect(createDemandLines).toHaveBeenCalledWith({
      data: [
        expect.objectContaining({
          estimateVersionId: "ver_new",
          scopeItemId: "scope_new",
          name: "Comp",
        }),
      ],
    });
    expect(updateEstimate).toHaveBeenCalledWith({
      where: { id: estimateId },
      data: {
        latestVersionNumber: 3,
        status: EstimateStatus.DRAFT,
      },
    });
  });

  it("creates an export artifact for the selected estimate version", async () => {
    const createdAt = new Date("2026-03-13T08:00:00.000Z");
    const findUnique = vi
      .fn()
      .mockResolvedValueOnce({
        id: "est_3",
        name: "CGI Estimate",
        baseCurrency: "EUR",
        createdAt,
        updatedAt: createdAt,
        status: EstimateStatus.APPROVED,
        latestVersionNumber: 1,
        project: {
          id: "project_1",
          shortCode: "CGI-001",
          name: "CGI Project",
          status: "ACTIVE",
        },
        versions: [
          {
            id: "ver_1",
            versionNumber: 1,
            status: EstimateVersionStatus.APPROVED,
            lockedAt: new Date("2026-03-13"),
            createdAt,
            updatedAt: createdAt,
            assumptions: [],
            scopeItems: [],
            demandLines: [
              {
                id: "line_1",
                scopeItemId: null,
                roleId: "role_1",
                resourceId: "resource_1",
                lineType: "LABOR",
                name: "Comp Artist",
                chapter: "Compositing",
                hours: 40,
                days: 5,
                fte: 1,
                rateSource: "resource",
                costRateCents: 5000,
                billRateCents: 8000,
                currency: "EUR",
                costTotalCents: 200000,
                priceTotalCents: 320000,
                monthlySpread: { "2026-03": 40 },
                staffingAttributes: {},
                metadata: {},
                createdAt,
                updatedAt: createdAt,
              },
            ],
            resourceSnapshots: [],
            metrics: [
              {
                id: "metric_1",
                key: "total_hours",
                label: "Total Hours",
                metricGroup: "summary",
                valueDecimal: 40,
                valueCents: null,
                currency: null,
                metadata: {},
                createdAt,
                updatedAt: createdAt,
              },
            ],
            exports: [],
            projectSnapshot: {
              startDate: "2026-03-01T00:00:00.000Z",
              endDate: "2026-04-01T00:00:00.000Z",
            },
          },
        ],
      })
      .mockResolvedValueOnce({
        id: "est_3",
        name: "CGI Estimate",
        baseCurrency: "EUR",
        createdAt,
        updatedAt: createdAt,
        status: EstimateStatus.APPROVED,
        latestVersionNumber: 1,
        project: {
          id: "project_1",
          shortCode: "CGI-001",
          name: "CGI Project",
          status: "ACTIVE",
        },
        versions: [
          {
            id: "ver_1",
            versionNumber: 1,
            status: EstimateVersionStatus.APPROVED,
            lockedAt: new Date("2026-03-13"),
            createdAt,
            updatedAt: createdAt,
            assumptions: [],
            scopeItems: [],
            demandLines: [],
            resourceSnapshots: [],
            metrics: [],
            exports: [
              {
                id: "exp_1",
                format: EstimateExportFormat.JSON,
                fileName: "cgi-estimate-v1.json",
                payload: {},
              },
            ],
            projectSnapshot: {},
          },
        ],
      });
    const createExport = vi.fn().mockResolvedValue({ id: "exp_1" });
    const db = {
      estimate: {
        findUnique,
      },
      estimateExport: {
        create: createExport,
      },
    };

    await createEstimateExport(db as never, {
      estimateId: "est_3",
      format: EstimateExportFormat.JSON,
    });

    expect(createExport).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          estimateVersionId: "ver_1",
          format: EstimateExportFormat.JSON,
          fileName: "cgi-estimate-v1.json",
          payload: expect.objectContaining({
            format: EstimateExportFormat.JSON,
            encoding: "utf8",
            mimeType: "application/json; charset=utf-8",
            content: expect.stringContaining('"estimateId": "est_3"'),
            summary: expect.objectContaining({
              estimateId: "est_3",
              projectName: "CGI Project",
              totalHours: 40,
              demandLineCount: 1,
            }),
          }),
        }),
      }),
    );
  });

  it("approves the submitted estimate version and marks the estimate approved", async () => {
    const findUnique = vi
      .fn()
      .mockResolvedValueOnce({
        id: "est_4",
        status: EstimateStatus.IN_REVIEW,
        latestVersionNumber: 2,
        versions: [
          {
            id: "ver_submit",
            versionNumber: 2,
            status: EstimateVersionStatus.SUBMITTED,
            lockedAt: new Date("2026-03-13"),
            assumptions: [],
            scopeItems: [],
            demandLines: [],
            resourceSnapshots: [],
            metrics: [],
            exports: [],
            projectSnapshot: {},
          },
          {
            id: "ver_approved_old",
            versionNumber: 1,
            status: EstimateVersionStatus.APPROVED,
            lockedAt: new Date("2026-03-01"),
            assumptions: [],
            scopeItems: [],
            demandLines: [],
            resourceSnapshots: [],
            metrics: [],
            exports: [],
            projectSnapshot: {},
          },
        ],
      })
      .mockResolvedValueOnce({
        id: "est_4",
        status: EstimateStatus.APPROVED,
        latestVersionNumber: 2,
        versions: [],
      });
    const updateMany = vi.fn().mockResolvedValue({ count: 1 });
    const updateVersion = vi.fn().mockResolvedValue({});
    const updateEstimate = vi.fn().mockResolvedValue({});
    const db = {
      estimate: {
        findUnique,
        update: updateEstimate,
      },
      estimateVersion: {
        updateMany,
        update: updateVersion,
      },
      $transaction: vi.fn(async (callback) =>
        callback({
          estimateVersion: {
            updateMany,
            update: updateVersion,
          },
          estimate: {
            update: updateEstimate,
          },
        }),
      ),
    };

    const result = await approveEstimateVersion(db as never, {
      estimateId: "est_4",
    });

    expect(result.status).toBe(EstimateStatus.APPROVED);
    expect(updateMany).toHaveBeenCalledWith({
      where: { id: { in: ["ver_approved_old"] } },
      data: { status: EstimateVersionStatus.SUPERSEDED },
    });
    expect(updateVersion).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "ver_submit" },
        data: expect.objectContaining({
          status: EstimateVersionStatus.APPROVED,
        }),
      }),
    );
  });

  it("creates planning allocations from an approved estimate version", async () => {
    const estimate = {
      id: "est_plan",
      projectId: "project_1",
      status: EstimateStatus.APPROVED,
      latestVersionNumber: 2,
      versions: [
        {
          id: "ver_approved",
          versionNumber: 2,
          status: EstimateVersionStatus.APPROVED,
          lockedAt: new Date("2026-03-13"),
          assumptions: [],
          scopeItems: [],
          demandLines: [
            {
              id: "line_resource",
              scopeItemId: null,
              roleId: "role_comp",
              resourceId: "resource_1",
              lineType: "LABOR",
              name: "Senior Comp",
              chapter: "Comp",
              hours: 80,
              days: 10,
              fte: 1,
              rateSource: "RESOURCE",
              costRateCents: 5000,
              billRateCents: 9000,
              currency: "EUR",
              costTotalCents: 400000,
              priceTotalCents: 720000,
              monthlySpread: {},
              staffingAttributes: {},
              metadata: {},
            },
            {
              id: "line_placeholder",
              scopeItemId: null,
              roleId: "role_fx",
              resourceId: null,
              lineType: "LABOR",
              name: "FX Artist",
              chapter: "FX",
              hours: 160,
              days: 20,
              fte: 2,
              rateSource: "ROLE",
              costRateCents: 4500,
              billRateCents: 8000,
              currency: "EUR",
              costTotalCents: 720000,
              priceTotalCents: 1280000,
              monthlySpread: {},
              staffingAttributes: {},
              metadata: {},
            },
          ],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
          projectSnapshot: {},
        },
      ],
    };

    const project = {
      id: "project_1",
      shortCode: "PRJ",
      name: "Project One",
      status: "ACTIVE",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-27"),
      orderType: "CHARGEABLE",
      allocationType: "EXT",
      winProbability: 100,
      budgetCents: 0,
      responsiblePerson: null,
    };

    const estimateFindUnique = vi.fn().mockResolvedValue(estimate);
    const projectFindUnique = vi.fn().mockResolvedValue(project);
    const resourceFindMany = vi.fn().mockResolvedValue([
      {
        id: "resource_1",
        availability: {
          monday: 8,
          tuesday: 8,
          wednesday: 8,
          thursday: 8,
          friday: 8,
          saturday: 0,
          sunday: 0,
        },
      },
    ]);
    const allocationFindMany = vi.fn().mockResolvedValue([]);
    const demandRequirementFindMany = vi.fn().mockResolvedValue([]);
    const demandRequirementCreate = vi
      .fn()
      .mockResolvedValueOnce({
        id: "demand_resource",
        projectId: "project_1",
        startDate: project.startDate,
        endDate: project.endDate,
        hoursPerDay: 8,
        percentage: 100,
        role: "Compositor",
        roleId: "role_comp",
        headcount: 1,
        status: AllocationStatus.PROPOSED,
        metadata: {},
        createdAt: new Date("2026-03-13"),
        updatedAt: new Date("2026-03-13"),
        project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
        roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      })
      .mockResolvedValueOnce({
        id: "demand_placeholder",
        projectId: "project_1",
        startDate: project.startDate,
        endDate: project.endDate,
        hoursPerDay: 4,
        percentage: 50,
        role: "FX Artist",
        roleId: "role_fx",
        headcount: 2,
        status: AllocationStatus.PROPOSED,
        metadata: {},
        createdAt: new Date("2026-03-13"),
        updatedAt: new Date("2026-03-13"),
        project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
        roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
      });
    const demandRequirementUpdate = vi.fn().mockResolvedValue({});
    const resourceFindUnique = vi.fn().mockResolvedValue({
      id: "resource_1",
      lcrCents: 5000,
      availability: {
        monday: 8,
        tuesday: 8,
        wednesday: 8,
        thursday: 8,
        friday: 8,
        saturday: 0,
        sunday: 0,
      },
    });
    const assignmentFindMany = vi.fn().mockResolvedValue([]);
    const assignmentCreate = vi.fn().mockResolvedValue({
      id: "assignment_1",
      projectId: "project_1",
      demandRequirementId: "demand_resource",
      resourceId: "resource_1",
      startDate: project.startDate,
      endDate: project.endDate,
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: { id: "resource_1", displayName: "Alice", eid: "E-001", lcrCents: 5000 },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: {
        id: "demand_resource",
        projectId: "project_1",
        startDate: project.startDate,
        endDate: project.endDate,
        hoursPerDay: 8,
        percentage: 100,
        role: "Compositor",
        roleId: "role_comp",
        headcount: 1,
        status: AllocationStatus.PROPOSED,
      },
    });
    const assignmentUpdate = vi.fn().mockResolvedValue({});
    const auditLogCreate = vi.fn().mockResolvedValue({});
    const vacationFindMany = vi.fn().mockResolvedValue([]);

    const db = {
      estimate: {
        findUnique: estimateFindUnique,
      },
      project: {
        findUnique: projectFindUnique,
      },
      allocation: {
        findMany: allocationFindMany,
      },
      demandRequirement: {
        findMany: demandRequirementFindMany,
      },
      assignment: {
        findMany: assignmentFindMany,
      },
      resource: {
        findMany: resourceFindMany,
      },
      $transaction: vi.fn(async (callback) =>
        callback({
          project: {
            findUnique: projectFindUnique,
          },
          resource: {
            findUnique: resourceFindUnique,
          },
          allocation: {
            findMany: allocationFindMany,
          },
          demandRequirement: {
            create: demandRequirementCreate,
            findUnique: vi.fn().mockResolvedValue({
              id: "demand_resource",
              projectId: "project_1",
            }),
            update: demandRequirementUpdate,
          },
          assignment: {
            findMany: assignmentFindMany,
            create: assignmentCreate,
            update: assignmentUpdate,
          },
          auditLog: {
            create: auditLogCreate,
          },
          vacation: {
            findMany: vacationFindMany,
          },
        }),
      ),
    };

    const result = await createEstimatePlanningHandoff(db as never, {
      estimateId: "est_plan",
    });

    expect(result).toMatchObject({
      estimateId: "est_plan",
      estimateVersionId: "ver_approved",
      projectId: "project_1",
      createdCount: 2,
      assignedCount: 1,
      placeholderCount: 1,
      fallbackPlaceholderCount: 0,
    });
    expect(demandRequirementCreate).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({
        data: expect.objectContaining({
          roleId: "role_comp",
          headcount: 1,
          metadata: expect.objectContaining({
            estimateHandoff: expect.objectContaining({
              estimateDemandLineId: "line_resource",
              handoffMode: "resource",
            }),
          }),
        }),
      }),
    );
    expect(demandRequirementCreate).toHaveBeenNthCalledWith(
      2,
      expect.objectContaining({
        data: expect.objectContaining({
          roleId: "role_fx",
          headcount: 2,
          metadata: expect.objectContaining({
            estimateHandoff: expect.objectContaining({
              estimateDemandLineId: "line_placeholder",
              handoffMode: "placeholder",
            }),
          }),
        }),
      }),
    );
    expect(assignmentCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          demandRequirementId: "demand_resource",
          resourceId: "resource_1",
          roleId: "role_comp",
          metadata: expect.objectContaining({
            estimateHandoff: expect.objectContaining({
              estimateDemandLineId: "line_resource",
              handoffMode: "resource",
            }),
          }),
        }),
      }),
    );
    expect(demandRequirementUpdate).not.toHaveBeenCalled();
    expect(assignmentUpdate).not.toHaveBeenCalled();
  });

  it("blocks duplicate planning handoff for the same approved version", async () => {
    const estimateFindUnique = vi.fn().mockResolvedValue({
      id: "est_plan_dup",
      projectId: "project_dup",
      status: EstimateStatus.APPROVED,
      latestVersionNumber: 1,
      versions: [
        {
          id: "ver_dup",
          versionNumber: 1,
          status: EstimateVersionStatus.APPROVED,
          lockedAt: new Date("2026-03-13"),
          assumptions: [],
          scopeItems: [],
          demandLines: [],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
          projectSnapshot: {},
        },
      ],
    });
    const projectFindUnique = vi.fn().mockResolvedValue({
      id: "project_dup",
      shortCode: "PRJ",
      name: "Project Dup",
      status: "ACTIVE",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-27"),
      orderType: "CHARGEABLE",
      allocationType: "EXT",
      winProbability: 100,
      budgetCents: 0,
      responsiblePerson: null,
    });
    const demandRequirementFindMany = vi.fn().mockResolvedValue([
      {
        id: "demand_existing",
        projectId: "project_dup",
        startDate: new Date("2026-03-16"),
        endDate: new Date("2026-03-27"),
        hoursPerDay: 8,
        percentage: 100,
        role: "Developer",
        roleId: null,
        headcount: 1,
        status: "ACTIVE",
        metadata: { estimateHandoff: { estimateVersionId: "ver_dup" } },
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    ]);
    const assignmentFindMany = vi.fn().mockResolvedValue([]);
    const db = {
      estimate: {
        findUnique: estimateFindUnique,
      },
      project: {
        findUnique: projectFindUnique,
      },
      demandRequirement: {
        findMany: demandRequirementFindMany,
      },
      assignment: {
        findMany: assignmentFindMany,
      },
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    await expect(
      createEstimatePlanningHandoff(db as never, { estimateId: "est_plan_dup" }),
    ).rejects.toThrow("Planning handoff already exists for this approved version");
  });

  it("blocks duplicate planning handoff when only an explicit assignment row remains", async () => {
    const estimateFindUnique = vi.fn().mockResolvedValue({
      id: "est_plan_assignment_dup",
      projectId: "project_dup",
      status: EstimateStatus.APPROVED,
      latestVersionNumber: 1,
      versions: [
        {
          id: "ver_dup_assignment",
          versionNumber: 1,
          status: EstimateVersionStatus.APPROVED,
          lockedAt: new Date("2026-03-13"),
          assumptions: [],
          scopeItems: [],
          demandLines: [],
          resourceSnapshots: [],
          metrics: [],
          exports: [],
          projectSnapshot: {},
        },
      ],
    });
    const projectFindUnique = vi.fn().mockResolvedValue({
      id: "project_dup",
      shortCode: "PRJ",
      name: "Project Dup",
      status: "ACTIVE",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-27"),
      orderType: "CHARGEABLE",
      allocationType: "EXT",
      winProbability: 100,
      budgetCents: 0,
      responsiblePerson: null,
    });
    const db = {
      estimate: {
        findUnique: estimateFindUnique,
      },
      project: {
        findUnique: projectFindUnique,
      },
      demandRequirement: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "assignment_existing",
            demandRequirementId: null,
            resourceId: "resource_1",
            projectId: "project_dup",
            startDate: new Date("2026-03-16"),
            endDate: new Date("2026-03-27"),
            hoursPerDay: 8,
            percentage: 100,
            role: "Comp",
            roleId: "role_comp",
            dailyCostCents: 32000,
            status: AllocationStatus.PROPOSED,
            metadata: {
              estimateHandoff: {
                estimateVersionId: "ver_dup_assignment",
              },
            },
            createdAt: new Date("2026-03-13"),
            updatedAt: new Date("2026-03-13"),
          },
        ]),
      },
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    await expect(
      createEstimatePlanningHandoff(db as never, {
        estimateId: "est_plan_assignment_dup",
      }),
    ).rejects.toThrow("Planning handoff already exists for this approved version");
  });
});
