import { Decimal } from "decimal.js";
import type { Selectable, Transaction } from "../generated/kysely-tailordb";
import type { defineModule as defineBusinessPartnerModule } from "../../business-partner";
import type { defineModule as defineItemManagementModule } from "../../item-management";
type BusinessPartnerModule = ReturnType<typeof defineBusinessPartnerModule>;
type BusinessPartnerQueries = BusinessPartnerModule["queries"];
type ItemManagementModule = ReturnType<typeof defineItemManagementModule>;
type ItemManagementQueries = ItemManagementModule["queries"];
import {
  EmptyPurchaseOrderLinesError,
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PartnerNotSupplierError,
  PaymentTermInactiveError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

export interface PurchaseOrderLineInput {
  itemId: string;
  description?: string;
  quantity: string;
  unitPrice: string;
  unitId?: string | null;
  receivingSiteId?: string | null;
  requiresPhysicalReceipt?: boolean;
  matchedPriceRuleId?: string | null;
}

export interface CreatePurchaseOrderInput {
  companyId: string;
  supplierId: string;
  orderDate?: Date;
  documentReceivingSiteId?: string | null;
  paymentTermId?: string | null;
  externalSupplierOrderReference?: string;
  lines: PurchaseOrderLineInput[];
}

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: Omit<CreatePurchaseOrderInput, "lines"> & CF & { lines: (PurchaseOrderLineInput & LCF)[] },
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const {
    companyId,
    supplierId,
    orderDate,
    documentReceivingSiteId,
    paymentTermId,
    externalSupplierOrderReference,
    lines,
    ...customFields
  } = input;
  const { partner: supplier } = (
    await businessPartnerQueries.getPartner(db, { id: supplierId }, ctx)
  ).value;
  if (!supplier) {
    return err(new SupplierNotFoundError(supplierId));
  }
  if (supplier.status !== "ACTIVE") {
    return err(new SupplierNotActiveError(supplierId));
  }
  const { partnerRole } = (
    await businessPartnerQueries.getPartnerRole(
      db,
      { partnerId: supplierId, role: "SUPPLIER" },
      ctx,
    )
  ).value;
  if (!partnerRole) {
    return err(new PartnerNotSupplierError(supplierId));
  }
  if (lines.length === 0) {
    return err(new EmptyPurchaseOrderLinesError(companyId));
  }

  let selectedPaymentTermId = paymentTermId ?? null;
  if (!selectedPaymentTermId) {
    const supplierProfile = await db
      .selectFrom("SupplierProfile")
      .selectAll()
      .where("companyId", "=", companyId)
      .where("supplierId", "=", supplierId)
      .executeTakeFirst();
    selectedPaymentTermId = supplierProfile?.defaultPurchasePaymentTermId ?? null;
  }

  let paymentTermSnapshotLines: Selectable<"PurchaseOrder">["paymentTermSnapshotLines"] = null;
  if (selectedPaymentTermId) {
    const paymentTerm = await db
      .selectFrom("PurchasePaymentTerm")
      .selectAll()
      .where("id", "=", selectedPaymentTermId)
      .executeTakeFirst();
    if (paymentTerm && (paymentTerm.status as string | null) !== "ACTIVE") {
      return err(new PaymentTermInactiveError(selectedPaymentTermId));
    }
    if (paymentTerm) {
      const termLines = await db
        .selectFrom("PurchasePaymentTermLine")
        .selectAll()
        .where("purchasePaymentTermId", "=", selectedPaymentTermId)
        .execute();
      paymentTermSnapshotLines = termLines;
    }
  }

  const itemsByLineItemId = new Map<string, { name: string; sku: string | null }>();
  for (const line of lines) {
    const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }
    if (item.status !== "ACTIVE") {
      return err(new ItemNotActiveError(line.itemId));
    }
    itemsByLineItemId.set(line.itemId, { name: item.name, sku: item.sku ?? null });
    if (new Decimal(line.quantity).lte(0)) {
      return err(new InvalidQuantityError(line.itemId));
    }
    if (new Decimal(line.unitPrice).lt(0)) {
      return err(new InvalidUnitPriceError(line.itemId));
    }

    const receivingSiteId = line.receivingSiteId ?? documentReceivingSiteId ?? null;
    if ((line.requiresPhysicalReceipt ?? true) && !receivingSiteId) {
      return err(new ReceivingSiteRequiredError(line.itemId));
    }
    if (receivingSiteId) {
      const site = await db
        .selectFrom("Site")
        .selectAll()
        .where("id", "=", receivingSiteId)
        .executeTakeFirst();
      if (!site) {
        return err(new ReceivingSiteNotFoundError(receivingSiteId));
      }
    }
  }

  const purchaseOrder = await db
    .insertInto("PurchaseOrder")
    .values({
      ...(customFields as Record<string, unknown>),
      companyId,
      supplierId,
      documentReceivingSiteId: documentReceivingSiteId ?? null,
      paymentTermId: selectedPaymentTermId,
      orderStatus: "DRAFT",
      receiptStatus: null,
      billingStatus: null,
      orderDate: orderDate ?? new Date(),
      externalSupplierOrderReference: externalSupplierOrderReference ?? null,
      supplierSnapshotName: supplier.name,
      paymentTermSnapshotLines,
      rejectionReason: null,
      closeReason: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  await db
    .insertInto("PurchaseOrderLine")
    .values(
      lines.map((line) => {
        const {
          itemId,
          description,
          quantity,
          unitPrice,
          unitId,
          receivingSiteId: lineReceivingSiteId,
          requiresPhysicalReceipt,
          matchedPriceRuleId,
          ...lineCustomFields
        } = line;
        const receivingSiteId = lineReceivingSiteId ?? documentReceivingSiteId ?? null;
        const itemSnapshot = itemsByLineItemId.get(itemId);
        return {
          ...(lineCustomFields as Record<string, unknown>),
          purchaseOrderId: purchaseOrder!.id,
          itemId,
          itemSnapshotName: itemSnapshot?.name ?? null,
          itemSnapshotSku: itemSnapshot?.sku ?? null,
          description: description ?? null,
          quantity,
          unitPrice,
          unitId: unitId ?? null,
          receivingSiteId,
          requiresPhysicalReceipt: requiresPhysicalReceipt ?? true,
          matchedPriceRuleId: matchedPriceRuleId ?? null,
          createdAt: new Date(),
          updatedAt: null,
        };
      }),
    )
    .execute();

  return ok({ purchaseOrder: purchaseOrder! });
}
