import type { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateSupplierInvoiceNumberError,
  InvalidBilledQuantityError,
  PurchaseBillNotDraftError,
  PurchaseBillNotFoundError,
  PurchaseOrderCancelledError,
  PurchaseOrderLineNotFoundError,
} from "../lib/errors.generated";
import {
  decodeJson,
  encodeJson,
  findOrderLine,
  findPurchaseBillDuplicate,
  normalizeBillLines,
  type PurchaseBillLineInput,
} from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

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

  const invoiceNumber = input.invoiceNumber ?? purchaseBill.invoiceNumber;
  if (
    invoiceNumber !== purchaseBill.invoiceNumber &&
    (await findPurchaseBillDuplicate(
      db,
      purchaseBill.companyId,
      purchaseBill.supplierId,
      invoiceNumber,
    ))
  ) {
    return err(new DuplicateSupplierInvoiceNumberError(invoiceNumber));
  }

  const purchaseOrders = await db.selectFrom("PurchaseOrder").selectAll().execute();
  const lines: ReturnType<typeof normalizeBillLines> = [];
  for (const line of normalizeBillLines(
    input.lines ?? decodeJson<PurchaseBillLineInput[]>(purchaseBill.lines, []),
  )) {
    const match = findOrderLine(purchaseOrders, line.purchaseOrderLineId);
    if (!match) {
      return err(new PurchaseOrderLineNotFoundError(line.purchaseOrderLineId));
    }
    if (line.quantity <= 0) {
      return err(new InvalidBilledQuantityError(line.purchaseOrderLineId));
    }
    if (match.order.status === "CANCELLED") {
      return err(new PurchaseOrderCancelledError(String(match.order.id)));
    }
    lines.push({
      ...line,
      billedUnitPrice: line.billedUnitPrice ?? match.line.unitPrice,
    });
  }

  const updated = await db
    .updateTable("PurchaseBill")
    .set({
      invoiceNumber,
      invoiceDate: input.invoiceDate ?? purchaseBill.invoiceDate,
      lines: encodeJson(lines),
      updatedAt: new Date(),
    })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchaseBill: updated });
}
