import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DescriptionRequiredError,
  EmptyPurchaseRequisitionLinesError,
  InvalidEstimatedUnitPriceError,
  InvalidQuantityError,
  ItemNotFoundError,
  PartnerNotSupplierError,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import type { BusinessPartnerQueries, ItemManagementQueries } from "../module";

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

export interface PurchaseRequisitionHeaderInput {
  companyId: string;
  requesterId: string;
  justification?: string | null;
  desiredDeliveryDate?: Date | null;
}

export interface CreatePurchaseRequisitionInput {
  header: PurchaseRequisitionHeaderInput;
  lines: PurchaseRequisitionLineInput[];
}

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: {
    header: PurchaseRequisitionHeaderInput & CF;
    lines: (PurchaseRequisitionLineInput & LCF)[];
  },
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const { header, lines } = input;
  const { companyId, requesterId, justification, desiredDeliveryDate } = header;
  if (lines.length === 0) {
    return err(new EmptyPurchaseRequisitionLinesError(companyId));
  }

  for (const line of lines) {
    if (!line.description) {
      return err(new DescriptionRequiredError(String(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 itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
      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({
      // Header input first (incl. custom fields) so command-set columns always win.
      ...(header as Record<string, unknown>),
      companyId,
      requesterId,
      status: "DRAFT",
      justification: justification ?? null,
      desiredDeliveryDate: desiredDeliveryDate ?? null,
      rejectionReason: null,
      closeReason: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  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,
          unitId,
          suggestedSupplierId: suggestedSupplierId ?? null,
          needByDate: needByDate ?? null,
        };
      }),
    )
    .execute();

  return ok({ purchaseRequisition });
}
