import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  EmptyOrderNotAllowedError,
  InvalidOrderLineError,
  ItemNotActiveError,
  LineNotFoundError,
  OrderNotEditableError,
  SalesOrderNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseItem,
  baseOrderLineInput,
  baseSalesOrder,
  baseSalesOrderLine,
} from "../testing/fixtures";
import { mockItemQueries } from "../testing/moduleMocks";
import { run } from "./updateSalesOrder";

describe("updateSalesOrder", () => {
  it("updates mutable draft order header fields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const requestedShipDate = new Date("2024-01-05T00:00:00.000Z");
    const updated = {
      ...baseSalesOrder,
      requestedShipDate,
      updatedAt: new Date("2024-01-02T00:00:00.000Z"),
    };
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine], [updated]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseSalesOrder.id, headerPatch: { requestedShipDate } },
        commandCtx,
        mockItemQueries([]),
      ),
    );

    expectOk(result);
  });

  it("updates a line in place so its id and createdAt survive the edit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesOrder.id,
          updateLines: [{ lineId: baseSalesOrderLine.id, linePatch: { quantity: 5 } }],
        },
        commandCtx,
        mockItemQueries([]),
      ),
    );

    expectOk(result);
    expect(mock.deletes.length).toBe(0);
    expect(mock.inserts.length).toBe(0);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ quantity: 5 }));
  });

  it("adds an order line via addLines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseSalesOrder.id, addLines: [{ ...baseOrderLineInput, quantity: 5 }] },
        commandCtx,
        mockItemQueries([baseItem]),
      ),
    );

    expectOk(result);
    expect(mock.inserts[mock.inserts.length - 1].insertRows()).toHaveLength(1);
  });

  it("removes an order line via removeLineIds", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseSalesOrder],
      [baseSalesOrderLine, { ...baseSalesOrderLine, id: "sales-order-line-2" }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseSalesOrder.id, removeLineIds: [baseSalesOrderLine.id] },
        commandCtx,
        mockItemQueries([]),
      ),
    );

    expectOk(result);
    expect(mock.deletes.length).toBe(1);
  });

  it("returns error when the order does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseSalesOrder.id }, commandCtx, mockItemQueries([])),
    );

    expectErr(result, SalesOrderNotFoundError);
  });

  it("returns error when updating a non-draft order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ ...baseSalesOrder, status: "SUBMITTED" }]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseSalesOrder.id }, commandCtx, mockItemQueries([])),
    );

    expectErr(result, OrderNotEditableError);
  });

  it("returns error when an update targets a missing line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesOrder.id,
          updateLines: [{ lineId: "missing-line", linePatch: { quantity: 5 } }],
        },
        commandCtx,
        mockItemQueries([]),
      ),
    );

    expectErr(result, LineNotFoundError);
  });

  it("returns error when a removal would leave the order without lines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseSalesOrder.id, removeLineIds: [baseSalesOrderLine.id] },
        commandCtx,
        mockItemQueries([]),
      ),
    );

    expectErr(result, EmptyOrderNotAllowedError);
  });

  it("returns error when an added line references an inactive item", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: baseSalesOrder.id, addLines: [baseOrderLineInput] },
        commandCtx,
        mockItemQueries([{ ...baseItem, status: "INACTIVE" }]),
      ),
    );

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when an added line has invalid content", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesOrder], [baseSalesOrderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesOrder.id,
          addLines: [{ ...baseOrderLineInput, quantity: 0, unitPrice: -1 }],
        },
        commandCtx,
        mockItemQueries([baseItem]),
      ),
    );

    expectErr(result, InvalidOrderLineError);
  });
});
