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,
  InvalidQuantityError,
  InvalidStateTransitionError,
  TransferOrderLineNotFoundError,
} from "../lib/errors.generated";
import {
  expectErr,
  expectOk,
  mockItemManagementQueries,
  mockOrganizationQueries,
} from "../testing/commandTestUtils";
import {
  baseActiveItem,
  baseActiveSite,
  baseDraftTransferOrder,
  baseOpenTransferOrder,
  baseTransferOrderLine,
} from "../testing/fixtures";
import { run } from "./updateTransferOrder";

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

  it("updates header metadata on a draft transfer order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updated = {
      ...baseDraftTransferOrder,
      plannedShipmentDate: new Date("2024-02-01T00:00:00.000Z"),
    };
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
      [updated], // updateTable TransferOrder
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseDraftTransferOrder.id,
          headerPatch: { plannedShipmentDate: updated.plannedShipmentDate },
        },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

    expectOk(result as never);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({ plannedShipmentDate: updated.plannedShipmentDate }),
    );
    expect(mock.deletes).toHaveLength(0);
  });

  it("updates a line in place so its id and createdAt survive the edit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseDraftTransferOrder.id,
          updateLines: [{ lineId: baseTransferOrderLine.id, linePatch: { orderedQuantity: "8" } }],
        },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

    expectOk(result as never);
    expect(mock.deletes).toHaveLength(0);
    expect(mock.inserts).toHaveLength(0);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({ orderedQuantity: "8" }),
    );
  });

  it("adds a transfer order line via addLines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseDraftTransferOrder.id,
          addLines: [{ itemId: "item-1", orderedQuantity: "4" }],
        },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

    expectOk(result as never);
    expect(mock.inserts[0].insertRows()).toHaveLength(1);
  });

  it("removes a transfer order line via removeLineIds", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const secondLine = { ...baseTransferOrderLine, id: "transfer-order-line-extra" };
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine, secondLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseDraftTransferOrder.id, removeLineIds: [baseTransferOrderLine.id] },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

    expectOk(result as never);
    expect(mock.deletes).toHaveLength(1);
  });

  it("returns error when transfer order is not DRAFT", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseOpenTransferOrder]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseOpenTransferOrder.id, addLines: [{ itemId: "item-1", orderedQuantity: "8" }] },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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

  it("returns error when an update targets a missing line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseDraftTransferOrder.id,
          updateLines: [{ lineId: "missing-line", linePatch: { orderedQuantity: "8" } }],
        },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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

  it("returns error when a removal would leave the transfer order without lines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseDraftTransferOrder.id, removeLineIds: [baseTransferOrderLine.id] },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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

  it("returns error when an added line ordered quantity is zero or negative", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftTransferOrder], // TransferOrder
      [baseTransferOrderLine], // TransferOrderLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseDraftTransferOrder.id,
          addLines: [{ itemId: "item-1", orderedQuantity: "0" }],
        },
        ctx,
        mockItemManagementQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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