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 { EmptyTransferLinesError, InvalidQuantityError } from "../lib/errors.generated";
import { expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseOpenTransferOrder,
  baseStockLevel,
  baseStorageLocation,
  baseTransferOrderLine,
  transitStorageLocation,
} from "../testing/fixtures";
import { run } from "./shipTransferOrder";

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

  it("ships a transfer line into in-transit stock", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseOpenTransferOrder], // TransferOrder select
      [baseTransferOrderLine], // TransferOrderLine select
      [baseStorageLocation], // source StorageLocation select
      [transitStorageLocation], // transit StorageLocation select
      [baseStockLevel], // source StockLevel select
      [{ id: baseStorageLocation.id }], // site location ids select
      [baseStockLevel], // site StockLevel select
      [], // open reservations select
      [{ id: "execution-transfer-ship-1", status: "POSTED" }], // InventoryExecution insert
      [{ ...baseStockLevel, quantity: "75" }], // source StockLevel update
      [], // transit StockLevel select (not found)
      [], // transit StockLevel insert
      [{ ...baseTransferOrderLine, shippedQuantity: "10" }], // TransferOrderLine update
      [{ id: "execution-line-out" }, { id: "execution-line-in" }], // InventoryExecutionLine insert
      [
        { id: "ledger-source", dcIndicator: "CR" },
        { id: "ledger-transit", dcIndicator: "DR" },
      ], // InventoryLedger insert
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: "transfer-order-3",
          effectiveDate: new Date("2024-01-15T00:00:00.000Z"),
          lines: [
            {
              transferOrderLineId: "transfer-order-line-1",
              sourceStorageLocationId: "location-1",
              transitStorageLocationId: "location-transit-1",
              quantity: "10",
            },
          ],
        },
        ctx,
      ),
    );

    const value = expectOk<{
      inventoryExecution: { status: string };
      inventoryLedgerEntries: unknown[];
      transferOrderLines: { shippedQuantity: string }[];
    }>(result as never);

    expect(value.inventoryExecution.status).toBe("POSTED");
    expect(value.inventoryLedgerEntries).toHaveLength(2);
    expect(value.transferOrderLines[0]?.shippedQuantity).toBe("10");
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({
        executionType: "GOODS_ISSUE",
        sourceDocumentType: "TRANSFER_ORDER",
        sourceDocumentId: "transfer-order-3",
      }),
    );
    expect(mock.inserts[3].insertRows()).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ dcIndicator: "CR", stockType: "AVAILABLE", quantity: "10" }),
        expect.objectContaining({ dcIndicator: "DR", stockType: "IN_TRANSIT", quantity: "10" }),
      ]),
    );
  });

  it("returns error when shipping more than remaining ordered quantity", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseOpenTransferOrder],
      [
        {
          ...baseTransferOrderLine,
          orderedQuantity: "10",
          shippedQuantity: "8",
        },
      ],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: "transfer-order-3",
          effectiveDate: new Date("2024-01-15T00:00:00.000Z"),
          lines: [
            {
              transferOrderLineId: "transfer-order-line-1",
              sourceStorageLocationId: "location-1",
              transitStorageLocationId: "location-transit-1",
              quantity: "3",
            },
          ],
        },
        ctx,
      ),
    );

    expectErr(result as never, InvalidQuantityError);
    expect(mock.inserts).toHaveLength(0);
  });

  it("returns error when no lines are provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseOpenTransferOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: "transfer-order-3",
          effectiveDate: new Date("2024-01-15T00:00:00.000Z"),
          lines: [],
        },
        ctx,
      ),
    );

    expectErr(result as never, EmptyTransferLinesError);
    expect(mock.inserts).toHaveLength(0);
  });
});
