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,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

export interface PurchaseRequisitionLineInput {
  description: string;
  quantity: string;
  estimatedUnitPrice: string;
  itemId?: string | null;
  unitId?: string | null;
  suggestedSupplierId?: string | null;
  needByDate?: Date | null;
}

export interface CreatePurchaseRequisitionInput {
  companyId: string;
  requesterId: 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<CreatePurchaseRequisitionInput, "lines"> &
    CF & { lines: (PurchaseRequisitionLineInput & LCF)[] },
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
) {
  const { companyId, requesterId, justification, desiredDeliveryDate, lines, ...customFields } =
    input;
  if (lines.length === 0) {
    return err(new EmptyPurchaseRequisitionLinesError(companyId));
  }

  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
    .insertInto("PurchaseRequisition")
    .values({
      ...(customFields as Record<string, unknown>),
      companyId,
      requesterId,
      status: "DRAFT",
      justification: justification ?? null,
      desiredDeliveryDate: desiredDeliveryDate ?? null,
      rejectionReason: null,
      closeReason: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  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: purchaseRequisition!.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: purchaseRequisition! });
}
