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 {
  InvalidQuantityError,
  InvalidUnitConversionRateError,
  InvalidUnitCostError,
  PrimaryUnitMismatchError,
} from "../lib/errors.generated";
import { expectErr, expectOk, mockItemManagementQueries } from "../testing/commandTestUtils";
import {
  baseActiveItem,
  baseGoodsReceiptExecution,
  baseGoodsReceiptExecutionLine,
  baseStorageLocation,
} from "../testing/fixtures";
import { run } from "./updateGoodsReceipt";

describe("updateGoodsReceipt", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["inventory:inventoryOperation:updateGoodsReceipt"],
  };
  const receiptLine = {
    itemId: "item-1",
    storageLocationId: "location-1",
    quantity: "5",
    unitId: "unit-box",
    primaryQuantity: "10",
    primaryUnitId: baseActiveItem.unitId,
    unitConversionRate: "2",
  };

  it("updates header fields without replacing lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = {
      ...baseGoodsReceiptExecution,
      effectiveDate: new Date("2024-02-01T00:00:00.000Z"),
    };
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution);
    spies.update.mockReturnValueOnce(updated);

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

    const value = expectOk<{ inventoryExecution: { effectiveDate: Date } }>(result as never);

    expect(value.inventoryExecution.effectiveDate).toEqual(updated.effectiveDate);
    expect(spies.delete).not.toHaveBeenCalled();
  });

  it("replaces lines for a DRAFT goods receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsReceiptExecution)
      .mockReturnValueOnce(baseStorageLocation);
    spies.insert.mockReturnValueOnce([baseGoodsReceiptExecutionLine]);

    const result = await run(
      db,
      {
        id: baseGoodsReceiptExecution.id,
        lines: [{ ...receiptLine, unitCost: "12" }],
      },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    const value = expectOk<{ inventoryExecutionLines: unknown[] }>(result as never);

    expect(value.inventoryExecutionLines).toHaveLength(1);
    expect(spies.delete).toHaveBeenCalledOnce();
  });

  it("returns error when replacement line unit cost is negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution);

    const result = await run(
      db,
      {
        id: baseGoodsReceiptExecution.id,
        lines: [{ ...receiptLine, unitCost: "-1" }],
      },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectErr(result as never, InvalidUnitCostError);
    expect(spies.delete).not.toHaveBeenCalled();
  });

  it("returns error when replacement line primary quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution);

    const result = await run(
      db,
      {
        id: baseGoodsReceiptExecution.id,
        lines: [{ ...receiptLine, primaryQuantity: "0" }],
      },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectErr(result as never, InvalidQuantityError);
    expect(spies.delete).not.toHaveBeenCalled();
  });

  it("returns error when replacement line unit conversion rate is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution);

    const result = await run(
      db,
      {
        id: baseGoodsReceiptExecution.id,
        lines: [{ ...receiptLine, unitConversionRate: "0" }],
      },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectErr(result as never, InvalidUnitConversionRateError);
    expect(spies.delete).not.toHaveBeenCalled();
  });

  it("returns error when replacement line primary unit differs from item primary unit", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceiptExecution);

    const result = await run(
      db,
      {
        id: baseGoodsReceiptExecution.id,
        lines: [{ ...receiptLine, primaryUnitId: "unit-kg" }],
      },
      ctx,
      mockItemManagementQueries(baseActiveItem),
    );

    expectErr(result as never, PrimaryUnitMismatchError);
    expect(spies.delete).not.toHaveBeenCalled();
  });
});
