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 { EmptyReceiptLinesError, InvalidStatusError } from "../lib/errors.generated";
import { expectErr, expectOk, mockItemManagementQueries } from "../testing/commandTestUtils";
import {
  baseActiveItem,
  baseGoodsReceiptExecution,
  baseGoodsReceiptExecutionLine,
  baseStorageLocation,
  baseSupplyPlan,
} from "../testing/fixtures";
import { run } from "./postGoodsReceipt";

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

  it("returns error when execution is missing", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: "missing" }, ctx, mockItemManagementQueries(baseActiveItem)),
    );

    expectErr(result as never, InvalidStatusError);
  });

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

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseGoodsReceiptExecution.id },
        ctx,
        mockItemManagementQueries(baseActiveItem),
      ),
    );

    expectErr(result as never, EmptyReceiptLinesError);
    expect(mock.updates).toHaveLength(0);
  });

  it("posts execution lines into ledger and stock", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const ledger = {
      id: "ledger-1",
      inventoryExecutionId: "execution-1",
      dcIndicator: "DR",
    };
    const postedExecution = {
      ...baseGoodsReceiptExecution,
      status: "POSTED",
      postedAt: new Date("2024-01-10T00:00:00.000Z"),
    };

    mock.enqueueResults(
      [baseGoodsReceiptExecution],
      [baseGoodsReceiptExecutionLine],
      [baseStorageLocation],
      [baseSupplyPlan],
      [baseSupplyPlan],
      [{ ...baseSupplyPlan, receivedQuantity: "5" }],
      [ledger],
      [],
      [],
      [],
      [postedExecution],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseGoodsReceiptExecution.id },
        ctx,
        mockItemManagementQueries(baseActiveItem),
      ),
    );

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

    expect(value.inventoryExecution.status).toBe("POSTED");
    expect(value.inventoryLedgerEntries[0]).toMatchObject({
      inventoryExecutionId: baseGoodsReceiptExecution.id,
    });
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({
        inventoryExecutionId: baseGoodsReceiptExecution.id,
        quantity: "5",
      }),
    );
  });

  it("consumes matching supply plans when posting", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const ledger = {
      id: "ledger-1",
      inventoryExecutionId: baseGoodsReceiptExecution.id,
      dcIndicator: "DR",
    };
    const postedExecution = {
      ...baseGoodsReceiptExecution,
      status: "POSTED",
      postedAt: new Date("2024-01-10T00:00:00.000Z"),
    };

    mock.enqueueResults(
      [baseGoodsReceiptExecution],
      [baseGoodsReceiptExecutionLine],
      [baseStorageLocation],
      [baseSupplyPlan],
      [baseSupplyPlan],
      [{ ...baseSupplyPlan, receivedQuantity: "5" }],
      [ledger],
      [],
      [],
      [],
      [postedExecution],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseGoodsReceiptExecution.id },
        ctx,
        mockItemManagementQueries(baseActiveItem),
      ),
    );

    expectOk(result as never);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({
        receivedQuantity: "5",
        status: "OPEN",
      }),
    );
  });

  it("posts receipt without supply matching when source linkage is missing", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const legacyExecutionLine = { ...baseGoodsReceiptExecutionLine, sourceLineId: null };
    const legacyExecution = { ...baseGoodsReceiptExecution, sourceDocumentId: null };
    const ledger = {
      id: "ledger-1",
      inventoryExecutionId: legacyExecution.id,
      dcIndicator: "DR",
    };
    const postedExecution = {
      ...legacyExecution,
      status: "POSTED",
      postedAt: new Date("2024-01-10T00:00:00.000Z"),
    };

    mock.enqueueResults(
      [legacyExecution],
      [legacyExecutionLine],
      [baseStorageLocation],
      [ledger],
      [],
      [],
      [],
      [postedExecution],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { id: legacyExecution.id }, ctx, mockItemManagementQueries(baseActiveItem)),
    );

    expectOk(result as never);
  });

  it("posts receipt without supply matching when no supply plan matches", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const ledger = {
      id: "ledger-1",
      inventoryExecutionId: baseGoodsReceiptExecution.id,
      dcIndicator: "DR",
    };
    const postedExecution = {
      ...baseGoodsReceiptExecution,
      status: "POSTED",
      postedAt: new Date("2024-01-10T00:00:00.000Z"),
    };

    mock.enqueueResults(
      [baseGoodsReceiptExecution],
      [baseGoodsReceiptExecutionLine],
      [baseStorageLocation],
      [],
      [ledger],
      [],
      [],
      [],
      [postedExecution],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseGoodsReceiptExecution.id },
        ctx,
        mockItemManagementQueries(baseActiveItem),
      ),
    );

    expectOk(result as never);
  });

  it("posts receipt and consumes available supply when receipt quantity exceeds supply", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const smallSupplyPlan = { ...baseSupplyPlan, expectedQuantity: "4" };
    const ledger = {
      id: "ledger-1",
      inventoryExecutionId: baseGoodsReceiptExecution.id,
      dcIndicator: "DR",
    };
    const postedExecution = {
      ...baseGoodsReceiptExecution,
      status: "POSTED",
      postedAt: new Date("2024-01-10T00:00:00.000Z"),
    };

    mock.enqueueResults(
      [baseGoodsReceiptExecution],
      [baseGoodsReceiptExecutionLine],
      [baseStorageLocation],
      [smallSupplyPlan],
      [smallSupplyPlan],
      [{ ...smallSupplyPlan, receivedQuantity: "4" }],
      [ledger],
      [],
      [],
      [],
      [postedExecution],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseGoodsReceiptExecution.id },
        ctx,
        mockItemManagementQueries(baseActiveItem),
      ),
    );

    expectOk(result as never);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({
        receivedQuantity: "4",
      }),
    );
  });
});
