<?php

namespace WMS\Services;

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

/**
 * Servicio de Control de Calidad (QA).
 *
 * Flujo:
 * 1. Stock llega con estado QA (cuarentena) desde recepción
 * 2. Inspector revisa el lote y decide: aprobar, rechazar o mantener en cuarentena
 * 3. Aprobación: stock pasa de QA a AVAILABLE, lote pasa a AVAILABLE
 * 4. Rechazo: stock pasa de QA a BLOCKED, lote pasa a REJECTED
 * 5. Cuarentena extendida: stock permanece QA, se registra motivo
 * 6. Todas las decisiones se registran en qa_decisions y stock_movements
 */
class QaService
{
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;
    private BatchRepository $batchRepo;
    private BinRepository $binRepo;

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

    /**
     * Aprueba un lote: stock QA -> AVAILABLE.
     * Opcionalmente mueve el stock a un bin de almacenamiento.
     */
    public function approveBatch(int $batchId, array $data, int $userId): array
    {
        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            $batch = $this->batchRepo->findById($batchId);
            if (!$batch) {
                throw new \RuntimeException('Lote no encontrado');
            }

            // Obtener posiciones de stock en QA para este lote
            $stockPositions = $this->getQaStockForBatch($batchId);
            if (empty($stockPositions)) {
                throw new \RuntimeException('No hay stock en estado QA para este lote');
            }

            $targetBinId = $data['target_bin_id'] ?? null;

            foreach ($stockPositions as $position) {
                $qty = (float) $position['quantity'];
                if ($qty <= 0) {
                    continue;
                }

                if ($targetBinId) {
                    // Mover a nuevo bin con estado AVAILABLE
                    $newStockId = $this->stockRepo->findOrCreatePosition(
                        $position['warehouse_id'],
                        $targetBinId,
                        $position['item_id'],
                        $position['batch_id'],
                        'AVAILABLE',
                        $position['uom']
                    );
                    $this->stockRepo->addQuantity($newStockId, $qty);
                    $this->stockRepo->subtractQuantity($position['id'], $qty);

                    // Registrar movimiento con cambio de bin
                    $this->movementRepo->logMovement([
                        'warehouse_id'   => $position['warehouse_id'],
                        'movement_type'  => 'QA_APPROVE',
                        'item_id'        => $position['item_id'],
                        'batch_id'       => $position['batch_id'],
                        'from_bin_id'    => $position['bin_id'],
                        'from_status'    => 'QA',
                        'to_bin_id'      => $targetBinId,
                        'to_status'      => 'AVAILABLE',
                        'quantity'       => $qty,
                        'uom'            => $position['uom'],
                        'reference_type' => 'QA_DECISION',
                        'reference_id'   => $batchId,
                        'created_by'     => $userId,
                    ]);
                } else {
                    // Cambiar estado in-place: QA -> AVAILABLE
                    $newStockId = $this->stockRepo->findOrCreatePosition(
                        $position['warehouse_id'],
                        $position['bin_id'],
                        $position['item_id'],
                        $position['batch_id'],
                        'AVAILABLE',
                        $position['uom']
                    );
                    $this->stockRepo->addQuantity($newStockId, $qty);
                    $this->stockRepo->subtractQuantity($position['id'], $qty);

                    // Registrar movimiento de cambio de estado
                    $this->movementRepo->logMovement([
                        'warehouse_id'   => $position['warehouse_id'],
                        'movement_type'  => 'QA_APPROVE',
                        'item_id'        => $position['item_id'],
                        'batch_id'       => $position['batch_id'],
                        'from_bin_id'    => $position['bin_id'],
                        'from_status'    => 'QA',
                        'to_bin_id'      => $position['bin_id'],
                        'to_status'      => 'AVAILABLE',
                        'quantity'       => $qty,
                        'uom'            => $position['uom'],
                        'reference_type' => 'QA_DECISION',
                        'reference_id'   => $batchId,
                        'created_by'     => $userId,
                    ]);
                }
            }

            // Actualizar estado del lote
            $this->batchRepo->updateStatus($batchId, 'AVAILABLE');

            // Registrar decisión QA
            $this->logQaDecision($db, [
                'batch_id'    => $batchId,
                'decision'    => 'APPROVED',
                'reason'      => $data['reason'] ?? null,
                'notes'       => $data['notes'] ?? null,
                'decided_by'  => $userId,
            ]);

            $db->commit();

            return [
                'batch_id' => $batchId,
                'decision' => 'APPROVED',
                'batch'    => $this->batchRepo->findById($batchId),
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Rechaza un lote: stock QA -> BLOCKED.
     * El stock se mueve a un bin de rechazados si está configurado.
     */
    public function rejectBatch(int $batchId, array $data, int $userId): array
    {
        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            $batch = $this->batchRepo->findById($batchId);
            if (!$batch) {
                throw new \RuntimeException('Lote no encontrado');
            }

            if (empty($data['reason'])) {
                throw new \RuntimeException('El motivo de rechazo es obligatorio');
            }

            $stockPositions = $this->getQaStockForBatch($batchId);
            if (empty($stockPositions)) {
                throw new \RuntimeException('No hay stock en estado QA para este lote');
            }

            $targetBinId = $data['target_bin_id'] ?? null;

            foreach ($stockPositions as $position) {
                $qty = (float) $position['quantity'];
                if ($qty <= 0) {
                    continue;
                }

                $destBinId = $targetBinId ?? $position['bin_id'];

                // Crear posición BLOCKED
                $newStockId = $this->stockRepo->findOrCreatePosition(
                    $position['warehouse_id'],
                    $destBinId,
                    $position['item_id'],
                    $position['batch_id'],
                    'BLOCKED',
                    $position['uom']
                );
                $this->stockRepo->addQuantity($newStockId, $qty);
                $this->stockRepo->subtractQuantity($position['id'], $qty);

                // Registrar movimiento
                $this->movementRepo->logMovement([
                    'warehouse_id'   => $position['warehouse_id'],
                    'movement_type'  => 'QA_REJECT',
                    'item_id'        => $position['item_id'],
                    'batch_id'       => $position['batch_id'],
                    'from_bin_id'    => $position['bin_id'],
                    'from_status'    => 'QA',
                    'to_bin_id'      => $destBinId,
                    'to_status'      => 'BLOCKED',
                    'quantity'       => $qty,
                    'uom'            => $position['uom'],
                    'reference_type' => 'QA_DECISION',
                    'reference_id'   => $batchId,
                    'created_by'     => $userId,
                ]);
            }

            // Actualizar estado del lote
            $this->batchRepo->updateStatus($batchId, 'REJECTED');

            // Registrar decisión QA
            $this->logQaDecision($db, [
                'batch_id'    => $batchId,
                'decision'    => 'REJECTED',
                'reason'      => $data['reason'],
                'notes'       => $data['notes'] ?? null,
                'decided_by'  => $userId,
            ]);

            $db->commit();

            return [
                'batch_id' => $batchId,
                'decision' => 'REJECTED',
                'batch'    => $this->batchRepo->findById($batchId),
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Mantiene un lote en cuarentena extendida.
     * Stock permanece en QA pero se registra el motivo y la revisión.
     */
    public function quarantineBatch(int $batchId, array $data, int $userId): array
    {
        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            $batch = $this->batchRepo->findById($batchId);
            if (!$batch) {
                throw new \RuntimeException('Lote no encontrado');
            }

            if (empty($data['reason'])) {
                throw new \RuntimeException('El motivo de cuarentena extendida es obligatorio');
            }

            $stockPositions = $this->getQaStockForBatch($batchId);
            if (empty($stockPositions)) {
                throw new \RuntimeException('No hay stock en estado QA para este lote');
            }

            // Registrar movimiento informativo (sin cambio de estado)
            foreach ($stockPositions as $position) {
                $qty = (float) $position['quantity'];
                if ($qty <= 0) {
                    continue;
                }

                $this->movementRepo->logMovement([
                    'warehouse_id'   => $position['warehouse_id'],
                    'movement_type'  => 'QA_QUARANTINE',
                    'item_id'        => $position['item_id'],
                    'batch_id'       => $position['batch_id'],
                    'from_bin_id'    => $position['bin_id'],
                    'from_status'    => 'QA',
                    'to_bin_id'      => $position['bin_id'],
                    'to_status'      => 'QA',
                    'quantity'       => $qty,
                    'uom'            => $position['uom'],
                    'reference_type' => 'QA_DECISION',
                    'reference_id'   => $batchId,
                    'created_by'     => $userId,
                ]);
            }

            // Registrar decisión QA
            $this->logQaDecision($db, [
                'batch_id'       => $batchId,
                'decision'       => 'QUARANTINE',
                'reason'         => $data['reason'],
                'notes'          => $data['notes'] ?? null,
                'review_date'    => $data['review_date'] ?? null,
                'decided_by'     => $userId,
            ]);

            $db->commit();

            return [
                'batch_id' => $batchId,
                'decision' => 'QUARANTINE',
                'batch'    => $this->batchRepo->findById($batchId),
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Obtiene posiciones de stock en estado QA para un lote dado.
     */
    private function getQaStockForBatch(int $batchId): array
    {
        $db = Database::getConnection();
        $stmt = $db->prepare(
            "SELECT * FROM stock WHERE batch_id = ? AND stock_status = 'QA' AND quantity > 0"
        );
        $stmt->execute([$batchId]);
        return $stmt->fetchAll();
    }

    /**
     * Registra una decisión de QA en la tabla qa_decisions.
     */
    private function logQaDecision(\PDO $db, array $data): int
    {
        $stmt = $db->prepare(
            "INSERT INTO qa_decisions (batch_id, decision, reason, notes, review_date, decided_by)
             VALUES (?, ?, ?, ?, ?, ?)"
        );
        $stmt->execute([
            $data['batch_id'],
            $data['decision'],
            $data['reason'] ?? null,
            $data['notes'] ?? null,
            $data['review_date'] ?? null,
            $data['decided_by'],
        ]);
        return (int) $db->lastInsertId();
    }
}
