<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Core\Database;
use App\Core\Tenant;
use RuntimeException;

class PurchaseOrderService
{
    private ?bool $variantTableAvailable = null;
    private ?bool $variantQuantityColumnAvailable = null;
    /** @var array<string,bool> */
    private array $columnAvailabilityCache = [];

    public function createOrder(array $payload): int
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        $userId = (int) (Auth::id() ?? 0);

        if ($companyId <= 0 || $userId <= 0) {
            throw new RuntimeException('Sesión inválida para crear orden de compra.');
        }

        $supplierId = (int) ($payload['proveedor_id'] ?? 0);
        if ($supplierId <= 0) {
            throw new RuntimeException('Selecciona un proveedor válido para la orden.');
        }

        $items = $payload['items'] ?? [];
        if (!is_array($items) || $items === []) {
            throw new RuntimeException('Debes agregar al menos un producto en la orden.');
        }

        return Database::transaction(function () use ($companyId, $userId, $supplierId, $items, $payload): int {
            $pdo = Database::connection();

            $userStmt = $pdo->prepare(
                'SELECT sucursal_id
                 FROM usuarios
                 WHERE id = :usuario_id
                   AND empresa_id = :empresa_id
                 LIMIT 1'
            );
            $userStmt->execute([
                ':usuario_id' => $userId,
                ':empresa_id' => $companyId,
            ]);
            $userBranchId = (int) ($userStmt->fetchColumn() ?: 0);

            $supplierExistsStmt = $pdo->prepare(
                'SELECT id
                 FROM proveedores
                 WHERE id = :proveedor_id
                   AND empresa_id = :empresa_id
                   AND activo = 1
                 LIMIT 1'
            );
            $supplierExistsStmt->execute([
                ':proveedor_id' => $supplierId,
                ':empresa_id' => $companyId,
            ]);
            if (!$supplierExistsStmt->fetch()) {
                throw new RuntimeException('El proveedor seleccionado no existe o está inactivo.');
            }

            $productBySupplierStmt = $pdo->prepare(
                'SELECT id
                 FROM productos
                 WHERE id = :producto_id
                   AND empresa_id = :empresa_id
                   AND proveedor_id = :proveedor_id
                   AND activo = 1
                 LIMIT 1'
            );

            $hasVariantTable = $this->hasVariantTable($pdo);
            $hasOrderDetailVariantColumn = $this->tableHasColumn($pdo, 'orden_compra_detalles', 'variante_id');
            $productVariantCountStmt = null;
            $variantSelectStmt = null;
            if ($hasVariantTable) {
                $productVariantCountStmt = $pdo->prepare(
                    'SELECT COUNT(*)
                     FROM producto_variantes
                     WHERE empresa_id = :empresa_id
                       AND producto_id = :producto_id
                       AND activo = 1'
                );
                $variantSelectStmt = $pdo->prepare(
                    'SELECT id, nombre
                     FROM producto_variantes
                     WHERE id = :variante_id
                       AND empresa_id = :empresa_id
                       AND producto_id = :producto_id
                       AND activo = 1
                     LIMIT 1'
                );
            }

            $subtotal = 0.0;
            $taxes = 0.0;
            $validItems = [];

            foreach ($items as $item) {
                $productId = (int) ($item['producto_id'] ?? 0);
                $variantId = (int) ($item['variante_id'] ?? 0);
                $qtyRaw = (float) ($item['cantidad'] ?? 0);
                $cost = round((float) ($item['costo_unitario'] ?? 0), 2);
                $lineTax = round((float) ($item['impuesto'] ?? 0), 2);

                if ($qtyRaw > 0 && !$this->isWholeQuantity($qtyRaw)) {
                    throw new RuntimeException('La cantidad en la orden de compra debe ser un número entero.');
                }
                $qty = (int) round($qtyRaw);

                if ($productId <= 0 || $qty <= 0 || $cost <= 0) {
                    continue;
                }

                $productBySupplierStmt->execute([
                    ':producto_id' => $productId,
                    ':empresa_id' => $companyId,
                    ':proveedor_id' => $supplierId,
                ]);
                if (!$productBySupplierStmt->fetch()) {
                    throw new RuntimeException(
                        'Producto inválido para el proveedor seleccionado en la orden (ID ' . $productId . ').'
                    );
                }

                $hasActiveVariants = false;
                if ($productVariantCountStmt !== null) {
                    $productVariantCountStmt->execute([
                        ':empresa_id' => $companyId,
                        ':producto_id' => $productId,
                    ]);
                    $hasActiveVariants = (int) $productVariantCountStmt->fetchColumn() > 0;
                }

                if ($hasActiveVariants && $variantId <= 0) {
                    throw new RuntimeException(
                        'Debes seleccionar una variación para el producto variable (ID ' . $productId . ').'
                    );
                }

                if ($variantId > 0) {
                    if (!$hasVariantTable || !$variantSelectStmt) {
                        throw new RuntimeException('El módulo de variaciones no está disponible en la base de datos.');
                    }

                    $variantSelectStmt->execute([
                        ':variante_id' => $variantId,
                        ':empresa_id' => $companyId,
                        ':producto_id' => $productId,
                    ]);
                    if (!$variantSelectStmt->fetch()) {
                        throw new RuntimeException('La variación seleccionada no existe para el producto ID ' . $productId . '.');
                    }

                    if (!$hasOrderDetailVariantColumn) {
                        throw new RuntimeException(
                            'No puedes crear OC por variación sin columna variante_id en orden_compra_detalles. Ejecuta la migración 024.'
                        );
                    }
                }

                $lineSubtotal = $qty * $cost;
                $lineTotal = $lineSubtotal + $lineTax;
                $subtotal += $lineSubtotal;
                $taxes += $lineTax;

                $validItems[] = [
                    'producto_id' => $productId,
                    'variante_id' => $variantId > 0 ? $variantId : null,
                    'cantidad' => $qty,
                    'costo_unitario' => $cost,
                    'impuesto' => $lineTax,
                    'total_linea' => $lineTotal,
                ];
            }

            if ($validItems === []) {
                throw new RuntimeException('No se encontraron ítems válidos en la orden.');
            }

            $total = $subtotal + $taxes;

            $insertOrderStmt = $pdo->prepare(
                'INSERT INTO ordenes_compra (
                    empresa_id, sucursal_id, proveedor_id, usuario_id, numero, fecha,
                    subtotal, impuestos, total, estado, observacion, created_at, updated_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :proveedor_id, :usuario_id, :numero, NOW(),
                    :subtotal, :impuestos, :total, :estado, :observacion, NOW(), NOW()
                )'
            );
            $insertOrderStmt->execute([
                ':empresa_id' => $companyId,
                ':sucursal_id' => $userBranchId > 0 ? $userBranchId : null,
                ':proveedor_id' => $supplierId,
                ':usuario_id' => $userId,
                ':numero' => 'TMP-' . date('YmdHis'),
                ':subtotal' => $subtotal,
                ':impuestos' => $taxes,
                ':total' => $total,
                ':estado' => 'abierta',
                ':observacion' => trim((string) ($payload['observacion'] ?? '')) ?: null,
            ]);

            $orderId = (int) $pdo->lastInsertId();
            $orderNumber = 'OC-' . date('Ymd') . '-' . str_pad((string) $orderId, 5, '0', STR_PAD_LEFT);

            $updateNumberStmt = $pdo->prepare(
                'UPDATE ordenes_compra
                 SET numero = :numero,
                     updated_at = NOW()
                 WHERE id = :id
                   AND empresa_id = :empresa_id'
            );
            $updateNumberStmt->execute([
                ':numero' => $orderNumber,
                ':id' => $orderId,
                ':empresa_id' => $companyId,
            ]);

            $detailSql = $hasOrderDetailVariantColumn
                ? 'INSERT INTO orden_compra_detalles (
                    orden_compra_id, empresa_id, producto_id, variante_id, cantidad, cantidad_recibida,
                    costo_unitario, impuesto, total_linea
                ) VALUES (
                    :orden_compra_id, :empresa_id, :producto_id, :variante_id, :cantidad, 0,
                    :costo_unitario, :impuesto, :total_linea
                )'
                : 'INSERT INTO orden_compra_detalles (
                    orden_compra_id, empresa_id, producto_id, cantidad, cantidad_recibida,
                    costo_unitario, impuesto, total_linea
                ) VALUES (
                    :orden_compra_id, :empresa_id, :producto_id, :cantidad, 0,
                    :costo_unitario, :impuesto, :total_linea
                )';
            $detailStmt = $pdo->prepare($detailSql);

            foreach ($validItems as $item) {
                $detailParams = [
                    ':orden_compra_id' => $orderId,
                    ':empresa_id' => $companyId,
                    ':producto_id' => $item['producto_id'],
                    ':cantidad' => $item['cantidad'],
                    ':costo_unitario' => $item['costo_unitario'],
                    ':impuesto' => $item['impuesto'],
                    ':total_linea' => $item['total_linea'],
                ];
                if ($hasOrderDetailVariantColumn) {
                    $detailParams[':variante_id'] = $item['variante_id'];
                }
                $detailStmt->execute($detailParams);
            }

            return $orderId;
        });
    }

    public function receiveOrder(int $orderId, array $receivedQuantities, string $observation = ''): array
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        $userId = (int) (Auth::id() ?? 0);

        if ($companyId <= 0 || $userId <= 0) {
            throw new RuntimeException('Sesión inválida para registrar recepción.');
        }

        if ($orderId <= 0) {
            throw new RuntimeException('Orden inválida para recepción.');
        }

        return Database::transaction(function () use (
            $companyId,
            $userId,
            $orderId,
            $receivedQuantities,
            $observation
        ): array {
            $pdo = Database::connection();

            $orderStmt = $pdo->prepare(
                'SELECT *
                 FROM ordenes_compra
                 WHERE id = :id
                   AND empresa_id = :empresa_id
                 LIMIT 1
                 FOR UPDATE'
            );
            $orderStmt->execute([
                ':id' => $orderId,
                ':empresa_id' => $companyId,
            ]);
            $order = $orderStmt->fetch();

            if (!$order) {
                throw new RuntimeException('La orden de compra no existe.');
            }

            if (($order['estado'] ?? '') === 'anulada') {
                throw new RuntimeException('No se puede recibir una orden anulada.');
            }

            if (($order['estado'] ?? '') === 'completa') {
                throw new RuntimeException('La orden ya está recibida completamente.');
            }

            $hasVariantTable = $this->hasVariantTable($pdo);
            $hasVariantQty = $hasVariantTable ? $this->hasVariantQuantityColumn($pdo) : false;
            $hasOrderDetailVariantColumn = $this->tableHasColumn($pdo, 'orden_compra_detalles', 'variante_id');
            $hasReceptionDetailVariantColumn = $this->tableHasColumn($pdo, 'recepcion_compra_detalles', 'variante_id');
            $hasPurchaseDetailVariantColumn = $this->tableHasColumn($pdo, 'compra_detalles', 'variante_id');

            $detailsSql = $hasOrderDetailVariantColumn
                ? 'SELECT od.*, p.nombre AS producto, p.precio_compra, v.nombre AS variante_nombre
                 FROM orden_compra_detalles od
                 INNER JOIN productos p ON p.id = od.producto_id AND p.empresa_id = od.empresa_id
                 LEFT JOIN producto_variantes v ON v.id = od.variante_id AND v.empresa_id = od.empresa_id
                 WHERE od.empresa_id = :empresa_id
                   AND od.orden_compra_id = :orden_id
                 ORDER BY od.id ASC
                 FOR UPDATE'
                : 'SELECT od.*, NULL AS variante_id, NULL AS variante_nombre, p.nombre AS producto, p.precio_compra
                 FROM orden_compra_detalles od
                 INNER JOIN productos p ON p.id = od.producto_id AND p.empresa_id = od.empresa_id
                 WHERE od.empresa_id = :empresa_id
                   AND od.orden_compra_id = :orden_id
                 ORDER BY od.id ASC
                 FOR UPDATE';
            $detailsStmt = $pdo->prepare($detailsSql);
            $detailsStmt->execute([
                ':empresa_id' => $companyId,
                ':orden_id' => $orderId,
            ]);
            $details = $detailsStmt->fetchAll() ?: [];

            if ($details === []) {
                throw new RuntimeException('La orden no tiene detalles para recepción.');
            }

            $itemsToReceive = [];
            $subtotal = 0.0;
            $taxTotal = 0.0;

            foreach ($details as $detail) {
                $detailId = (int) ($detail['id'] ?? 0);
                $orderedQty = (float) ($detail['cantidad'] ?? 0);
                $receivedQty = (float) ($detail['cantidad_recibida'] ?? 0);
                $pendingQty = max(0, $orderedQty - $receivedQty);
                $receiveQtyRaw = (float) ($receivedQuantities[$detailId] ?? 0);
                if ($receiveQtyRaw > 0 && !$this->isWholeQuantity($receiveQtyRaw)) {
                    throw new RuntimeException('La cantidad recibida debe ser un número entero.');
                }
                $receiveQty = (int) round($receiveQtyRaw);

                if ($receiveQty <= 0) {
                    continue;
                }

                if ($pendingQty <= 0) {
                    throw new RuntimeException('El ítem #' . $detailId . ' ya está completo en esta orden.');
                }

                if ($receiveQty > $pendingQty + 0.0001) {
                    throw new RuntimeException(
                        'La recepción para "' . (string) ($detail['producto'] ?? ('Item #' . $detailId)) . '" excede la cantidad pendiente.'
                    );
                }

                $unitCost = (float) ($detail['costo_unitario'] ?? 0);
                $tax = (float) ($detail['impuesto'] ?? 0);
                $lineTax = $orderedQty > 0 ? ($tax * ($receiveQty / $orderedQty)) : 0;
                $lineSubtotal = $receiveQty * $unitCost;
                $lineTotal = $lineSubtotal + $lineTax;

                $subtotal += $lineSubtotal;
                $taxTotal += $lineTax;

                $itemsToReceive[] = [
                    'detalle_id' => $detailId,
                    'producto_id' => (int) ($detail['producto_id'] ?? 0),
                    'variante_id' => (int) ($detail['variante_id'] ?? 0),
                    'variante_nombre' => (string) ($detail['variante_nombre'] ?? ''),
                    'cantidad' => $receiveQty,
                    'costo_unitario' => $unitCost,
                    'impuesto' => $lineTax,
                    'total_linea' => $lineTotal,
                    'precio_compra_actual' => (float) ($detail['precio_compra'] ?? 0),
                ];
            }

            if ($itemsToReceive === []) {
                throw new RuntimeException('Debes ingresar al menos una cantidad de recepción mayor a cero.');
            }

            $branchId = (int) ($order['sucursal_id'] ?? 0);
            if ($branchId <= 0) {
                $userBranchStmt = $pdo->prepare(
                    'SELECT sucursal_id
                     FROM usuarios
                     WHERE id = :usuario_id
                       AND empresa_id = :empresa_id
                     LIMIT 1'
                );
                $userBranchStmt->execute([
                    ':usuario_id' => $userId,
                    ':empresa_id' => $companyId,
                ]);
                $branchId = (int) ($userBranchStmt->fetchColumn() ?: 0);
            }

            if ($branchId <= 0) {
                throw new RuntimeException('La orden no tiene sucursal y el usuario no tiene una sucursal asignada.');
            }

            $receptionCountStmt = $pdo->prepare(
                'SELECT COUNT(*)
                 FROM recepciones_compra
                 WHERE empresa_id = :empresa_id
                   AND orden_compra_id = :orden_id'
            );
            $receptionCountStmt->execute([
                ':empresa_id' => $companyId,
                ':orden_id' => $orderId,
            ]);
            $nextSequence = (int) $receptionCountStmt->fetchColumn() + 1;
            $receiptNumber = 'RC-' . date('Ymd') . '-' . str_pad((string) $orderId, 4, '0', STR_PAD_LEFT) . '-' . str_pad((string) $nextSequence, 2, '0', STR_PAD_LEFT);

            $insertReceptionStmt = $pdo->prepare(
                'INSERT INTO recepciones_compra (
                    empresa_id, orden_compra_id, sucursal_id, usuario_id,
                    numero_recepcion, fecha_recepcion, estado, observacion, created_at
                ) VALUES (
                    :empresa_id, :orden_compra_id, :sucursal_id, :usuario_id,
                    :numero_recepcion, NOW(), "recibida", :observacion, NOW()
                )'
            );
            $insertReceptionStmt->execute([
                ':empresa_id' => $companyId,
                ':orden_compra_id' => $orderId,
                ':sucursal_id' => $branchId,
                ':usuario_id' => $userId,
                ':numero_recepcion' => $receiptNumber,
                ':observacion' => trim($observation) !== '' ? trim($observation) : null,
            ]);
            $receptionId = (int) $pdo->lastInsertId();

            $insertReceptionDetailSql = $hasReceptionDetailVariantColumn
                ? 'INSERT INTO recepcion_compra_detalles (
                    recepcion_id, empresa_id, orden_detalle_id, producto_id, variante_id,
                    cantidad, costo_unitario, impuesto, total_linea
                ) VALUES (
                    :recepcion_id, :empresa_id, :orden_detalle_id, :producto_id, :variante_id,
                    :cantidad, :costo_unitario, :impuesto, :total_linea
                )'
                : 'INSERT INTO recepcion_compra_detalles (
                    recepcion_id, empresa_id, orden_detalle_id, producto_id,
                    cantidad, costo_unitario, impuesto, total_linea
                ) VALUES (
                    :recepcion_id, :empresa_id, :orden_detalle_id, :producto_id,
                    :cantidad, :costo_unitario, :impuesto, :total_linea
                )';
            $insertReceptionDetailStmt = $pdo->prepare($insertReceptionDetailSql);

            $updateOrderDetailStmt = $pdo->prepare(
                'UPDATE orden_compra_detalles
                 SET cantidad_recibida = cantidad_recibida + :cantidad
                 WHERE id = :id
                   AND empresa_id = :empresa_id
                   AND orden_compra_id = :orden_id'
            );

            $updateProductStockStmt = $pdo->prepare(
                'UPDATE productos
                 SET stock_actual = stock_actual + :cantidad,
                     precio_compra = :costo_unitario
                 WHERE id = :producto_id
                   AND empresa_id = :empresa_id'
            );

            $updateProductCostStmt = $pdo->prepare(
                'UPDATE productos
                 SET precio_compra = :costo_unitario
                 WHERE id = :producto_id
                   AND empresa_id = :empresa_id'
            );

            $variantStockIncreaseStmt = null;
            $syncProductStockFromVariantsStmt = null;
            if ($hasVariantTable && $hasVariantQty) {
                $variantStockIncreaseStmt = $pdo->prepare(
                    'UPDATE producto_variantes
                     SET cantidad = cantidad + :cantidad,
                         updated_at = NOW()
                     WHERE id = :variante_id
                       AND empresa_id = :empresa_id
                       AND producto_id = :producto_id'
                );

                $syncProductStockFromVariantsStmt = $pdo->prepare(
                    'UPDATE productos p
                     SET p.stock_actual = (
                         SELECT COALESCE(SUM(v.cantidad), 0)
                         FROM producto_variantes v
                         WHERE v.empresa_id = p.empresa_id
                           AND v.producto_id = p.id
                           AND v.activo = 1
                     ),
                     p.updated_at = NOW()
                     WHERE p.id = :producto_id
                       AND p.empresa_id = :empresa_id'
                );
            }

            $branchStockUpsertStmt = $pdo->prepare(
                'INSERT INTO inventario (
                    empresa_id, sucursal_id, producto_id, stock, costo_promedio, updated_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :producto_id, :stock, :costo_promedio, NOW()
                )
                ON DUPLICATE KEY UPDATE
                    stock = stock + VALUES(stock),
                    costo_promedio = VALUES(costo_promedio),
                    updated_at = VALUES(updated_at)'
            );

            $movementStmt = $pdo->prepare(
                'INSERT INTO movimientos_inventario (
                    empresa_id, sucursal_id, producto_id, tipo, referencia, cantidad, costo_unitario, created_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :producto_id, "entrada", :referencia, :cantidad, :costo_unitario, NOW()
                )'
            );

            $insertPurchaseStmt = $pdo->prepare(
                'INSERT INTO compras (
                    empresa_id, orden_compra_id, recepcion_compra_id, proveedor_id, usuario_id,
                    fecha, subtotal, impuestos, total, estado, created_at
                ) VALUES (
                    :empresa_id, :orden_compra_id, :recepcion_compra_id, :proveedor_id, :usuario_id,
                    NOW(), :subtotal, :impuestos, :total, "recibida", NOW()
                )'
            );

            $insertPurchaseDetailSql = $hasPurchaseDetailVariantColumn
                ? 'INSERT INTO compra_detalles (
                    compra_id, empresa_id, producto_id, variante_id, cantidad, costo_unitario, impuesto, total_linea
                ) VALUES (
                    :compra_id, :empresa_id, :producto_id, :variante_id, :cantidad, :costo_unitario, :impuesto, :total_linea
                )'
                : 'INSERT INTO compra_detalles (
                    compra_id, empresa_id, producto_id, cantidad, costo_unitario, impuesto, total_linea
                ) VALUES (
                    :compra_id, :empresa_id, :producto_id, :cantidad, :costo_unitario, :impuesto, :total_linea
                )';
            $insertPurchaseDetailStmt = $pdo->prepare($insertPurchaseDetailSql);

            $purchaseTotal = $subtotal + $taxTotal;
            $insertPurchaseStmt->execute([
                ':empresa_id' => $companyId,
                ':orden_compra_id' => $orderId,
                ':recepcion_compra_id' => $receptionId,
                ':proveedor_id' => (int) $order['proveedor_id'],
                ':usuario_id' => $userId,
                ':subtotal' => $subtotal,
                ':impuestos' => $taxTotal,
                ':total' => $purchaseTotal,
            ]);
            $purchaseId = (int) $pdo->lastInsertId();

            $variantProductsToSync = [];
            foreach ($itemsToReceive as $item) {
                $variantId = (int) ($item['variante_id'] ?? 0);
                if ($variantId > 0) {
                    if (!$hasOrderDetailVariantColumn || !$hasReceptionDetailVariantColumn || !$hasPurchaseDetailVariantColumn) {
                        throw new RuntimeException(
                            'No puedes recibir OC por variación sin columnas variante_id en detalle de OC/recepción/compra. Ejecuta la migración 024.'
                        );
                    }
                    if ($variantStockIncreaseStmt === null) {
                        throw new RuntimeException(
                            'No puedes recibir OC por variación sin columna cantidad en producto_variantes.'
                        );
                    }
                }

                $receptionDetailParams = [
                    ':recepcion_id' => $receptionId,
                    ':empresa_id' => $companyId,
                    ':orden_detalle_id' => $item['detalle_id'],
                    ':producto_id' => $item['producto_id'],
                    ':cantidad' => $item['cantidad'],
                    ':costo_unitario' => $item['costo_unitario'],
                    ':impuesto' => $item['impuesto'],
                    ':total_linea' => $item['total_linea'],
                ];
                if ($hasReceptionDetailVariantColumn) {
                    $receptionDetailParams[':variante_id'] = $variantId > 0 ? $variantId : null;
                }
                $insertReceptionDetailStmt->execute($receptionDetailParams);

                $updateOrderDetailStmt->execute([
                    ':cantidad' => $item['cantidad'],
                    ':id' => $item['detalle_id'],
                    ':empresa_id' => $companyId,
                    ':orden_id' => $orderId,
                ]);

                if ($variantId > 0) {
                    $variantStockIncreaseStmt->execute([
                        ':cantidad' => $item['cantidad'],
                        ':variante_id' => $variantId,
                        ':empresa_id' => $companyId,
                        ':producto_id' => $item['producto_id'],
                    ]);

                    if ($variantStockIncreaseStmt->rowCount() <= 0) {
                        throw new RuntimeException('No fue posible actualizar stock de la variación recibida.');
                    }

                    $updateProductCostStmt->execute([
                        ':costo_unitario' => $item['costo_unitario'],
                        ':producto_id' => $item['producto_id'],
                        ':empresa_id' => $companyId,
                    ]);

                    $variantProductsToSync[(int) $item['producto_id']] = true;
                } else {
                    $updateProductStockStmt->execute([
                        ':cantidad' => $item['cantidad'],
                        ':costo_unitario' => $item['costo_unitario'],
                        ':producto_id' => $item['producto_id'],
                        ':empresa_id' => $companyId,
                    ]);
                }

                $branchStockUpsertStmt->execute([
                    ':empresa_id' => $companyId,
                    ':sucursal_id' => $branchId,
                    ':producto_id' => $item['producto_id'],
                    ':stock' => $item['cantidad'],
                    ':costo_promedio' => $item['costo_unitario'],
                ]);

                $movementStmt->execute([
                    ':empresa_id' => $companyId,
                    ':sucursal_id' => $branchId,
                    ':producto_id' => $item['producto_id'],
                    ':referencia' => 'RECEPCION_OC#' . $orderId . '-' . $receptionId,
                    ':cantidad' => $item['cantidad'],
                    ':costo_unitario' => $item['costo_unitario'],
                ]);

                $purchaseDetailParams = [
                    ':compra_id' => $purchaseId,
                    ':empresa_id' => $companyId,
                    ':producto_id' => $item['producto_id'],
                    ':cantidad' => $item['cantidad'],
                    ':costo_unitario' => $item['costo_unitario'],
                    ':impuesto' => $item['impuesto'],
                    ':total_linea' => $item['total_linea'],
                ];
                if ($hasPurchaseDetailVariantColumn) {
                    $purchaseDetailParams[':variante_id'] = $variantId > 0 ? $variantId : null;
                }
                $insertPurchaseDetailStmt->execute($purchaseDetailParams);
            }

            if ($syncProductStockFromVariantsStmt !== null && $variantProductsToSync !== []) {
                foreach (array_keys($variantProductsToSync) as $variantProductId) {
                    $syncProductStockFromVariantsStmt->execute([
                        ':producto_id' => (int) $variantProductId,
                        ':empresa_id' => $companyId,
                    ]);
                }
            }

            $pendingCheckStmt = $pdo->prepare(
                'SELECT COUNT(*)
                 FROM orden_compra_detalles
                 WHERE empresa_id = :empresa_id
                   AND orden_compra_id = :orden_id
                   AND cantidad_recibida + 0.0001 < cantidad'
            );
            $pendingCheckStmt->execute([
                ':empresa_id' => $companyId,
                ':orden_id' => $orderId,
            ]);
            $pendingRows = (int) $pendingCheckStmt->fetchColumn();
            $newStatus = $pendingRows > 0 ? 'parcial' : 'completa';

            $updateOrderStmt = $pdo->prepare(
                'UPDATE ordenes_compra
                 SET estado = :estado,
                     updated_at = NOW()
                 WHERE id = :id
                   AND empresa_id = :empresa_id'
            );
            $updateOrderStmt->execute([
                ':estado' => $newStatus,
                ':id' => $orderId,
                ':empresa_id' => $companyId,
            ]);

            return [
                'order_id' => $orderId,
                'reception_id' => $receptionId,
                'purchase_id' => $purchaseId,
                'order_status' => $newStatus,
            ];
        });
    }

    private function isWholeQuantity(float $quantity): bool
    {
        return abs($quantity - round($quantity)) < 0.0001;
    }

    private function hasVariantTable(\PDO $pdo): bool
    {
        if ($this->variantTableAvailable !== null) {
            return $this->variantTableAvailable;
        }

        $stmt = $pdo->prepare(
            "SELECT COUNT(*) FROM information_schema.TABLES
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = 'producto_variantes'"
        );
        $stmt->execute();

        $this->variantTableAvailable = (int) $stmt->fetchColumn() > 0;
        return $this->variantTableAvailable;
    }

    private function hasVariantQuantityColumn(\PDO $pdo): bool
    {
        if ($this->variantQuantityColumnAvailable !== null) {
            return $this->variantQuantityColumnAvailable;
        }

        $stmt = $pdo->prepare(
            "SELECT COUNT(*) FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = 'producto_variantes'
               AND COLUMN_NAME = 'cantidad'"
        );
        $stmt->execute();

        $this->variantQuantityColumnAvailable = (int) $stmt->fetchColumn() > 0;
        return $this->variantQuantityColumnAvailable;
    }

    private function tableHasColumn(\PDO $pdo, string $table, string $column): bool
    {
        $cacheKey = $table . '.' . $column;
        if (array_key_exists($cacheKey, $this->columnAvailabilityCache)) {
            return $this->columnAvailabilityCache[$cacheKey];
        }

        $stmt = $pdo->prepare(
            "SELECT COUNT(*) FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = :table
               AND COLUMN_NAME = :column"
        );
        $stmt->execute([
            ':table' => $table,
            ':column' => $column,
        ]);

        $this->columnAvailabilityCache[$cacheKey] = (int) $stmt->fetchColumn() > 0;
        return $this->columnAvailabilityCache[$cacheKey];
    }
}
