<?php

namespace WMS\Services;

use WMS\Core\Database;
use WMS\Repositories\AgendaRepository;
use WMS\Repositories\ItemRepository;
use WMS\Repositories\BatchRepository;
use WMS\Repositories\BinRepository;
use WMS\Repositories\StockRepository;
use WMS\Repositories\StockMovementRepository;
use WMS\Integrations\Sap\SapServiceLayerClient;
use WMS\Services\AuditService;

/**
 * Servicio de Agenda de Recepcion.
 *
 * Flujo:
 * 1. Crear agenda (fecha planificada, almacen)
 * 2. Asociar OCs a la agenda (fetch de SAP via Service Layer)
 * 3. Abrir agenda para recepcion fisica
 * 4. Operadores escanean productos a ciegas (codigo barras, qty, lote, vencimiento, lote proveedor)
 * 5. Comparar escaneado vs esperado (mostrar diferencias)
 * 6. Cerrar agenda -> crear Goods Receipt en SAP (PurchaseDeliveryNotes)
 */
class AgendaService
{
    private AgendaRepository $agendaRepo;
    private ItemRepository $itemRepo;
    private BatchRepository $batchRepo;
    private BinRepository $binRepo;
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;

    public function __construct()
    {
        $this->agendaRepo   = new AgendaRepository();
        $this->itemRepo     = new ItemRepository();
        $this->batchRepo    = new BatchRepository();
        $this->binRepo      = new BinRepository();
        $this->stockRepo    = new StockRepository();
        $this->movementRepo = new StockMovementRepository();
    }

    /**
     * Crea una nueva agenda de recepcion.
     */
    public function createAgenda(array $data, int $userId): array
    {
        $agendaNumber = Database::nextDocNumber('AGENDA');

        $agendaId = $this->agendaRepo->insert([
            'agenda_number' => $agendaNumber,
            'warehouse_id'  => $data['warehouse_id'],
            'status'        => 'DRAFT',
            'planned_date'  => $data['planned_date'],
            'notes'         => $data['notes'] ?? null,
            'created_by'    => $userId,
        ]);

        AuditService::log('reception_agendas', $agendaId, 'AGENDA_CREATE', null, [
            'agenda_number' => $agendaNumber,
            'warehouse_id'  => $data['warehouse_id'],
            'planned_date'  => $data['planned_date'],
            'notes'         => $data['notes'] ?? null,
        ], $userId);

        return $this->agendaRepo->getWithDetails($agendaId);
    }

    /**
     * Asocia una Orden de Compra (OC) de SAP a la agenda.
     * Obtiene la OC via Service Layer y extrae sus lineas.
     */
    public function addReference(int $agendaId, array $data, int $userId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if ($agenda['status'] !== 'DRAFT') {
            throw new \RuntimeException('Solo se pueden agregar referencias a agendas en estado DRAFT');
        }

        // Verificar que la OC no este ya asociada a esta agenda
        $db = Database::getConnection();
        $stmt = $db->prepare(
            "SELECT id FROM reception_agenda_refs
             WHERE agenda_id = ? AND sap_doc_entry = ? AND sap_doc_type = 'PurchaseOrder'"
        );
        $stmt->execute([$agendaId, $sapDocEntry]);
        if ($stmt->fetch()) {
            throw new \RuntimeException("La OC #{$sapDocEntry} ya esta asociada a esta agenda");
        }

        // Obtener OC de SAP
        $sap = new SapServiceLayerClient();
        // Resolve DocNum → DocEntry if needed
        if (empty($sapDocEntry) && !empty($data['sap_doc_num'])) {
            $docNum = (int)$data['sap_doc_num'];
            $qr = $sap->get("/PurchaseOrders?\$filter=DocNum eq {$docNum}&\$select=DocEntry,DocNum&\$top=1");
            if (empty($qr['value'])) {
                throw new \RuntimeException("OC con Nro. {$docNum} no encontrada en SAP");
            }
            $sapDocEntry = (int)$qr['value'][0]['DocEntry'];
        }
        if (empty($sapDocEntry)) {
            throw new \RuntimeException("Debe ingresar un Nro. de OC valido");
        }

        $oc = $sap->get("/PurchaseOrders({$sapDocEntry})");

        if (empty($oc['DocEntry'])) {
            throw new \RuntimeException("No se pudo obtener la OC #{$sapDocEntry} de SAP");
        }

        $db->beginTransaction();

        try {
            // Insertar referencia
            $stmtRef = $db->prepare(
                "INSERT INTO reception_agenda_refs
                 (agenda_id, sap_doc_type, sap_doc_entry, sap_doc_num, ref_type, supplier_code, supplier_name, status)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
            );

            // Determinar tipo de referencia segun tipo de OC
            $refType = 'OC_PLAZA'; // default
            if (!empty($oc['U_TipoOC'])) {
                $refType = match ($oc['U_TipoOC']) {
                    'IMP'  => 'OC_IMPORTACION',
                    'PROD' => 'OT_PRODUCCION',
                    default => 'OC_PLAZA',
                };
            }

            $stmtRef->execute([
                $agendaId,
                'PurchaseOrder',
                $oc['DocEntry'],
                $oc['DocNum'],
                $refType,
                $oc['CardCode'] ?? null,
                $oc['CardName'] ?? null,
                'PENDING',
            ]);
            $refId = (int) $db->lastInsertId();

            // Insertar lineas de la OC
            if (!empty($oc['DocumentLines'])) {
                $stmtLine = $db->prepare(
                    "INSERT INTO reception_agenda_lines
                     (agenda_id, ref_id, line_num, item_id, item_code, item_name,
                      expected_qty, received_qty, difference_qty, sap_line_num, status)
                     VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, ?, 'PENDING')"
                );

                foreach ($oc['DocumentLines'] as $i => $sapLine) {
                    $itemCode = $sapLine['ItemCode'] ?? '';
                    $itemName = $sapLine['ItemDescription'] ?? '';

                    // Buscar item en WMS local
                    $localItem = $this->itemRepo->findByCode($itemCode);
                    $itemId = $localItem ? $localItem['id'] : null;

                    $expectedQty = (float) ($sapLine['Quantity'] ?? 0);
                    $sapLineNum  = $sapLine['LineNum'] ?? $i;

                    $stmtLine->execute([
                        $agendaId,
                        $refId,
                        $i + 1,
                        $itemId,
                        $itemCode,
                        $itemName,
                        $expectedQty,
                        $sapLineNum,
                    ]);
                }
            }

            $db->commit();

            AuditService::log('reception_agenda_refs', (int)$refId, 'AGENDA_ADD_REF', null, [
                'agenda_id'     => $agendaId,
                'sap_doc_entry' => $sapDocEntry,
                'sap_doc_num'   => $oc['DocNum'] ?? null,
                'sap_doc_type'  => 'PurchaseOrder',
                'supplier_code' => $oc['CardCode'] ?? null,
                'lines_added'   => count($oc['DocumentLines'] ?? []),
            ], $userId);

            return $this->agendaRepo->getWithDetails($agendaId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Elimina una referencia (OC) de la agenda y sus lineas.
     */
    public function removeReference(int $agendaId, int $refId, int $userId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if ($agenda['status'] !== 'DRAFT') {
            throw new \RuntimeException('Solo se pueden eliminar referencias de agendas en estado DRAFT');
        }

        $db = Database::getConnection();

        // Verificar que la referencia pertenece a esta agenda
        $stmt = $db->prepare("SELECT id FROM reception_agenda_refs WHERE id = ? AND agenda_id = ?");
        $stmt->execute([$refId, $agendaId]);
        if (!$stmt->fetch()) {
            throw new \RuntimeException('Referencia no encontrada en esta agenda');
        }

        $db->beginTransaction();

        try {
            // Eliminar lineas asociadas
            $db->prepare("DELETE FROM reception_agenda_lines WHERE ref_id = ? AND agenda_id = ?")
               ->execute([$refId, $agendaId]);

            // Eliminar referencia
            $db->prepare("DELETE FROM reception_agenda_refs WHERE id = ?")
               ->execute([$refId]);

            $db->commit();

            AuditService::log('reception_agenda_refs', $refId, 'AGENDA_REMOVE_REF', null, [
                'agenda_id' => $agendaId,
                'ref_id'    => $refId,
            ], $userId);

            return $this->agendaRepo->getWithDetails($agendaId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Asigna la agenda a un operador responsable.
     */
    public function assignAgenda(int $agendaId, int $assigneeId, int $byUserId, ?string $notes = null): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if (in_array($agenda['status'], ['CLOSED', 'CANCELLED'])) {
            throw new \RuntimeException('No se puede asignar una agenda cerrada o cancelada');
        }

        $db = Database::getConnection();
        $stmt = $db->prepare("SELECT id, username, full_name, is_active FROM users WHERE id = ?");
        $stmt->execute([$assigneeId]);
        $assignee = $stmt->fetch();
        if (!$assignee || !$assignee['is_active']) {
            throw new \RuntimeException('Usuario destino inválido o inactivo');
        }

        $this->agendaRepo->update($agendaId, [
            'assigned_to' => $assigneeId,
            'assigned_at' => date('Y-m-d H:i:s'),
            'assigned_by' => $byUserId,
        ]);

        AuditService::log('reception_agendas', $agendaId, 'AGENDA_ASSIGN', [
            'assigned_to_prev' => $agenda['assigned_to'] ?? null,
        ], [
            'agenda_number'   => $agenda['agenda_number'] ?? null,
            'assigned_to'     => $assigneeId,
            'assignee_user'   => $assignee['username'],
            'assignee_name'   => $assignee['full_name'],
            'assigned_by'     => $byUserId,
            'notes'           => $notes,
        ], $byUserId);

        return $this->agendaRepo->getWithDetails($agendaId);
    }

    /**
     * Abre la agenda para recepcion fisica.
     * Valida que tenga al menos una referencia asociada.
     */
    public function openAgenda(int $agendaId, int $userId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if ($agenda['status'] !== 'DRAFT') {
            throw new \RuntimeException('Solo se pueden abrir agendas en estado DRAFT');
        }

        // Verificar que tiene al menos una referencia
        $db = Database::getConnection();
        $stmt = $db->prepare("SELECT COUNT(*) FROM reception_agenda_refs WHERE agenda_id = ?");
        $stmt->execute([$agendaId]);
        $refCount = (int) $stmt->fetchColumn();

        if ($refCount === 0) {
            throw new \RuntimeException('La agenda debe tener al menos una OC asociada para poder abrirla');
        }

        $this->agendaRepo->update($agendaId, [
            'status'    => 'OPEN',
            'opened_at' => date('Y-m-d H:i:s'),
        ]);

        AuditService::log('reception_agendas', $agendaId, 'AGENDA_OPEN', [
            'status_prev' => 'DRAFT',
        ], [
            'agenda_number' => $agenda['agenda_number'] ?? null,
            'refs_count'    => $refCount,
        ], $userId);

        return $this->agendaRepo->getWithDetails($agendaId);
    }

    /**
     * Registra un escaneo ciego de producto.
     * Intenta hacer match con una linea de la agenda por item_code.
     * Actualiza received_qty en la linea correspondiente.
     */
    public function scanItem(int $agendaId, array $data, int $userId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if ($agenda['status'] !== 'OPEN') {
            throw new \RuntimeException('La agenda debe estar ABIERTA para registrar escaneos');
        }

        $itemCode    = $data['item_code'];
        $scannedQty  = (float) $data['quantity'];
        $batchNumber = $data['batch_number'] ?? null;
        $expiryDate  = $data['expiry_date'] ?? null;
        $supplierLot = $data['supplier_lot'] ?? null;
        $binId       = $data['bin_id'] ?? null;

        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            // Buscar linea correspondiente en la agenda por item_code
            // Priorizar lineas con cantidad pendiente
            $stmt = $db->prepare(
                "SELECT l.id, l.item_code, l.expected_qty, l.received_qty
                 FROM reception_agenda_lines l
                 WHERE l.agenda_id = ? AND l.item_code = ?
                 ORDER BY (l.expected_qty - l.received_qty) DESC
                 LIMIT 1"
            );
            $stmt->execute([$agendaId, $itemCode]);
            $matchedLine = $stmt->fetch();

            $lineId = null;

            if ($matchedLine) {
                $lineId = (int) $matchedLine['id'];

                // Actualizar cantidad recibida en la linea
                $newReceivedQty = (float) $matchedLine['received_qty'] + $scannedQty;
                $expectedQty    = (float) $matchedLine['expected_qty'];
                $differenceQty  = $newReceivedQty - $expectedQty;

                $lineStatus = 'IN_PROGRESS';
                if ($newReceivedQty >= $expectedQty) {
                    $lineStatus = 'COMPLETED';
                }

                $db->prepare(
                    "UPDATE reception_agenda_lines
                     SET received_qty = ?, difference_qty = ?, batch_number = COALESCE(?, batch_number),
                         expiry_date = COALESCE(?, expiry_date), supplier_lot = COALESCE(?, supplier_lot),
                         status = ?
                     WHERE id = ?"
                )->execute([$newReceivedQty, $differenceQty, $batchNumber, $expiryDate, $supplierLot, $lineStatus, $lineId]);
            } else {
                // Producto no esperado: verificar si la agenda lo permite
                if (!$this->allowUnexpectedProducts($agendaId)) {
                    throw new \RuntimeException(
                        "El articulo '{$itemCode}' no esta en la agenda y este tipo de agenda no permite productos inesperados"
                    );
                }
            }

            // Registrar escaneo
            $db->prepare(
                "INSERT INTO reception_scans
                 (agenda_id, line_id, item_code, batch_number, expiry_date, supplier_lot, scanned_qty, operator_id, scanned_at, bin_id)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)"
            )->execute([
                $agendaId,
                $lineId,
                $itemCode,
                $batchNumber,
                $expiryDate,
                $supplierLot,
                $scannedQty,
                $userId,
                $binId,
            ]);

            $db->commit();

            return $this->agendaRepo->getWithDetails($agendaId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Obtiene las diferencias entre lo esperado y lo recibido.
     * Incluye lineas con discrepancia y productos escaneados no esperados.
     */
    public function getDifferences(int $agendaId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }

        $db = Database::getConnection();

        // Diferencias en lineas esperadas
        $stmt = $db->prepare(
            "SELECT l.item_code, l.item_name, l.expected_qty, l.received_qty,
                    (l.received_qty - l.expected_qty) AS difference_qty,
                    l.batch_number, l.supplier_lot,
                    CASE
                        WHEN l.received_qty = 0 THEN 'NO_RECIBIDO'
                        WHEN l.received_qty < l.expected_qty THEN 'PARCIAL'
                        WHEN l.received_qty = l.expected_qty THEN 'COMPLETO'
                        WHEN l.received_qty > l.expected_qty THEN 'EXCEDENTE'
                    END AS status,
                    r.sap_doc_num AS ref_doc_num
             FROM reception_agenda_lines l
             LEFT JOIN reception_agenda_refs r ON l.ref_id = r.id
             WHERE l.agenda_id = ?
             ORDER BY l.item_code"
        );
        $stmt->execute([$agendaId]);
        $expectedLines = $stmt->fetchAll();

        // Productos escaneados no asociados a ninguna linea (inesperados)
        $stmt = $db->prepare(
            "SELECT s.item_code,
                    COALESCE(i.item_name, 'Articulo no registrado') AS item_name,
                    0 AS expected_qty,
                    SUM(s.scanned_qty) AS received_qty,
                    SUM(s.scanned_qty) AS difference_qty,
                    s.batch_number, s.supplier_lot,
                    'INESPERADO' AS status,
                    NULL AS ref_doc_num
             FROM reception_scans s
             LEFT JOIN items i ON s.item_code = i.item_code
             WHERE s.agenda_id = ? AND s.line_id IS NULL
             GROUP BY s.item_code, i.item_name, s.batch_number, s.supplier_lot"
        );
        $stmt->execute([$agendaId]);
        $unexpectedItems = $stmt->fetchAll();

        return [
            'expected_lines'   => $expectedLines,
            'unexpected_items' => $unexpectedItems,
            'summary' => [
                'total_expected_lines' => count($expectedLines),
                'completed'  => count(array_filter($expectedLines, fn($l) => $l['status'] === 'COMPLETO')),
                'partial'    => count(array_filter($expectedLines, fn($l) => $l['status'] === 'PARCIAL')),
                'not_received' => count(array_filter($expectedLines, fn($l) => $l['status'] === 'NO_RECIBIDO')),
                'excess'     => count(array_filter($expectedLines, fn($l) => $l['status'] === 'EXCEDENTE')),
                'unexpected' => count($unexpectedItems),
            ],
        ];
    }

    /**
     * Cierra la agenda y crea PurchaseDeliveryNotes en SAP.
     * Agrupa lineas por referencia (OC) y crea un documento SAP por cada una.
     */
    public function closeAgenda(int $agendaId, int $userId): array
    {
        $agenda = $this->agendaRepo->findById($agendaId);
        if (!$agenda) {
            throw new \RuntimeException('Agenda no encontrada');
        }
        if ($agenda['status'] !== 'OPEN') {
            throw new \RuntimeException('Solo se pueden cerrar agendas en estado OPEN');
        }

        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            // Obtener referencias con sus lineas recibidas
            $stmt = $db->prepare(
                "SELECT r.id AS ref_id, r.sap_doc_entry, r.sap_doc_num, r.supplier_code, r.sap_doc_type
                 FROM reception_agenda_refs r
                 WHERE r.agenda_id = ?"
            );
            $stmt->execute([$agendaId]);
            $refs = $stmt->fetchAll();

            $sapResults = [];

            foreach ($refs as $ref) {
                // Obtener lineas recibidas para esta referencia
                $stmt = $db->prepare(
                    "SELECT l.*, GROUP_CONCAT(
                         DISTINCT CONCAT_WS('|', s.batch_number, s.scanned_qty, s.expiry_date, s.supplier_lot)
                     ) AS scan_details
                     FROM reception_agenda_lines l
                     LEFT JOIN reception_scans s ON s.line_id = l.id AND s.agenda_id = l.agenda_id
                     WHERE l.agenda_id = ? AND l.ref_id = ? AND l.received_qty > 0
                     GROUP BY l.id"
                );
                $stmt->execute([$agendaId, $ref['ref_id']]);
                $lines = $stmt->fetchAll();

                if (empty($lines)) {
                    // Marcar referencia sin lineas recibidas
                    $db->prepare("UPDATE reception_agenda_refs SET status = 'SKIPPED' WHERE id = ?")
                       ->execute([$ref['ref_id']]);
                    continue;
                }

                // Construir payload de PurchaseDeliveryNotes para SAP
                $sapData = [
                    'CardCode'      => $ref['supplier_code'],
                    'DocDate'       => date('Y-m-d'),
                    'DocumentLines' => [],
                ];

                foreach ($lines as $line) {
                    $sapLine = [
                        'ItemCode'      => $line['item_code'],
                        'Quantity'      => (float) $line['received_qty'],
                        'BaseType'      => 22, // PurchaseOrder
                        'BaseEntry'     => (int) $ref['sap_doc_entry'],
                        'BaseLine'      => (int) $line['sap_line_num'],
                    ];

                    // Agregar lotes si tiene batch
                    if (!empty($line['batch_number'])) {
                        $sapLine['BatchNumbers'] = [];

                        // Obtener escaneos individuales para esta linea con sus lotes
                        $stmtScans = $db->prepare(
                            "SELECT batch_number, SUM(scanned_qty) AS qty, expiry_date, supplier_lot
                             FROM reception_scans
                             WHERE agenda_id = ? AND line_id = ? AND batch_number IS NOT NULL
                             GROUP BY batch_number, expiry_date, supplier_lot"
                        );
                        $stmtScans->execute([$agendaId, $line['id']]);
                        $scanBatches = $stmtScans->fetchAll();

                        foreach ($scanBatches as $sb) {
                            $batchEntry = [
                                'BatchNumber' => $sb['batch_number'],
                                'Quantity'    => (float) $sb['qty'],
                            ];
                            if (!empty($sb['expiry_date'])) {
                                $batchEntry['ExpiryDate'] = $sb['expiry_date'];
                            }
                            if (!empty($sb['supplier_lot'])) {
                                $batchEntry['ManufacturerSerialNumber'] = $sb['supplier_lot'];
                            }
                            $sapLine['BatchNumbers'][] = $batchEntry;
                        }
                    }

                    $sapData['DocumentLines'][] = $sapLine;
                }

                // Crear PurchaseDeliveryNotes en SAP
                try {
                    $sap = new SapServiceLayerClient();
                    $result = $sap->post('/PurchaseDeliveryNotes', $sapData);

                    if (isset($result['DocEntry'])) {
                        // Actualizar referencia con datos de SAP
                        $db->prepare(
                            "UPDATE reception_agenda_refs
                             SET status = 'COMPLETED'
                             WHERE id = ?"
                        )->execute([$ref['ref_id']]);

                        $sapResults[] = [
                            'ref_id'        => $ref['ref_id'],
                            'sap_doc_entry' => $result['DocEntry'],
                            'sap_doc_num'   => $result['DocNum'] ?? null,
                            'status'        => 'SUCCESS',
                        ];
                    }

                    // Log exito en sap_sync_log
                    $db->prepare(
                        "INSERT INTO sap_sync_log
                         (sync_type, direction, sap_doc_entry, sap_doc_type, wms_reference_id, status)
                         VALUES (?, ?, ?, ?, ?, ?)"
                    )->execute([
                        'DOC_RECEPTION_AGENDA',
                        'WMS_TO_SAP',
                        $result['DocEntry'] ?? null,
                        'PurchaseDeliveryNote',
                        $agendaId,
                        'SUCCESS',
                    ]);
                } catch (\Throwable $e) {
                    // Log error en sap_sync_log
                    $db->prepare(
                        "INSERT INTO sap_sync_log
                         (sync_type, direction, sap_doc_type, wms_reference_id, status, error_message)
                         VALUES (?, ?, ?, ?, ?, ?)"
                    )->execute([
                        'DOC_RECEPTION_AGENDA',
                        'WMS_TO_SAP',
                        'PurchaseDeliveryNote',
                        $agendaId,
                        'ERROR',
                        $e->getMessage(),
                    ]);

                    $db->prepare("UPDATE reception_agenda_refs SET status = 'ERROR' WHERE id = ?")
                       ->execute([$ref['ref_id']]);

                    $sapResults[] = [
                        'ref_id' => $ref['ref_id'],
                        'status' => 'ERROR',
                        'error'  => $e->getMessage(),
                    ];
                }
            }

            // Crear stock entries para las lineas recibidas
            $this->createStockEntries($agendaId, $agenda['warehouse_id'], $userId);

            // Cerrar agenda
            $this->agendaRepo->update($agendaId, [
                'status'    => 'CLOSED',
                'closed_at' => date('Y-m-d H:i:s'),
            ]);

            // Actualizar lineas finales
            $db->prepare(
                "UPDATE reception_agenda_lines
                 SET difference_qty = (received_qty - expected_qty)
                 WHERE agenda_id = ?"
            )->execute([$agendaId]);

            $db->commit();

            AuditService::log('reception_agendas', $agendaId, 'AGENDA_CLOSE', [
                'status_prev' => 'OPEN',
            ], [
                'agenda_number' => $agenda['agenda_number'] ?? null,
                'refs_processed'=> count($refs),
                'sap_documents' => array_map(fn($r) => $r['sap_doc_entry'] ?? null, $sapResults),
            ], $userId);

            $result = $this->agendaRepo->getWithDetails($agendaId);
            $result['sap_results'] = $sapResults;

            return $result;
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Verifica si la agenda permite productos inesperados.
     * OC importacion/plaza = si, OT produccion = no.
     */
    public function allowUnexpectedProducts(int $agendaId): bool
    {
        $db = Database::getConnection();
        $stmt = $db->prepare(
            "SELECT ref_type FROM reception_agenda_refs WHERE agenda_id = ? LIMIT 1"
        );
        $stmt->execute([$agendaId]);
        $ref = $stmt->fetch();

        if (!$ref) {
            return true; // Sin referencias, permitir por defecto
        }

        // OT produccion no permite productos inesperados
        return $ref['ref_type'] !== 'OT_PRODUCCION';
    }

    /**
     * Crea entradas de stock en el WMS a partir de las lineas recibidas.
     */
    private function createStockEntries(int $agendaId, int $warehouseId, int $userId): void
    {
        $db = Database::getConnection();

        // Obtener bin de recepcion
        $receivingBins = $this->binRepo->getReceivingBins($warehouseId);
        if (empty($receivingBins)) {
            throw new \RuntimeException('No hay bin RECEIVING configurado en este almacen');
        }
        $receivingBinId = $receivingBins[0]['id'];

        $config = require __DIR__ . '/../config/app.php';
        $initialStatus = $config['wms']['qa_on_receipt'] ? 'QA' : 'AVAILABLE';

        // Agrupar escaneos por item_code + batch_number
        $stmt = $db->prepare(
            "SELECT s.item_code, s.batch_number, s.expiry_date,
                    SUM(s.scanned_qty) AS total_qty,
                    COALESCE(s.bin_id, ?) AS target_bin_id
             FROM reception_scans s
             WHERE s.agenda_id = ?
             GROUP BY s.item_code, s.batch_number, s.expiry_date, s.bin_id"
        );
        $stmt->execute([$receivingBinId, $agendaId]);
        $scanGroups = $stmt->fetchAll();

        foreach ($scanGroups as $group) {
            $item = $this->itemRepo->findByCode($group['item_code']);
            if (!$item) {
                continue; // Item no registrado en WMS
            }

            $batchId = null;
            if (!empty($group['batch_number'])) {
                $batch = $this->batchRepo->findOrCreate(
                    $item['id'],
                    $group['batch_number'],
                    $group['expiry_date'],
                    $userId
                );
                $batchId = $batch['id'];
            }

            $binId = (int) $group['target_bin_id'];
            $qty   = (float) $group['total_qty'];

            // Crear o actualizar posicion de stock
            $stockId = $this->stockRepo->findOrCreatePosition(
                $warehouseId,
                $binId,
                $item['id'],
                $batchId,
                $initialStatus,
                $item['uom'] ?? 'UN'
            );
            $this->stockRepo->addQuantity($stockId, $qty);

            // Registrar movimiento
            $this->movementRepo->logMovement([
                'warehouse_id'   => $warehouseId,
                'movement_type'  => 'RECEIPT',
                'item_id'        => $item['id'],
                'batch_id'       => $batchId,
                'to_bin_id'      => $binId,
                'to_status'      => $initialStatus,
                'quantity'       => $qty,
                'uom'            => $item['uom'] ?? 'UN',
                'reference_type' => 'RECEPTION_AGENDA',
                'reference_id'   => $agendaId,
                'created_by'     => $userId,
            ]);
        }
    }
}
