import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidShipmentLineError,
  ShipmentNotEditableError,
  ShipmentNotFoundError,
} from "../lib/errors.generated";
import { encodeJson, type ShipmentLineInput } from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export type UpdateShipmentInput = {
  shipmentId: string;
  shipFromSiteId?: string | null;
  trackingNumber?: string | null;
  shippingAddressSnapshot?: Record<string, unknown> | null;
  lines?: ShipmentLineInput[];
};
export async function run(db: Transaction, input: UpdateShipmentInput, ctx: CommandContext) {
  void ctx;
  const shipment = await db
    .selectFrom("Shipment")
    .selectAll()
    .where("id", "=", input.shipmentId)
    .forUpdate()
    .executeTakeFirst();
  if (!shipment) {
    return err(new ShipmentNotFoundError(input.shipmentId));
  }
  if (shipment.status !== "DRAFT") {
    return err(new ShipmentNotEditableError(input.shipmentId));
  }
  if (input.lines) {
    for (const line of input.lines) {
      const orderLine = await db
        .selectFrom("SalesOrderLine")
        .selectAll()
        .where("id", "=", line.salesOrderLineId)
        .executeTakeFirst();
      if (orderLine?.salesOrderId !== shipment.salesOrderId || line.quantity <= 0) {
        return err(new InvalidShipmentLineError(input.shipmentId));
      }
      if (orderLine.quantity - orderLine.shippedQuantity < line.quantity) {
        return err(new InvalidShipmentLineError(input.shipmentId));
      }
    }
  }
  const updated = await db
    .updateTable("Shipment")
    .set({
      shipFromSiteId:
        input.shipFromSiteId === undefined ? shipment.shipFromSiteId : input.shipFromSiteId,
      trackingNumber:
        input.trackingNumber === undefined ? shipment.trackingNumber : input.trackingNumber,
      shippingAddressSnapshot:
        input.shippingAddressSnapshot === undefined
          ? shipment.shippingAddressSnapshot
          : input.shippingAddressSnapshot
            ? encodeJson(input.shippingAddressSnapshot)
            : null,
      updatedAt: new Date(),
    })
    .where("id", "=", input.shipmentId)
    .returningAll()
    .executeTakeFirst();
  if (input.lines) {
    await db.deleteFrom("ShipmentLine").where("shipmentId", "=", input.shipmentId).execute();
    for (const line of input.lines) {
      await db
        .insertInto("ShipmentLine")
        .values({
          shipmentId: input.shipmentId,
          salesOrderLineId: line.salesOrderLineId,
          quantity: line.quantity,
          unitId: line.unitId ?? null,
          trackingReference: line.trackingReference ?? null,
          requiresInventoryHandoff: line.requiresInventoryHandoff ?? true,
          createdAt: new Date(),
          updatedAt: null,
        })
        .executeTakeFirst();
    }
  }
  return ok({ shipment: updated! });
}
