import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } 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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, { id: "missing" }, ctx, mockItemManagementQueries(baseActiveItem));

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

  it("returns error when execution has no lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution).mockReturnValueOnce([]);

    const result = await run(
      db,
      { id: baseGoodsReceiptExecution.id },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectErr(result as never, EmptyReceiptLinesError);
    expect(spies.update).not.toHaveBeenCalled();
  });

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

    spies.select
      .mockReturnValueOnce(baseGoodsReceiptExecution)
      .mockReturnValueOnce([baseGoodsReceiptExecutionLine])
      .mockReturnValueOnce(baseStorageLocation)
      .mockReturnValueOnce([baseSupplyPlan])
      .mockReturnValueOnce(baseSupplyPlan)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(ledger);
    spies.update
      .mockReturnValueOnce({ ...baseSupplyPlan, receivedQuantity: "5" })
      .mockReturnValueOnce(postedExecution);

    const result = await run(
      db,
      { 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(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({
        inventoryExecutionId: baseGoodsReceiptExecution.id,
        quantity: "5",
      }),
    );
  });

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

    spies.select
      .mockReturnValueOnce(baseGoodsReceiptExecution)
      .mockReturnValueOnce([baseGoodsReceiptExecutionLine])
      .mockReturnValueOnce(baseStorageLocation)
      .mockReturnValueOnce([baseSupplyPlan])
      .mockReturnValueOnce(baseSupplyPlan)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(ledger);
    spies.update
      .mockReturnValueOnce({ ...baseSupplyPlan, receivedQuantity: "5" })
      .mockReturnValueOnce(postedExecution);

    const result = await run(
      db,
      { id: baseGoodsReceiptExecution.id },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectOk(result as never);
    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({
        receivedQuantity: "5",
        status: "OPEN",
      }),
    );
  });

  it("posts receipt without supply matching when source linkage is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    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"),
    };

    spies.select
      .mockReturnValueOnce(legacyExecution)
      .mockReturnValueOnce([legacyExecutionLine])
      .mockReturnValueOnce(baseStorageLocation)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(ledger);
    spies.update.mockReturnValueOnce(postedExecution);

    const result = await run(
      db,
      { id: legacyExecution.id },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectOk(result as never);
  });

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

    spies.select
      .mockReturnValueOnce(baseGoodsReceiptExecution)
      .mockReturnValueOnce([baseGoodsReceiptExecutionLine])
      .mockReturnValueOnce(baseStorageLocation)
      .mockReturnValueOnce([])
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(ledger);
    spies.update.mockReturnValueOnce(postedExecution);

    const result = await run(
      db,
      { id: baseGoodsReceiptExecution.id },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectOk(result as never);
  });

  it("posts receipt and consumes available supply when receipt quantity exceeds supply", async () => {
    const { db, spies } = createMockDb<Transaction>();
    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"),
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceiptExecution)
      .mockReturnValueOnce([baseGoodsReceiptExecutionLine])
      .mockReturnValueOnce(baseStorageLocation)
      .mockReturnValueOnce([smallSupplyPlan])
      .mockReturnValueOnce(smallSupplyPlan)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(ledger);
    spies.update
      .mockReturnValueOnce({ ...smallSupplyPlan, receivedQuantity: "4" })
      .mockReturnValueOnce(postedExecution);

    const result = await run(
      db,
      { id: baseGoodsReceiptExecution.id },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectOk(result as never);
    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({
        receivedQuantity: "4",
      }),
    );
  });
});
