import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import { InvalidShipmentStatusError, OverShipmentNotAllowedError } from "../lib/errors.generated";
import {
  baseSalesOrderLine,
  baseShipment,
  baseShipmentLine,
  postedShipment,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./postShipment";

describe("postShipment", () => {
  it("posts a shipment and updates order shipped progress", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const shipmentLine = { ...baseShipmentLine, shipmentId: baseShipment.id };
    const orderLine = { ...baseSalesOrderLine, salesOrderId: baseShipment.salesOrderId };
    spies.select
      .mockReturnValueOnce(baseShipment)
      .mockReturnValueOnce([shipmentLine])
      .mockReturnValueOnce(orderLine)
      .mockReturnValueOnce([{ ...orderLine, shippedQuantity: shipmentLine.quantity }])
      .mockReturnValueOnce({ id: baseShipment.salesOrderId, status: "CONFIRMED" });
    spies.update
      .mockReturnValueOnce({ ...orderLine, shippedQuantity: shipmentLine.quantity })
      .mockReturnValueOnce({ id: baseShipment.salesOrderId, status: "PARTIALLY_SHIPPED" })
      .mockReturnValueOnce({ ...postedShipment, inventoryHandoff: JSON.stringify([]) });

    const result = await run(db, { shipmentId: baseShipment.id }, commandCtx);

    const value = expectOk(result);
    expect(value.shipment.status).toBe("POSTED");
  });

  it("emits downstream inventory handoff for stock lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const shipmentLine = { ...baseShipmentLine, shipmentId: baseShipment.id };
    const orderLine = { ...baseSalesOrderLine, salesOrderId: baseShipment.salesOrderId };
    spies.select
      .mockReturnValueOnce(baseShipment)
      .mockReturnValueOnce([shipmentLine])
      .mockReturnValueOnce(orderLine)
      .mockReturnValueOnce([{ ...orderLine, shippedQuantity: shipmentLine.quantity }])
      .mockReturnValueOnce({ id: baseShipment.salesOrderId, status: "CONFIRMED" });
    spies.update
      .mockReturnValueOnce({ ...orderLine, shippedQuantity: shipmentLine.quantity })
      .mockReturnValueOnce({ id: baseShipment.salesOrderId, status: "PARTIALLY_SHIPPED" })
      .mockReturnValueOnce({
        ...postedShipment,
        inventoryHandoff: JSON.stringify([
          {
            shipmentLineId: shipmentLine.id,
            salesOrderLineId: shipmentLine.salesOrderLineId,
            quantity: shipmentLine.quantity,
          },
        ]),
      });

    const result = await run(db, { shipmentId: baseShipment.id }, commandCtx);

    expectOk(result);
    const lastSetCall = spies.set.mock.calls.at(-1)?.[0] as
      | { inventoryHandoff?: string | null; status?: string }
      | undefined;
    expect(lastSetCall?.status).toBe("POSTED");
    expect(typeof lastSetCall?.inventoryHandoff).toBe("string");
  });

  it("throws when shipment quantity exceeds remaining order quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const shipmentLine = { ...baseShipmentLine, shipmentId: baseShipment.id };
    const orderLine = {
      ...baseSalesOrderLine,
      salesOrderId: baseShipment.salesOrderId,
      shippedQuantity: 9,
      quantity: 10,
    };
    spies.select
      .mockReturnValueOnce(baseShipment)
      .mockReturnValueOnce([shipmentLine])
      .mockReturnValueOnce(orderLine);

    const result = await run(db, { shipmentId: baseShipment.id }, commandCtx);

    expectErr(result, OverShipmentNotAllowedError);
  });

  it("throws when posting a non-draft shipment", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(postedShipment);

    const result = await run(db, { shipmentId: postedShipment.id }, commandCtx);

    expectErr(result, InvalidShipmentStatusError);
  });
});
