import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } 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";

describe("completeWorkOrder", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["manufacturing: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 mock = createKyselyMock<Namespace["main-db"]>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    mock.enqueueResults(
      [baseInProgressWorkOrder],
      [completedWorkOrder],
      [],
      [{ ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" }],
      [],
    );

    const result = await mock.withTx((trx) => run(trx, 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(mock.updates.length).toBeGreaterThan(0);
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("returns error when the work order does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) => run(trx, { ...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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePendingWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(trx, { ...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 mock = createKyselyMock<Namespace["main-db"]>();
    const unstartedWorkOrder = {
      ...baseInProgressWorkOrder,
      actualStartDate: null,
    };
    mock.enqueueResults([unstartedWorkOrder]);

    const result = await mock.withTx((trx) => run(trx, 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(trx, { ...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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    mock.enqueueResults(
      [baseInProgressWorkOrder],
      [completedWorkOrder],
      [],
      [{ ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" }],
      [],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInProgressWorkOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    mock.enqueueResults(
      [baseInProgressWorkOrder],
      [completedWorkOrder],
      [],
      [
        { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
        { ...baseCompleteWorkOrder, id: "work-order-sibling", status: "COMPLETE" },
      ],
      [],
    );

    const result = await mock.withTx((trx) => run(trx, 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(mock.updates).toHaveLength(2);
  });

  it("allows zero-quantity completion only under an explicit bypass policy", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: baseInProgressWorkOrder.completedQuantity,
    };

    mock.enqueueResults(
      [baseInProgressWorkOrder],
      [completedWorkOrder],
      [],
      [{ ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" }],
      [],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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 mock = createKyselyMock<Namespace["main-db"]>();
    const completedWorkOrder = {
      ...baseInProgressWorkOrder,
      status: "COMPLETE" as const,
      completedQuantity: 100,
    };

    mock.enqueueResults(
      [baseInProgressWorkOrder],
      [completedWorkOrder],
      [],
      [{ ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" }],
      [],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...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);
    }
  });
});
