import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  WorkOrderNotFoundError,
  WorkOrderNotCompletableError,
  WorkOrderNotStartedError,
  InvalidCompletionQuantityError,
  DuplicateBackflushRiskError,
  ReceiptHandoffRequiredError,
  LotReferenceRequiredError,
  SerialReferenceRequiredError,
} from "../lib/errors.generated";
import {
  baseInProgressWorkOrder,
  basePendingWorkOrder,
  baseCompleteWorkOrder,
} from "../testing/fixtures";
import { run } from "./completeWorkOrder";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

describe("completeWorkOrder", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["manufacturing:completeWorkOrder"],
  };

  const baseInput = {
    id: baseInProgressWorkOrder.id,
    completedQuantity: 50,
    backflushRequired: false,
    receiptRequired: false,
    notes: "Final completion",
  };

  it("completes an in-progress work order with final quantity reporting", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    // work order lookup
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
    // update work order
    spies.update.mockReturnValueOnce(completedWorkOrder);
    // insert execution event
    spies.insert.mockReturnValueOnce(undefined);
    // sibling work orders (all complete after this one)
    spies.select.mockReturnValueOnce([
      { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
    ]);
    // update parent production order
    spies.update.mockReturnValueOnce(undefined);

    const result = await run(db, baseInput, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.workOrder.status).toBe("COMPLETE");
      expect(result.value.workOrder.completedQuantity).toBe(100);
    }
    expect(spies.update).toHaveBeenCalled();
    expect(spies.insert).toHaveBeenCalled();
  });

  it("returns error when the work order does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, { ...baseInput, id: "nonexistent" }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
    }
  });

  it("returns error when the work order is not in progress", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePendingWorkOrder);

    const result = await run(db, { ...baseInput, id: basePendingWorkOrder.id }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(WorkOrderNotCompletableError);
    }
  });

  it("returns error when the work order was never started", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const unstartedWorkOrder = {
      ...baseInProgressWorkOrder,
      actualStartDate: null,
    };
    spies.select.mockReturnValueOnce(unstartedWorkOrder);

    const result = await run(db, baseInput, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(WorkOrderNotStartedError);
    }
  });

  it("returns error when completion quantity is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);

    const result = await run(db, { ...baseInput, completedQuantity: 0 }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidCompletionQuantityError);
    }
  });

  it("returns error when backflush would duplicate manual issue", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);

    const result = await run(
      db,
      {
        ...baseInput,
        backflushRequired: true,
        manuallyIssuedQuantity: 10,
      },
      ctx,
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(DuplicateBackflushRiskError);
    }
  });

  it("emits receipt handoff when output receipt is required", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    // work order lookup
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
    // update work order
    spies.update.mockReturnValueOnce(completedWorkOrder);
    // insert execution event
    spies.insert.mockReturnValueOnce(undefined);
    // sibling work orders
    spies.select.mockReturnValueOnce([
      { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
    ]);
    // update parent production order
    spies.update.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        ...baseInput,
        receiptRequired: true,
        receiptData: {
          itemReference: "item-fg-1",
          unitOfMeasure: "EA",
          siteReference: "site-1",
          postingDate: new Date("2024-03-01T12:00:00.000Z"),
          lotTracked: false,
          serialTracked: false,
        },
      },
      ctx,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.workOrder.status).toBe("COMPLETE");
      expect(result.value.receiptHandoff?.itemReference).toBe("item-fg-1");
    }
  });

  it("returns error when receipt is required but receipt data is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);

    const result = await run(
      db,
      {
        ...baseInput,
        receiptRequired: true,
        receiptData: null,
      },
      ctx,
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ReceiptHandoffRequiredError);
    }
  });

  it("returns error when a lot-tracked receipt omits finishedGoodLotReference", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);

    const result = await run(
      db,
      {
        ...baseInput,
        receiptRequired: true,
        receiptData: {
          itemReference: "item-fg-1",
          unitOfMeasure: "EA",
          siteReference: "site-1",
          postingDate: new Date("2024-03-01T12:00:00.000Z"),
          lotTracked: true,
          serialTracked: false,
          finishedGoodLotReference: null,
        },
      },
      ctx,
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(LotReferenceRequiredError);
    }
  });

  it("returns error when a serial-tracked receipt omits serialReferences", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);

    const result = await run(
      db,
      {
        ...baseInput,
        receiptRequired: true,
        receiptData: {
          itemReference: "item-fg-1",
          unitOfMeasure: "EA",
          siteReference: "site-1",
          postingDate: new Date("2024-03-01T12:00:00.000Z"),
          lotTracked: false,
          serialTracked: true,
          serialReferences: null,
        },
      },
      ctx,
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(SerialReferenceRequiredError);
    }
  });

  it("rolls up completion to the parent order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    // work order lookup
    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
    // update work order
    spies.update.mockReturnValueOnce(completedWorkOrder);
    // insert execution event
    spies.insert.mockReturnValueOnce(undefined);
    // sibling work orders - all siblings already complete/cancelled
    spies.select.mockReturnValueOnce([
      { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
      { ...baseCompleteWorkOrder, id: "work-order-sibling", status: "COMPLETE" },
    ]);
    // update parent production order to COMPLETED
    spies.update.mockReturnValueOnce(undefined);

    const result = await run(db, baseInput, ctx);

    expect(result.ok).toBe(true);
    // The update spy should have been called twice: once for the work order, once for the parent order
    expect(spies.update).toHaveBeenCalledTimes(2);
  });

  it("allows zero-quantity completion only under an explicit bypass policy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: baseInProgressWorkOrder.completedQuantity,
    };

    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
    spies.update.mockReturnValueOnce(completedWorkOrder);
    spies.insert.mockReturnValueOnce(undefined);
    spies.select.mockReturnValueOnce([
      { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
    ]);
    spies.update.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        ...baseInput,
        completedQuantity: 0,
        zeroQuantityBypassPolicy: {
          allowZeroCompletion: true,
          reasonCode: "QUALITY_HOLD",
        },
        receiptRequired: true,
        receiptData: {
          itemReference: "item-fg-1",
          unitOfMeasure: "EA",
          siteReference: "site-1",
          postingDate: new Date("2024-03-01T12:00:00.000Z"),
          lotTracked: false,
          serialTracked: false,
        },
      },
      ctx,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.backflushHandoff).toBeNull();
      expect(result.value.receiptHandoff?.quantity).toBe(0);
    }
  });

  it("emits a backflush handoff when completion requires backflush consumption", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
    spies.update.mockReturnValueOnce(completedWorkOrder);
    spies.insert.mockReturnValueOnce(undefined);
    spies.select.mockReturnValueOnce([
      { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
    ]);
    spies.update.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        ...baseInput,
        backflushRequired: true,
        manuallyIssuedQuantity: 0,
      },
      ctx,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.backflushHandoff?.productionOrderReference).toBe(
        baseInProgressWorkOrder.productionOrderId,
      );
      expect(result.value.backflushHandoff?.completedQuantity).toBe(50);
    }
  });
});
