import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  EmptyShipmentLinesError,
  InvalidShipmentLineError,
  LineNotFoundError,
  ShipmentNotEditableError,
  ShipmentNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseSalesOrderLine,
  baseShipment,
  baseShipmentLine,
  postedShipment,
} from "../testing/fixtures";
import { run } from "./updateShipment";

describe("updateShipment", () => {
  it("updates draft shipment metadata", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
      [{ ...baseShipment, trackingNumber: "TRACK-1", updatedAt: new Date() }], // updateTable Shipment
    );

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseShipment.id, headerPatch: { trackingNumber: "TRACK-1" } }, commandCtx),
    );

    expectOk(result);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({ trackingNumber: "TRACK-1" }),
    );
  });

  it("updates a line in place so its id and createdAt survive the edit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderLine = { ...baseSalesOrderLine, salesOrderId: baseShipment.salesOrderId };
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
      [orderLine], // SalesOrderLine bulk fetch (quantity change validates remaining shippable)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseShipment.id,
          updateLines: [{ lineId: baseShipmentLine.id, linePatch: { quantity: 3 } }],
        },
        commandCtx,
      ),
    );

    expectOk(result);
    // Patched in place — neither deleted nor reinserted.
    expect(mock.deletes).toHaveLength(0);
    expect(mock.inserts).toHaveLength(0);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ quantity: 3 }));
  });

  it("adds a shipment line via addLines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderLine = { ...baseSalesOrderLine, salesOrderId: baseShipment.salesOrderId };
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
      [orderLine], // SalesOrderLine bulk fetch
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseShipment.id,
          addLines: [{ salesOrderLineId: orderLine.id, quantity: 2 }],
        },
        commandCtx,
      ),
    );

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

  it("removes a shipment line via removeLineIds", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const secondLine = { ...baseShipmentLine, id: "shipment-line-2" };
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine, secondLine], // ShipmentLine (current lines)
    );

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

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

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

    const result = await mock.withTx((trx) => run(trx, { id: "missing-shipment" }, commandCtx));

    expectErr(result, ShipmentNotFoundError);
  });

  it("returns error when updating a posted shipment", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([postedShipment]);

    const result = await mock.withTx((trx) => run(trx, { id: postedShipment.id }, commandCtx));

    expectErr(result, ShipmentNotEditableError);
  });

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

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

    expectErr(result, LineNotFoundError);
  });

  it("returns error when a removal would leave the shipment without lines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
    );

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

    expectErr(result, EmptyShipmentLinesError);
  });

  it("returns error when an added line exceeds remaining order quantity", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderLine = {
      ...baseSalesOrderLine,
      salesOrderId: baseShipment.salesOrderId,
      quantity: 10,
      shippedQuantity: 8,
    };
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
      [orderLine], // SalesOrderLine bulk fetch
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseShipment.id,
          addLines: [{ salesOrderLineId: orderLine.id, quantity: 999 }],
        },
        commandCtx,
      ),
    );

    expectErr(result, InvalidShipmentLineError);
  });

  it("returns error when an added line references an invalid order line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseShipment], // Shipment
      [baseShipmentLine], // ShipmentLine (current lines)
      [], // SalesOrderLine bulk fetch (empty)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseShipment.id,
          addLines: [{ salesOrderLineId: "missing-order-line", quantity: 1 }],
        },
        commandCtx,
      ),
    );

    expectErr(result, InvalidShipmentLineError);
  });
});
