import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";
import type { defineModule as defineBusinessPartnerModule } from "../../business-partner";
type BusinessPartnerModule = ReturnType<typeof defineBusinessPartnerModule>;
type BusinessPartnerQueries = BusinessPartnerModule["queries"];
import {
  DescriptionRequiredError,
  EmptyPurchaseRequisitionLinesError,
  InvalidEstimatedUnitPriceError,
  InvalidQuantityError,
  ItemNotFoundError,
  PartnerNotSupplierError,
  PurchaseRequisitionNotDraftError,
  PurchaseRequisitionNotFoundError,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import type { PurchaseRequisitionLineInput } from "./createPurchaseRequisition";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

export type UpdatePurchaseRequisitionInput = {
  id: string;
  justification?: string | null;
  desiredDeliveryDate?: Date | null;
  lines?: PurchaseRequisitionLineInput[];
};

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: Omit<UpdatePurchaseRequisitionInput, "lines"> &
    Partial<CF> & { lines?: (PurchaseRequisitionLineInput & LCF)[] },
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
) {
  const { id, justification, desiredDeliveryDate, lines, ...customFields } = input;
  const existing = await db
    .selectFrom("PurchaseRequisition")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!existing) {
    return err(new PurchaseRequisitionNotFoundError(id));
  }
  if (existing.status !== "DRAFT") {
    return err(new PurchaseRequisitionNotDraftError(id));
  }

  if (lines !== undefined) {
    if (lines.length === 0) {
      return err(new EmptyPurchaseRequisitionLinesError(id));
    }

    for (const line of lines) {
      if (!line.description) {
        return err(new DescriptionRequiredError(line.description ?? ""));
      }
      if (new Decimal(line.quantity).lte(0)) {
        return err(new InvalidQuantityError(line.description));
      }
      if (new Decimal(line.estimatedUnitPrice).lt(0)) {
        return err(new InvalidEstimatedUnitPriceError(line.description));
      }
      if (line.itemId) {
        const item = await db
          .selectFrom("Item")
          .selectAll()
          .where("id", "=", line.itemId)
          .executeTakeFirst();
        if (!item) {
          return err(new ItemNotFoundError(line.itemId));
        }
      }
      if (line.suggestedSupplierId) {
        const { partner: supplier } = (
          await businessPartnerQueries.getPartner(db, { id: line.suggestedSupplierId }, ctx)
        ).value;
        if (!supplier) {
          return err(new SupplierNotFoundError(line.suggestedSupplierId));
        }
        if (supplier.status !== "ACTIVE") {
          return err(new SupplierNotActiveError(line.suggestedSupplierId));
        }
        const { partnerRole } = (
          await businessPartnerQueries.getPartnerRole(
            db,
            { partnerId: line.suggestedSupplierId, role: "SUPPLIER" },
            ctx,
          )
        ).value;
        if (!partnerRole) {
          return err(new PartnerNotSupplierError(line.suggestedSupplierId));
        }
      }
    }
  }

  const purchaseRequisition = await db
    .updateTable("PurchaseRequisition")
    .set({
      ...(customFields as Record<string, unknown>),
      justification: justification === undefined ? existing.justification : justification,
      desiredDeliveryDate:
        desiredDeliveryDate === undefined ? existing.desiredDeliveryDate : desiredDeliveryDate,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  if (lines !== undefined) {
    await db
      .deleteFrom("PurchaseRequisitionLine")
      .where("purchaseRequisitionId", "=", id)
      .execute();

    await db
      .insertInto("PurchaseRequisitionLine")
      .values(
        lines.map((line) => {
          const {
            description,
            quantity,
            estimatedUnitPrice,
            itemId,
            unitId,
            suggestedSupplierId,
            needByDate,
            ...lineCustomFields
          } = line;
          return {
            ...(lineCustomFields as Record<string, unknown>),
            purchaseRequisitionId: id,
            description,
            quantity,
            estimatedUnitPrice,
            itemId: itemId ?? null,
            unitId: unitId ?? null,
            suggestedSupplierId: suggestedSupplierId ?? null,
            needByDate: needByDate ?? null,
            createdAt: new Date(),
            updatedAt: null,
          };
        }),
      )
      .execute();
  }

  return ok({ purchaseRequisition });
}
