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,
  PrimaryUnitMismatchError,
} from "../lib/errors.generated";
import { expectErr, expectOk, mockItemManagementQueries } from "../testing/commandTestUtils";
import {
  baseActiveItem,
  baseGoodsIssueExecution,
  baseGoodsIssueExecutionLine,
  baseStorageLocation,
} from "../testing/fixtures";
import { run } from "./updateGoodsIssue";

describe("updateGoodsIssue", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["inventory:inventoryOperation:updateGoodsIssue"],
  };
  const issueLine = {
    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 = {
      ...baseGoodsIssueExecution,
      effectiveDate: new Date("2024-02-01T00:00:00.000Z"),
    };
    spies.select.mockReturnValueOnce(baseGoodsIssueExecution);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: baseGoodsIssueExecution.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 issue", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsIssueExecution)
      .mockReturnValueOnce(baseStorageLocation);
    spies.insert.mockReturnValueOnce([baseGoodsIssueExecutionLine]);

    const result = await run(
      db,
      {
        id: baseGoodsIssueExecution.id,
        lines: [issueLine],
      },
      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 primary quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsIssueExecution);

    const result = await run(
      db,
      {
        id: baseGoodsIssueExecution.id,
        lines: [{ ...issueLine, 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(baseGoodsIssueExecution);

    const result = await run(
      db,
      {
        id: baseGoodsIssueExecution.id,
        lines: [{ ...issueLine, 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(baseGoodsIssueExecution);

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

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