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

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

export type UpdatePurchaseOrderInput = {
  id: string;
  orderDate?: Date;
  receivingSiteId?: string | null;
  paymentTermId?: string;
  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,
    receivingSiteId,
    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 effectiveReceivingSiteId =
    receivingSiteId === undefined ? existing.receivingSiteId : receivingSiteId;

  if (
    receivingSiteId &&
    !(await db.selectFrom("Site").selectAll().where("id", "=", receivingSiteId).executeTakeFirst())
  ) {
    return err(new ReceivingSiteNotFoundError(receivingSiteId));
  }

  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));
      }
      if (
        (line.requiresPhysicalReceipt ?? true) &&
        !line.receivingSiteId &&
        !effectiveReceivingSiteId
      ) {
        return err(new ReceivingSiteRequiredError(line.itemId));
      }
      if (line.receivingSiteId) {
        if (
          !(await db
            .selectFrom("Site")
            .selectAll()
            .where("id", "=", line.receivingSiteId)
            .executeTakeFirst())
        ) {
          return err(new ReceivingSiteNotFoundError(line.receivingSiteId));
        }
      }
    }
  }

  const effectivePaymentTermId = paymentTermId ?? existing.paymentTermId;
  const paymentTerm = await db
    .selectFrom("PurchasePaymentTerm")
    .selectAll()
    .where("id", "=", effectivePaymentTermId)
    .executeTakeFirst();
  if (!paymentTerm) {
    return err(new PaymentTermNotFoundError(effectivePaymentTermId));
  }
  if (paymentTerm.status !== "ACTIVE") {
    return err(new PaymentTermInactiveError(effectivePaymentTermId));
  }
  const paymentTermSnapshotLines = await db
    .selectFrom("PurchasePaymentTermLine")
    .select([...PAYMENT_TERM_SNAPSHOT_FIELDS])
    .where("purchasePaymentTermId", "=", effectivePaymentTermId)
    .execute();

  const purchaseOrder = await db
    .updateTable("PurchaseOrder")
    .set({
      ...(customFields as Record<string, unknown>),
      orderDate: orderDate ?? existing.orderDate,
      receivingSiteId: effectiveReceivingSiteId,
      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 {
            itemId: lineItemId,
            quantity: lineQuantity,
            unitPrice,
            unitId: lineUnitId,
            receivingSiteId: lineReceivingSiteId,
            requiresPhysicalReceipt,
            ...lineCustomFields
          } = line;
          const itemSnapshot = itemsByLineItemId.get(lineItemId);
          return {
            ...(lineCustomFields as Record<string, unknown>),
            purchaseOrderId: id,
            itemId: lineItemId,
            itemSnapshotName: itemSnapshot?.name ?? null,
            itemSnapshotSku: itemSnapshot?.sku ?? null,

            quantity: lineQuantity,
            unitPrice,
            unitId: lineUnitId,
            receivingSiteId: lineReceivingSiteId ?? null,
            requiresPhysicalReceipt: requiresPhysicalReceipt ?? true,
            createdAt: new Date(),
            updatedAt: null,
          };
        }),
      )
      .execute();
  }

  return ok({ purchaseOrder });
}
