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

/** PO-linked bill line — billedUnitPrice and unitId default from the PO line when omitted */
interface POLinkedPurchaseBillLineInput {
  id?: string;
  purchaseOrderLineId: string;
  quantity: string;
  billedUnitPrice?: string;
  unitId?: string;
  taxCode?: string;
  taxableAmount?: string;
  taxAmount?: string;
}

/** PO-unlinked bill line — unitId must be provided explicitly */
interface POUnlinkedPurchaseBillLineInput {
  id?: string;
  purchaseOrderLineId: null;
  quantity: string;
  billedUnitPrice?: string;
  unitId: string;
  taxCode?: string;
  taxableAmount?: string;
  taxAmount?: string;
}

type PurchaseBillLineInput = POLinkedPurchaseBillLineInput | POUnlinkedPurchaseBillLineInput;

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]));

    const insertValues: Insertable<"PurchaseBillLine">[] = [];
    for (const line of lines) {
      const {
        id: _lineId,
        purchaseOrderLineId,
        quantity,
        billedUnitPrice,
        unitId,
        taxCode,
        taxableAmount,
        taxAmount,
        ...lineCustomFields
      } = line;

      if (new Decimal(quantity).lte(0)) {
        return err(new InvalidBilledQuantityError(purchaseOrderLineId ?? "unknown"));
      }

      const orderLine =
        purchaseOrderLineId != null ? orderLineMap.get(purchaseOrderLineId) : undefined;

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

      const resolvedUnitId = unitId ?? orderLine?.unitId;
      if (resolvedUnitId == null) {
        return err(new PurchaseOrderLineNotFoundError(purchaseOrderLineId ?? "unknown"));
      }

      insertValues.push({
        ...lineCustomFields,
        purchaseBillId: id,
        purchaseOrderLineId: purchaseOrderLineId ?? null,
        quantity,
        billedUnitPrice: billedUnitPrice ?? orderLine?.unitPrice ?? null,
        unitId: resolvedUnitId,
        taxCode: taxCode ?? null,
        taxableAmount: taxableAmount ?? null,
        taxAmount: taxAmount ?? null,
        createdAt: new Date(),
        updatedAt: null,
      });
    }

    await db.deleteFrom("PurchaseBillLine").where("purchaseBillId", "=", id).execute();
    await db.insertInto("PurchaseBillLine").values(insertValues).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 });
}
