import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateSupplierInvoiceNumberError,
  InvalidBilledQuantityError,
  PurchaseBillNotDraftError,
  PurchaseBillNotFoundError,
  PurchaseOrderCancelledError,
  PurchaseOrderLineNotFoundError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

interface PurchaseBillLineInput {
  id?: string;
  purchaseOrderLineId?: string | null;
  quantity: string;
  billedUnitPrice?: string;
  taxCode?: string;
  taxableAmount?: string;
  taxAmount?: string;
}

export type UpdatePurchaseBillInput = {
  id: string;
  invoiceNumber?: string;
  invoiceDate?: Date;
  lines?: PurchaseBillLineInput[];
};
export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: Omit<UpdatePurchaseBillInput, "lines"> &
    Partial<CF> & { lines?: (PurchaseBillLineInput & LCF)[] },
  ctx: CommandContext,
) {
  void ctx;
  const { id, invoiceNumber: inputInvoiceNumber, invoiceDate, lines, ...customFields } = input;
  const purchaseBill = await db
    .selectFrom("PurchaseBill")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!purchaseBill) {
    return err(new PurchaseBillNotFoundError(id));
  }
  if (purchaseBill.status !== "DRAFT") {
    return err(new PurchaseBillNotDraftError(id));
  }

  const invoiceNumber = inputInvoiceNumber ?? purchaseBill.invoiceNumber;
  if (
    invoiceNumber !== purchaseBill.invoiceNumber &&
    (await db
      .selectFrom("PurchaseBill")
      .selectAll()
      .where("companyId", "=", purchaseBill.companyId)
      .where("supplierId", "=", purchaseBill.supplierId)
      .where("invoiceNumber", "=", invoiceNumber)
      .executeTakeFirst())
  ) {
    return err(new DuplicateSupplierInvoiceNumberError(invoiceNumber));
  }

  if (lines !== undefined) {
    const poLineIds = [
      ...new Set(lines.map((l) => l.purchaseOrderLineId).filter((v): v is string => v != null)),
    ];
    const orderLines =
      poLineIds.length > 0
        ? await db
            .selectFrom("PurchaseOrderLine")
            .selectAll()
            .where("id", "in", poLineIds)
            .execute()
        : [];
    const orderLineMap = new Map(orderLines.map((ol) => [ol.id, ol]));

    const poIds = [...new Set(orderLines.map((ol) => ol.purchaseOrderId))];
    const purchaseOrders =
      poIds.length > 0
        ? await db.selectFrom("PurchaseOrder").selectAll().where("id", "in", poIds).execute()
        : [];
    const poMap = new Map(purchaseOrders.map((po) => [po.id, po]));

    for (const line of lines) {
      if (new Decimal(line.quantity).lte(0)) {
        return err(new InvalidBilledQuantityError(line.purchaseOrderLineId ?? "unknown"));
      }
      if (line.purchaseOrderLineId == null) continue;

      const orderLine = orderLineMap.get(line.purchaseOrderLineId);
      if (!orderLine) {
        return err(new PurchaseOrderLineNotFoundError(line.purchaseOrderLineId));
      }
      const purchaseOrder = poMap.get(orderLine.purchaseOrderId);
      if (purchaseOrder?.orderStatus === "CANCELLED") {
        return err(new PurchaseOrderCancelledError(purchaseOrder.id));
      }
    }

    await db.deleteFrom("PurchaseBillLine").where("purchaseBillId", "=", id).execute();
    await db
      .insertInto("PurchaseBillLine")
      .values(
        lines.map((line) => {
          const {
            id: _lineId,
            purchaseOrderLineId,
            quantity,
            billedUnitPrice,
            taxCode,
            taxableAmount,
            taxAmount,
            ...lineCustomFields
          } = line;
          const orderLine =
            purchaseOrderLineId != null ? orderLineMap.get(purchaseOrderLineId) : undefined;
          return {
            ...(lineCustomFields as Record<string, unknown>),
            purchaseBillId: id,
            purchaseOrderLineId: purchaseOrderLineId ?? null,
            quantity,
            billedUnitPrice: billedUnitPrice ?? orderLine?.unitPrice ?? null,
            taxCode: taxCode ?? null,
            taxableAmount: taxableAmount ?? null,
            taxAmount: taxAmount ?? null,
            createdAt: new Date(),
            updatedAt: null,
          };
        }),
      )
      .execute();
  }

  const updated = await db
    .updateTable("PurchaseBill")
    .set({
      ...(customFields as Record<string, unknown>),
      invoiceNumber,
      invoiceDate: invoiceDate ?? purchaseBill.invoiceDate,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchaseBill: updated });
}
