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

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

export interface CreatePurchaseOrderInput {
  companyId: string;
  supplierId: string;
  orderDate: Date;
  receivingSiteId?: string | null;
  paymentTermId: string;
  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,
    receivingSiteId,
    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));
  }

  const paymentTerm = await db
    .selectFrom("PurchasePaymentTerm")
    .selectAll()
    .where("id", "=", paymentTermId)
    .executeTakeFirst();
  if (!paymentTerm) {
    return err(new PaymentTermNotFoundError(paymentTermId));
  }
  if (paymentTerm.status !== "ACTIVE") {
    return err(new PaymentTermInactiveError(paymentTermId));
  }
  const paymentTermSnapshotLines = await db
    .selectFrom("PurchasePaymentTermLine")
    .select([...PAYMENT_TERM_SNAPSHOT_FIELDS])
    .where("purchasePaymentTermId", "=", paymentTermId)
    .execute();

  if (
    receivingSiteId &&
    !(await db.selectFrom("Site").selectAll().where("id", "=", receivingSiteId).executeTakeFirst())
  ) {
    return err(new ReceivingSiteNotFoundError(receivingSiteId));
  }

  const itemsByLineItemId = new Map<string, { name: string; sku: string }>();
  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,
    });
    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));
    }

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

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

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

          quantity,
          unitPrice,
          unitId,
          receivingSiteId: lineReceivingSiteId ?? null,
          requiresPhysicalReceipt: requiresPhysicalReceipt ?? true,
        };
      }),
    )
    .executeTakeFirstOrThrow();

  return ok({ purchaseOrder: purchaseOrder });
}
