import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyScopeMismatchError,
  EmptyReceiptLinesError,
  InvalidReceivedQuantityError,
  NoOpenReceiptQuantityError,
  PurchaseOrderLineNotFoundError,
  PurchaseOrderNotReceivableError,
  CompanyOrSupplierMismatchError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

export interface GoodsReceiptLineInput {
  purchaseOrderLineId?: string | null;
  quantity: string;
  itemId?: string;
  unitId?: string | null;
  requiresInventoryHandoff?: boolean;
  destinationSite?: string | null;
  destinationStorageLocation?: string | null;
}

export interface CreateGoodsReceiptInput {
  companyId: string;
  supplierId: string;
  receiptDate?: Date;
  lines: GoodsReceiptLineInput[];
}

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: Omit<CreateGoodsReceiptInput, "lines"> & CF & { lines: (GoodsReceiptLineInput & LCF)[] },
  ctx: CommandContext,
) {
  void ctx;
  const { companyId, supplierId, receiptDate, lines, ...customFields } = input;
  if (lines.length === 0) {
    return err(new EmptyReceiptLinesError("create"));
  }

  // Separate PO-linked and PO-unlinked lines
  const poLinkedInputLines = lines.filter((l) => l.purchaseOrderLineId != null);
  const poLineIds = [...new Set(poLinkedInputLines.map((l) => l.purchaseOrderLineId!))];

  // Fetch all referenced PO lines in one query
  const orderLines =
    poLineIds.length > 0
      ? await db.selectFrom("PurchaseOrderLine").selectAll().where("id", "in", poLineIds).execute()
      : [];

  const orderLineMap = new Map(orderLines.map((ol) => [ol.id, ol]));

  // Collect unique purchaseOrderIds from the fetched PO lines
  const poIds = [...new Set(orderLines.map((ol) => ol.purchaseOrderId))];

  // Fetch all referenced POs in one query
  const purchaseOrders =
    poIds.length > 0
      ? await db.selectFrom("PurchaseOrder").selectAll().where("id", "in", poIds).execute()
      : [];

  const poMap = new Map(purchaseOrders.map((po) => [po.id, po]));

  // Compute already-received quantities from POSTED GoodsReceipts (bulk)
  const alreadyReceivedByPoLineId = new Map<string, Decimal>();
  if (poLineIds.length > 0) {
    const existingGRLines = await db
      .selectFrom("GoodsReceiptLine")
      .selectAll()
      .where("purchaseOrderLineId", "in", poLineIds)
      .execute();

    const postedGRIds = new Set(
      (
        await db.selectFrom("GoodsReceipt").selectAll().where("status", "=", "POSTED").execute()
      ).map((r) => r.id),
    );

    for (const grl of existingGRLines) {
      if (grl.purchaseOrderLineId && postedGRIds.has(grl.goodsReceiptId)) {
        alreadyReceivedByPoLineId.set(
          grl.purchaseOrderLineId,
          (alreadyReceivedByPoLineId.get(grl.purchaseOrderLineId) ?? new Decimal(0)).plus(
            grl.quantity,
          ),
        );
      }
    }
  }

  const normalizedLines: {
    purchaseOrderLineId: string | null;
    quantity: string;
    itemId: string | null;
    unitId: string | null;
    requiresInventoryHandoff: boolean;
    destinationSite: string | null;
    destinationStorageLocation: string | null;
    lineCustomFields: Record<string, unknown>;
  }[] = [];

  for (const line of lines) {
    const {
      purchaseOrderLineId,
      quantity,
      itemId,
      unitId,
      requiresInventoryHandoff,
      destinationSite,
      destinationStorageLocation,
      ...lineCustomFields
    } = line;
    if (new Decimal(quantity).lte(0)) {
      return err(new InvalidReceivedQuantityError(purchaseOrderLineId ?? "unknown"));
    }

    if (purchaseOrderLineId == null) {
      // PO-unlinked line
      normalizedLines.push({
        purchaseOrderLineId: null,
        quantity,
        itemId: itemId ?? null,
        unitId: unitId ?? null,
        requiresInventoryHandoff: requiresInventoryHandoff ?? false,
        destinationSite: destinationSite ?? null,
        destinationStorageLocation: destinationStorageLocation ?? null,
        lineCustomFields,
      });
      continue;
    }

    const orderLine = orderLineMap.get(purchaseOrderLineId);
    if (!orderLine) {
      return err(new PurchaseOrderLineNotFoundError(purchaseOrderLineId));
    }

    const po = poMap.get(orderLine.purchaseOrderId);
    if (!po) {
      return err(new PurchaseOrderLineNotFoundError(purchaseOrderLineId));
    }

    // Validate company and supplier match
    if (po.companyId !== companyId) {
      return err(new CompanyScopeMismatchError(purchaseOrderLineId));
    }
    if (po.supplierId !== supplierId) {
      return err(new CompanyOrSupplierMismatchError(purchaseOrderLineId));
    }

    // Validate PO is receivable
    if (["CANCELLED", "CLOSED"].includes(po.orderStatus as string)) {
      return err(new PurchaseOrderNotReceivableError(po.id));
    }

    // Check open quantity
    const openQuantity = new Decimal(orderLine.quantity).minus(
      alreadyReceivedByPoLineId.get(purchaseOrderLineId) ?? new Decimal(0),
    );
    if (openQuantity.lte(0)) {
      return err(new NoOpenReceiptQuantityError(purchaseOrderLineId));
    }

    normalizedLines.push({
      purchaseOrderLineId,
      quantity,
      itemId: itemId ?? orderLine.itemId,
      unitId: unitId ?? orderLine.unitId ?? null,
      requiresInventoryHandoff: requiresInventoryHandoff ?? orderLine.requiresPhysicalReceipt,
      destinationSite: destinationSite ?? orderLine.receivingSiteId ?? null,
      destinationStorageLocation: destinationStorageLocation ?? null,
      lineCustomFields,
    });
  }

  const goodsReceipt = await db
    .insertInto("GoodsReceipt")
    .values({
      ...(customFields as Record<string, unknown>),
      companyId,
      supplierId,
      receiptDate: receiptDate ?? new Date(),
      status: "DRAFT",
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  await db
    .insertInto("GoodsReceiptLine")
    .values(
      normalizedLines.map((line) => ({
        ...line.lineCustomFields,
        goodsReceiptId: goodsReceipt!.id,
        purchaseOrderLineId: line.purchaseOrderLineId,
        itemId: line.itemId,
        quantity: line.quantity,
        unitId: line.unitId,
        requiresInventoryHandoff: line.requiresInventoryHandoff,
        destinationSite: line.destinationSite,
        destinationStorageLocation: line.destinationStorageLocation,
        createdAt: new Date(),
        updatedAt: null,
      })),
    )
    .execute();

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