<?php

namespace WMS\Services;

use WMS\Core\Database;
use WMS\Repositories\InventoryDocumentRepository;
use WMS\Repositories\ItemRepository;
use WMS\Repositories\BatchRepository;
use WMS\Repositories\BinRepository;
use WMS\Repositories\StockRepository;
use WMS\Repositories\StockMovementRepository;

/**
 * Servicio de Recepción de Mercadería.
 *
 * Flujo:
 * 1. Se crea un documento ASN (desde SAP o manual)
 * 2. El operador escanea items, lotes, cantidades
 * 3. Se valida vida útil mínima (farmacéutica)
 * 4. El stock ingresa al bin RECEIVING con estado QA (cuarentena)
 * 5. Se generan tareas de putaway automáticamente si está configurado
 */
class ReceptionService
{
    private InventoryDocumentRepository $docRepo;
    private ItemRepository $itemRepo;
    private BatchRepository $batchRepo;
    private BinRepository $binRepo;
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;

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

    /**
     * Crea un documento de recepción (Goods Receipt).
     */
    public function createReceipt(array $data, int $userId): array
    {
        // Validar que la OC SAP no haya sido importada previamente
        if (!empty($data['sap_doc_entry'])) {
            $db = Database::getConnection();
            $stmt = $db->prepare(
                "SELECT id, doc_number, status FROM inventory_documents
                 WHERE doc_type = 'GOODS_RECEIPT' AND sap_doc_entry = ? AND status != 'CANCELLED'"
            );
            $stmt->execute([$data['sap_doc_entry']]);
            $existing = $stmt->fetch();
            if ($existing) {
                throw new \RuntimeException(
                    "La OC SAP #{$data['sap_doc_entry']} ya fue importada como {$existing['doc_number']} (estado: {$existing['status']})"
                );
            }
        }

        $docNumber = Database::nextDocNumber('GR');

        $docId = $this->docRepo->insert([
            'doc_type'          => 'GOODS_RECEIPT',
            'doc_number'        => $docNumber,
            'warehouse_id'      => $data['warehouse_id'],
            'status'            => 'OPEN',
            'sap_doc_entry'     => $data['sap_doc_entry'] ?? null,
            'sap_doc_num'       => $data['sap_doc_num'] ?? null,
            'sap_doc_type'      => $data['sap_doc_type'] ?? null,
            'business_partner'  => $data['business_partner'] ?? null,
            'bp_code'           => $data['bp_code'] ?? null,
            'expected_date'     => $data['expected_date'] ?? null,
            'notes'             => $data['notes'] ?? null,
            'created_by'        => $userId,
        ]);

        // Insertar líneas del documento
        if (!empty($data['lines'])) {
            $this->insertDocLines($docId, $data['lines']);
        }

        return $this->docRepo->getWithLines($docId);
    }

    /**
     * Confirma la recepción de una línea: valida lote, vencimiento, vida útil.
     * Ingresa stock al bin RECEIVING con estado QA.
     */
    public function confirmLineReceipt(int $docId, int $lineNum, array $data, int $userId): array
    {
        $doc = $this->docRepo->findById($docId);
        if (!$doc || $doc['status'] === 'COMPLETED' || $doc['status'] === 'CANCELLED') {
            throw new \RuntimeException('Documento no válido para recepción');
        }

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

        try {
            // Obtener línea
            $stmt = $db->prepare(
                "SELECT * FROM inventory_document_lines WHERE document_id = ? AND line_num = ?"
            );
            $stmt->execute([$docId, $lineNum]);
            $line = $stmt->fetch();
            if (!$line) {
                throw new \RuntimeException("Línea {$lineNum} no encontrada");
            }

            $item = $this->itemRepo->findById($line['item_id']);
            $receivedQty = (float) $data['quantity'];

            // Validar lote obligatorio (farmacéutica)
            if ($item['requires_lot'] && empty($data['batch_number'])) {
                throw new \RuntimeException('Número de lote obligatorio para este artículo');
            }

            // Validar vencimiento obligatorio
            if ($item['requires_expiry'] && empty($data['expiry_date'])) {
                throw new \RuntimeException('Fecha de vencimiento obligatoria para este artículo');
            }

            // Validar vida útil mínima
            if (!empty($data['expiry_date']) && $item['min_remaining_shelf_life_days']) {
                $expiry = new \DateTime($data['expiry_date']);
                $today  = new \DateTime();
                $daysRemaining = $today->diff($expiry)->days;
                if ($expiry < $today || $daysRemaining < $item['min_remaining_shelf_life_days']) {
                    throw new \RuntimeException(
                        "Vida útil insuficiente. Mínimo requerido: {$item['min_remaining_shelf_life_days']} días, restante: {$daysRemaining} días"
                    );
                }
            }

            // Crear o buscar lote
            $batchId = null;
            if (!empty($data['batch_number'])) {
                $batch = $this->batchRepo->findOrCreate(
                    $item['id'],
                    $data['batch_number'],
                    $data['expiry_date'],
                    $userId
                );
                $batchId = $batch['id'];
            }

            // Obtener bin de recepción
            $receivingBins = $this->binRepo->getReceivingBins($doc['warehouse_id']);
            if (empty($receivingBins)) {
                throw new \RuntimeException('No hay bin RECEIVING configurado en este almacén');
            }
            $receivingBin = $data['bin_id'] ?? $receivingBins[0]['id'];

            // Determinar estado inicial
            $config = require __DIR__ . '/../config/app.php';
            if ($item['force_blocked_on_entry']) {
                $initialStatus = 'BLOCKED';
            } else {
                $initialStatus = $config['wms']['qa_on_receipt'] ? 'QA' : 'AVAILABLE';
            }

            // Crear/actualizar stock en posición
            $stockId = $this->stockRepo->findOrCreatePosition(
                $doc['warehouse_id'],
                $receivingBin,
                $item['id'],
                $batchId,
                $initialStatus,
                $item['uom']
            );
            $this->stockRepo->addQuantity($stockId, $receivedQty);

            // Registrar movimiento
            $this->movementRepo->logMovement([
                'warehouse_id'   => $doc['warehouse_id'],
                'movement_type'  => 'RECEIPT',
                'item_id'        => $item['id'],
                'batch_id'       => $batchId,
                'to_bin_id'      => $receivingBin,
                'to_status'      => $initialStatus,
                'quantity'       => $receivedQty,
                'uom'            => $item['uom'],
                'reference_type' => 'GOODS_RECEIPT',
                'reference_id'   => $docId,
                'created_by'     => $userId,
            ]);

            // Actualizar línea
            $newReceivedQty = $line['received_qty'] + $receivedQty;
            $lineStatus = $newReceivedQty >= $line['expected_qty'] ? 'COMPLETED' : 'PARTIAL';
            $db->prepare(
                "UPDATE inventory_document_lines
                 SET received_qty = ?, batch_id = ?, batch_number = ?, expiry_date = ?, status = ?
                 WHERE id = ?"
            )->execute([$newReceivedQty, $batchId, $data['batch_number'] ?? null, $data['expiry_date'] ?? null, $lineStatus, $line['id']]);

            // Actualizar estado del documento
            $db->prepare(
                "UPDATE inventory_documents SET status = 'IN_PROGRESS' WHERE id = ? AND status = 'OPEN'"
            )->execute([$docId]);

            // Generar tarea de putaway automáticamente
            if ($config['wms']['auto_putaway']) {
                $putawayService = new PutawayService();
                $putawayService->createPutawayTask(
                    $doc['warehouse_id'],
                    $item['id'],
                    $batchId,
                    $receivingBin,
                    $receivedQty,
                    $docId,
                    $userId
                );
            }

            $db->commit();

            return $this->docRepo->getWithLines($docId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Completa un documento de recepción.
     */
    public function completeReceipt(int $docId): array
    {
        $db = Database::getConnection();

        // Verificar que todas las líneas estén completadas o parciales
        $stmt = $db->prepare(
            "SELECT COUNT(*) FROM inventory_document_lines
             WHERE document_id = ? AND status = 'PENDING'"
        );
        $stmt->execute([$docId]);
        $pending = (int) $stmt->fetchColumn();

        if ($pending > 0) {
            throw new \RuntimeException("Hay {$pending} líneas pendientes de recibir");
        }

        $this->docRepo->update($docId, ['status' => 'COMPLETED']);

        return $this->docRepo->getWithLines($docId);
    }

    private function insertDocLines(int $docId, array $lines): void
    {
        $db = Database::getConnection();
        $stmt = $db->prepare(
            "INSERT INTO inventory_document_lines
             (document_id, line_num, item_id, expected_qty, uom, sap_line_num)
             VALUES (?, ?, ?, ?, ?, ?)"
        );

        foreach ($lines as $i => $line) {
            $itemId = null;
            if (!empty($line['item_id'])) {
                $itemId = (int) $line['item_id'];
            } elseif (!empty($line['item_code'])) {
                $item = $this->itemRepo->findByCode($line['item_code']);
                if (!$item) {
                    throw new \RuntimeException("Artículo '{$line['item_code']}' no encontrado");
                }
                $itemId = $item['id'];
            } else {
                throw new \RuntimeException("Línea " . ($i + 1) . ": item_id o item_code requerido");
            }
            $stmt->execute([
                $docId,
                $i + 1,
                $itemId,
                $line['expected_qty'] ?? $line['quantity'] ?? 0,
                $line['uom'] ?? 'UN',
                $line['sap_line_num'] ?? null,
            ]);
        }
    }
}
