/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, no-useless-assignment, prefer-const */
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PaymentTermInactiveError,
  PurchaseOrderNotDraftError,
  PurchaseOrderNotFoundError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
} from "../lib/errors.generated";
import {
  buildPaymentTermSnapshot,
  decodeJson,
  encodeJson,
  isActiveStatus,
  normalizeOrderLines,
  selectOne,
  type PurchaseOrderLineInput,
} from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export type UpdatePurchaseOrderInput = {
  id: string;
  orderDate?: Date;
  documentReceivingSiteId?: string | null;
  paymentTermId?: string | null;
  externalSupplierOrderReference?: string | null;
  lines?: PurchaseOrderLineInput[];
};
export async function run(db: Transaction, input: UpdatePurchaseOrderInput, ctx: CommandContext) {
  void ctx;
  const existing = await selectOne(db, "PurchaseOrder", "id", input.id, true);
  if (!existing) {
    return err(new PurchaseOrderNotFoundError(input.id));
  }
  if (existing.status !== "DRAFT") {
    return err(new PurchaseOrderNotDraftError(input.id));
  }

  const previousLines = decodeJson<PurchaseOrderLineInput[]>(existing.lines, []);
  const nextLines = normalizeOrderLines(
    (input.lines ?? previousLines).map((line) => {
      const previous = previousLines.find((candidate) => candidate.id === line.id);
      return {
        ...previous,
        ...line,
        matchedPriceRuleId: line.matchedPriceRuleId ?? previous?.matchedPriceRuleId ?? null,
        priceSnapshot: previous?.priceSnapshot ?? line.priceSnapshot ?? null,
        receivingSiteId:
          line.receivingSiteId ??
          input.documentReceivingSiteId ??
          previous?.receivingSiteId ??
          existing.documentReceivingSiteId,
      };
    }),
  );

  for (const line of nextLines) {
    const item = await selectOne(db, "Item", "id", line.itemId);
    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }
    if (!isActiveStatus(item.status as string | null)) {
      return err(new ItemNotActiveError(line.itemId));
    }
    if (line.quantity <= 0) {
      return err(new InvalidQuantityError(line.itemId));
    }
    if (line.unitPrice < 0) {
      return err(new InvalidUnitPriceError(line.itemId));
    }
    if ((line.requiresPhysicalReceipt ?? true) && !line.receivingSiteId) {
      return err(new ReceivingSiteRequiredError(line.itemId));
    }
    if (line.receivingSiteId && !(await selectOne(db, "Site", "id", line.receivingSiteId))) {
      return err(new ReceivingSiteNotFoundError(line.receivingSiteId));
    }
  }

  let paymentTermId =
    input.paymentTermId === undefined
      ? (existing.paymentTermId as string | null)
      : input.paymentTermId;
  let paymentTermSnapshot = existing.paymentTermSnapshot as string | null;
  if (paymentTermId) {
    const paymentTerm = await selectOne(db, "PurchasePaymentTerm", "id", paymentTermId);
    if (!paymentTerm || !isActiveStatus(paymentTerm.status as string | null)) {
      return err(new PaymentTermInactiveError(paymentTermId));
    }
    paymentTermSnapshot = encodeJson(buildPaymentTermSnapshot(paymentTerm));
  } else {
    paymentTermSnapshot = null;
  }

  const purchaseOrder = await db
    .updateTable("PurchaseOrder")
    .set({
      orderDate: input.orderDate ?? existing.orderDate,
      documentReceivingSiteId:
        input.documentReceivingSiteId === undefined
          ? existing.documentReceivingSiteId
          : input.documentReceivingSiteId,
      paymentTermId,
      externalSupplierOrderReference:
        input.externalSupplierOrderReference === undefined
          ? existing.externalSupplierOrderReference
          : input.externalSupplierOrderReference,
      lines: encodeJson(nextLines),
      paymentTermSnapshot,
      updatedAt: new Date(),
    })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchaseOrder });
}
