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

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

  it("receives in-transit stock into the destination site", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const shippedLine = { ...baseTransferOrderLine, shippedQuantity: "10", receivedQuantity: "0" };
    const transitStockLevel = {
      ...baseStockLevel,
      id: "stock-level-transit-1",
      storageLocationId: "location-transit-1",
      stockType: "IN_TRANSIT",
      quantity: "10",
    };
    const transferSupplyPlan = {
      ...baseSupplyPlan,
      sourceType: "TRANSFER_ORDER",
      sourceId: "transfer-order-3",
      sourceLineId: "transfer-order-line-1",
      siteId: "site-2",
      expectedQuantity: "10",
      receivedQuantity: "0",
    };

    mock.enqueueResults(
      [baseOpenTransferOrder], // TransferOrder select
      [shippedLine], // TransferOrderLine select
      [transitStorageLocation], // transit StorageLocation select
      [destinationStorageLocation], // destination StorageLocation select
      [transitStockLevel], // transit StockLevel select
      [{ direction: "IN", primaryQuantity: "10" }], // transit execution lines select
      [transferSupplyPlan], // matching supply plans select
      [{ id: "execution-transfer-receipt-1", status: "POSTED" }], // InventoryExecution insert
      [{ ...transitStockLevel, quantity: "0" }], // transit StockLevel update
      [], // destination StockLevel select (not found)
      [], // destination StockLevel insert
      [{ ...shippedLine, receivedQuantity: "10" }], // TransferOrderLine update
      [{ ...transferSupplyPlan, receivedQuantity: "10", status: "OPEN" }], // InventorySupplyPlan update
      [{ id: "execution-line-out" }, { id: "execution-line-in" }], // InventoryExecutionLine insert
      [
        { id: "ledger-transit", dcIndicator: "CR" },
        { id: "ledger-destination", dcIndicator: "DR" },
      ], // InventoryLedger insert
    );

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

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

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

  it("receives the same transfer line from multiple transit locations", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const shippedLine = { ...baseTransferOrderLine, shippedQuantity: "10", receivedQuantity: "0" };
    const transitStorageLocation2 = {
      ...transitStorageLocation,
      id: "location-transit-2",
    };
    const transitStockLevel1 = {
      ...baseStockLevel,
      id: "stock-level-transit-1",
      storageLocationId: "location-transit-1",
      stockType: "IN_TRANSIT",
      quantity: "5",
    };
    const transitStockLevel2 = {
      ...baseStockLevel,
      id: "stock-level-transit-2",
      storageLocationId: "location-transit-2",
      stockType: "IN_TRANSIT",
      quantity: "5",
    };
    const transferSupplyPlan = {
      ...baseSupplyPlan,
      sourceType: "TRANSFER_ORDER",
      sourceId: "transfer-order-3",
      sourceLineId: "transfer-order-line-1",
      siteId: "site-2",
      expectedQuantity: "10",
      receivedQuantity: "0",
    };

    mock.enqueueResults(
      // --- validation loop, line 1 ---
      [baseOpenTransferOrder], // TransferOrder select
      [shippedLine], // TransferOrderLine select
      [transitStorageLocation], // transit StorageLocation select
      [destinationStorageLocation], // destination StorageLocation select
      [transitStockLevel1], // transit StockLevel select
      [{ direction: "IN", primaryQuantity: "5" }], // transit execution lines select
      [transferSupplyPlan], // matching supply plans select
      // --- validation loop, line 2 ---
      [shippedLine], // TransferOrderLine select
      [transitStorageLocation2], // transit StorageLocation select
      [destinationStorageLocation], // destination StorageLocation select
      [transitStockLevel2], // transit StockLevel select
      [{ direction: "IN", primaryQuantity: "5" }], // transit execution lines select
      [transferSupplyPlan], // matching supply plans select
      // --- execution ---
      [{ id: "execution-transfer-receipt-1", status: "POSTED" }], // InventoryExecution insert
      // --- processing loop, line 1 ---
      [{ ...transitStockLevel1, quantity: "0" }], // transit StockLevel update
      [], // destination StockLevel select (not found)
      [], // destination StockLevel insert
      [{ ...shippedLine, receivedQuantity: "5" }], // TransferOrderLine update
      // --- processing loop, line 2 ---
      [{ ...transitStockLevel2, quantity: "0" }], // transit StockLevel update
      [{ ...baseStockLevel, id: "stock-level-destination-1", quantity: "5" }], // destination StockLevel select (found)
      [{ ...baseStockLevel, id: "stock-level-destination-1", quantity: "10" }], // destination StockLevel update
      [{ ...shippedLine, receivedQuantity: "10" }], // TransferOrderLine update
      // --- supply plan + final inserts ---
      [{ ...transferSupplyPlan, receivedQuantity: "10", status: "OPEN" }], // InventorySupplyPlan update
      [{ id: "execution-line-out-1" }, { id: "execution-line-in-1" }], // InventoryExecutionLine insert
      [
        { id: "ledger-transit-1", dcIndicator: "CR" },
        { id: "ledger-destination-1", dcIndicator: "DR" },
        { id: "ledger-transit-2", dcIndicator: "CR" },
        { id: "ledger-destination-2", dcIndicator: "DR" },
      ], // InventoryLedger insert
    );

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

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

    expect(value.inventoryLedgerEntries).toHaveLength(4);
    expect(value.transferOrderLines.at(-1)?.receivedQuantity).toBe("10");
  });

  it("returns error when receiving more than in-transit transfer quantity", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseOpenTransferOrder],
      [
        {
          ...baseTransferOrderLine,
          shippedQuantity: "5",
          receivedQuantity: "2",
        },
      ],
    );

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

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

  it("returns error when transit stock belongs to another transfer line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const shippedLine = { ...baseTransferOrderLine, shippedQuantity: "10", receivedQuantity: "0" };
    const transitStockLevel = {
      ...baseStockLevel,
      id: "stock-level-transit-1",
      storageLocationId: "location-transit-1",
      stockType: "IN_TRANSIT",
      quantity: "10",
    };

    mock.enqueueResults(
      [baseOpenTransferOrder],
      [shippedLine],
      [transitStorageLocation],
      [destinationStorageLocation],
      [transitStockLevel],
      [],
    );

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

    expectErr(result as never, InsufficientStockError);
    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-17T00:00:00.000Z"),
          lines: [],
        },
        ctx,
      ),
    );

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