import { Decimal } from "decimal.js";
import type { Selectable, Transaction } from "../generated/kysely-tailordb";
import type { defineModule as defineItemManagementModule } from "../../item-management";
type ItemManagementModule = ReturnType<typeof defineItemManagementModule>;
type ItemManagementQueries = ItemManagementModule["queries"];
import {
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PaymentTermInactiveError,
  PurchaseOrderNotDraftError,
  PurchaseOrderNotFoundError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

interface PurchaseOrderLineInput {
  id?: string;
  itemId: string;
  description?: string;
  quantity: string;
  unitPrice: string;
  unitId?: string | null;
  receivingSiteId?: string | null;
  requiresPhysicalReceipt?: boolean;
  matchedPriceRuleId?: string | null;
}

export type UpdatePurchaseOrderInput = {
  id: string;
  orderDate?: Date;
  documentReceivingSiteId?: string | null;
  paymentTermId?: string | null;
  externalSupplierOrderReference?: string | null;
  lines?: PurchaseOrderLineInput[];
};
export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: Omit<UpdatePurchaseOrderInput, "lines"> &
    Partial<CF> & { lines?: (PurchaseOrderLineInput & LCF)[] },
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const {
    id,
    orderDate,
    documentReceivingSiteId,
    paymentTermId,
    externalSupplierOrderReference,
    lines,
    ...customFields
  } = input;
  const existing = await db
    .selectFrom("PurchaseOrder")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!existing) {
    return err(new PurchaseOrderNotFoundError(id));
  }
  if (existing.orderStatus !== "DRAFT") {
    return err(new PurchaseOrderNotDraftError(id));
  }

  const effectiveDocumentReceivingSiteId =
    documentReceivingSiteId === undefined
      ? existing.documentReceivingSiteId
      : documentReceivingSiteId;

  const itemsByLineItemId = new Map<string, { name: string; sku: string | null }>();
  if (lines !== undefined) {
    for (const line of lines) {
      const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
      if (!item) {
        return err(new ItemNotFoundError(line.itemId));
      }
      if (item.status !== "ACTIVE") {
        return err(new ItemNotActiveError(line.itemId));
      }
      itemsByLineItemId.set(line.itemId, { name: item.name, sku: item.sku ?? null });
      if (new Decimal(line.quantity).lte(0)) {
        return err(new InvalidQuantityError(line.itemId));
      }
      if (new Decimal(line.unitPrice).lt(0)) {
        return err(new InvalidUnitPriceError(line.itemId));
      }
      const receivingSiteId = line.receivingSiteId ?? effectiveDocumentReceivingSiteId ?? null;
      if ((line.requiresPhysicalReceipt ?? true) && !receivingSiteId) {
        return err(new ReceivingSiteRequiredError(line.itemId));
      }
      if (
        receivingSiteId &&
        !(await db
          .selectFrom("Site")
          .selectAll()
          .where("id", "=", receivingSiteId)
          .executeTakeFirst())
      ) {
        return err(new ReceivingSiteNotFoundError(receivingSiteId));
      }
    }
  }

  const effectivePaymentTermId =
    paymentTermId === undefined ? existing.paymentTermId : paymentTermId;
  let paymentTermSnapshotLines: Selectable<"PurchaseOrder">["paymentTermSnapshotLines"] = null;
  if (effectivePaymentTermId) {
    const paymentTerm = await db
      .selectFrom("PurchasePaymentTerm")
      .selectAll()
      .where("id", "=", effectivePaymentTermId)
      .executeTakeFirst();
    if (!paymentTerm || (paymentTerm.status as string | null) !== "ACTIVE") {
      return err(new PaymentTermInactiveError(effectivePaymentTermId));
    }
    const termLines = await db
      .selectFrom("PurchasePaymentTermLine")
      .selectAll()
      .where("purchasePaymentTermId", "=", effectivePaymentTermId)
      .execute();
    paymentTermSnapshotLines = termLines;
  }

  const purchaseOrder = await db
    .updateTable("PurchaseOrder")
    .set({
      ...(customFields as Record<string, unknown>),
      orderDate: orderDate ?? existing.orderDate,
      documentReceivingSiteId: effectiveDocumentReceivingSiteId,
      paymentTermId: effectivePaymentTermId,
      externalSupplierOrderReference:
        externalSupplierOrderReference === undefined
          ? existing.externalSupplierOrderReference
          : externalSupplierOrderReference,
      paymentTermSnapshotLines,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  if (lines !== undefined) {
    await db.deleteFrom("PurchaseOrderLine").where("purchaseOrderId", "=", id).execute();
    await db
      .insertInto("PurchaseOrderLine")
      .values(
        lines.map((line) => {
          const {
            id: _lineId,
            itemId: lineItemId,
            description,
            quantity: lineQuantity,
            unitPrice,
            unitId: lineUnitId,
            receivingSiteId: lineReceivingSiteId,
            requiresPhysicalReceipt,
            matchedPriceRuleId,
            ...lineCustomFields
          } = line;
          const receivingSiteId = lineReceivingSiteId ?? effectiveDocumentReceivingSiteId ?? null;
          const itemSnapshot = itemsByLineItemId.get(lineItemId);
          return {
            ...(lineCustomFields as Record<string, unknown>),
            purchaseOrderId: id,
            itemId: lineItemId,
            itemSnapshotName: itemSnapshot?.name ?? null,
            itemSnapshotSku: itemSnapshot?.sku ?? null,
            description: description ?? null,
            quantity: lineQuantity,
            unitPrice,
            unitId: lineUnitId ?? null,
            receivingSiteId,
            requiresPhysicalReceipt: requiresPhysicalReceipt ?? true,
            matchedPriceRuleId: matchedPriceRuleId ?? null,
            createdAt: new Date(),
            updatedAt: null,
          };
        }),
      )
      .execute();
  }

  return ok({ purchaseOrder });
}
