import type { Transaction } from "../generated/kysely-tailordb";
import {
  CrossCompanyReferenceError,
  EmptyOrderNotAllowedError,
  InvalidOrderLineError,
  ItemNotActiveError,
  ItemNotFoundError,
  OrderNotEditableError,
  SalesOrderNotFoundError,
} from "../lib/errors.generated";
import { encodeJson, type SalesOrderLineInput } from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export type UpdateSalesOrderInput = {
  salesOrderId: string;
  customerId?: string;
  paymentTermId?: string | null;
  channelId?: string | null;
  requestedShipDate?: Date | null;
  shippingAddressSnapshot?: Record<string, unknown> | null;
  billingAddressSnapshot?: Record<string, unknown> | null;
  lines?: SalesOrderLineInput[];
};
export async function run(db: Transaction, input: UpdateSalesOrderInput, ctx: CommandContext) {
  void ctx;
  const salesOrder = await db
    .selectFrom("SalesOrder")
    .selectAll()
    .where("id", "=", input.salesOrderId)
    .forUpdate()
    .executeTakeFirst();
  if (!salesOrder) {
    return err(new SalesOrderNotFoundError(input.salesOrderId));
  }
  if (salesOrder.status !== "DRAFT") {
    return err(new OrderNotEditableError(input.salesOrderId));
  }
  if (input.lines?.length === 0) {
    return err(new EmptyOrderNotAllowedError(input.salesOrderId));
  }
  if (input.lines) {
    for (const line of input.lines) {
      if (!line.itemId || line.quantity <= 0 || line.unitPrice < 0 || !line.currencyId) {
        return err(new InvalidOrderLineError(input.salesOrderId));
      }
      const item = await db
        .selectFrom("Item")
        .selectAll()
        .where("id", "=", line.itemId)
        .executeTakeFirst();
      if (!item) {
        return err(new ItemNotFoundError(line.itemId));
      }
      if (item.status !== "ACTIVE") {
        return err(new ItemNotActiveError(line.itemId));
      }
      if (item.companyId && item.companyId !== salesOrder.companyId) {
        return err(new CrossCompanyReferenceError(line.itemId));
      }
    }
  }

  const updated = await db
    .updateTable("SalesOrder")
    .set({
      customerId: input.customerId ?? salesOrder.customerId,
      paymentTermId:
        input.paymentTermId === undefined ? salesOrder.paymentTermId : input.paymentTermId,
      channelId: input.channelId === undefined ? salesOrder.channelId : input.channelId,
      requestedShipDate:
        input.requestedShipDate === undefined
          ? salesOrder.requestedShipDate
          : input.requestedShipDate,
      shippingAddressSnapshot:
        input.shippingAddressSnapshot === undefined
          ? salesOrder.shippingAddressSnapshot
          : input.shippingAddressSnapshot
            ? encodeJson(input.shippingAddressSnapshot)
            : null,
      billingAddressSnapshot:
        input.billingAddressSnapshot === undefined
          ? salesOrder.billingAddressSnapshot
          : input.billingAddressSnapshot
            ? encodeJson(input.billingAddressSnapshot)
            : null,
      updatedAt: new Date(),
    })
    .where("id", "=", input.salesOrderId)
    .returningAll()
    .executeTakeFirst();

  if (input.lines) {
    await db.deleteFrom("SalesOrderLine").where("salesOrderId", "=", input.salesOrderId).execute();
    for (const line of input.lines) {
      await db
        .insertInto("SalesOrderLine")
        .values({
          salesOrderId: input.salesOrderId,
          itemId: line.itemId,
          description: line.description ?? null,
          quantity: line.quantity,
          unitPrice: line.unitPrice,
          currencyId: line.currencyId,
          unitId: line.unitId ?? null,
          matchedPriceRuleId: line.matchedPriceRuleId ?? null,
          billingPolicy: line.billingPolicy ?? "ORDER",
          requestedShipDate: line.requestedShipDate ?? null,
          requiresPhysicalFulfillment: line.requiresPhysicalFulfillment ?? true,
          priceSnapshot: line.priceSnapshot ? encodeJson(line.priceSnapshot) : null,
          shippedQuantity: line.shippedQuantity ?? 0,
          invoicedQuantity: line.invoicedQuantity ?? 0,
          createdAt: new Date(),
          updatedAt: null,
        })
        .executeTakeFirst();
    }
  }

  return ok({ salesOrder: updated! });
}
