<?php

namespace WMS\Services;

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

/**
 * Servicio de Putaway.
 *
 * Flujo:
 * 1. Se genera tarea PUTAWAY (manual o automática post-recepción)
 * 2. Se sugiere bin destino (estrategia configurable)
 * 3. El operador confirma el putaway desde handheld
 * 4. Se mueve stock de RECEIVING a bin definitivo
 */
class PutawayService
{
    private BinRepository $binRepo;
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;
    private WarehouseTaskRepository $taskRepo;

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

    /**
     * Crea una tarea de putaway.
     */
    public function createPutawayTask(
        int $warehouseId,
        int $itemId,
        ?int $batchId,
        int $fromBinId,
        float $quantity,
        ?int $referenceId = null,
        ?int $userId = null
    ): int {
        // Sugerir bin destino
        $suggestedBin = $this->binRepo->suggestStorageBin($warehouseId, $itemId);
        $toBinId = $suggestedBin ? $suggestedBin['id'] : null;

        return $this->taskRepo->insert([
            'warehouse_id'   => $warehouseId,
            'task_type'      => 'PUTAWAY',
            'priority'       => 3,
            'status'         => 'PENDING',
            'item_id'        => $itemId,
            'batch_id'       => $batchId,
            'from_bin_id'    => $fromBinId,
            'to_bin_id'      => $toBinId,
            'quantity'       => $quantity,
            'reference_type' => 'GOODS_RECEIPT',
            'reference_id'   => $referenceId,
            'created_by'     => $userId,
        ]);
    }

    /**
     * Putaway agrupado: mueve TODOS los items de una etiqueta de transferencia a un solo bin destino.
     */
    public function groupedPutaway(int $labelId, int $toBinId, int $userId): array
    {
        $db = Database::getConnection();

        // Obtener etiqueta con líneas
        $stmt = $db->prepare("SELECT * FROM transfer_labels WHERE id = ?");
        $stmt->execute([$labelId]);
        $label = $stmt->fetch();
        if (!$label) {
            throw new \RuntimeException('Etiqueta de transferencia no encontrada');
        }

        $stmt = $db->prepare(
            "SELECT * FROM transfer_label_lines WHERE label_id = ? AND quantity > 0"
        );
        $stmt->execute([$labelId]);
        $lines = $stmt->fetchAll();
        if (empty($lines)) {
            throw new \RuntimeException('La etiqueta no tiene líneas con stock');
        }

        $db->beginTransaction();
        try {
            $movedItems = [];
            foreach ($lines as $line) {
                $qty = (float) $line['quantity'];

                // Descontar del bin origen (línea de la etiqueta)
                $fromBinId = $line['from_bin_id'] ?? $line['current_bin_id'] ?? null;
                if ($fromBinId) {
                    $fromStockId = $this->stockRepo->findOrCreatePosition(
                        $label['warehouse_id'], (int) $fromBinId, $line['item_id'], $line['batch_id'], 'AVAILABLE'
                    );
                    $this->stockRepo->subtractQuantity($fromStockId, $qty);
                }

                // Agregar al bin destino
                $toStockId = $this->stockRepo->findOrCreatePosition(
                    $label['warehouse_id'], $toBinId, $line['item_id'], $line['batch_id'], 'AVAILABLE'
                );
                $this->stockRepo->addQuantity($toStockId, $qty);

                // Registrar movimiento
                $this->movementRepo->logMovement([
                    'warehouse_id'   => $label['warehouse_id'],
                    'movement_type'  => 'PUTAWAY_GROUPED',
                    'item_id'        => $line['item_id'],
                    'batch_id'       => $line['batch_id'],
                    'from_bin_id'    => $fromBinId,
                    'to_bin_id'      => $toBinId,
                    'quantity'       => $qty,
                    'reference_type' => 'TRANSFER_LABEL',
                    'reference_id'   => $labelId,
                    'created_by'     => $userId,
                ]);

                $movedItems[] = [
                    'item_id'  => $line['item_id'],
                    'batch_id' => $line['batch_id'],
                    'quantity' => $qty,
                ];
            }

            $db->commit();

            return [
                'label_id'    => $labelId,
                'to_bin_id'   => $toBinId,
                'moved_items' => $movedItems,
                'total_lines' => count($movedItems),
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Sugiere el mejor bin para guardar un artículo.
     * Prioridad: 1) Mismo item ya almacenado, 2) Misma zona, 3) FEFO compatible,
     * 4) Bin vacío, 5) Más cercano.
     */
    public function suggestBin(int $itemId, int $warehouseId): array
    {
        $db = Database::getConnection();
        $suggestions = [];

        // 1) Bin donde ya existe el mismo item con stock AVAILABLE
        $stmt = $db->prepare(
            "SELECT b.id, b.code, b.zone, 'SAME_ITEM' AS strategy,
                    SUM(s.quantity) AS current_qty
             FROM stock s
             JOIN bins b ON s.bin_id = b.id
             WHERE s.item_id = ? AND s.warehouse_id = ?
               AND s.stock_status = 'AVAILABLE' AND s.quantity > 0
               AND b.bin_type = 'STORAGE' AND b.is_active = 1
             GROUP BY b.id, b.code, b.zone
             ORDER BY current_qty ASC
             LIMIT 3"
        );
        $stmt->execute([$itemId, $warehouseId]);
        $sameItem = $stmt->fetchAll();
        foreach ($sameItem as $row) {
            $suggestions[] = $row;
        }

        // 2) Bin vacío en la misma zona del item (si ya existe una zona asignada)
        $stmt = $db->prepare(
            "SELECT DISTINCT b2.zone FROM stock s2
             JOIN bins b2 ON s2.bin_id = b2.id
             WHERE s2.item_id = ? AND s2.warehouse_id = ? AND b2.zone IS NOT NULL
             LIMIT 1"
        );
        $stmt->execute([$itemId, $warehouseId]);
        $zoneRow = $stmt->fetch();

        if ($zoneRow && $zoneRow['zone']) {
            $stmt = $db->prepare(
                "SELECT b.id, b.code, b.zone, 'SAME_ZONE' AS strategy
                 FROM bins b
                 LEFT JOIN stock s ON s.bin_id = b.id AND s.quantity > 0
                 WHERE b.warehouse_id = ? AND b.zone = ? AND b.bin_type = 'STORAGE'
                   AND b.is_active = 1
                 GROUP BY b.id, b.code, b.zone
                 HAVING COALESCE(SUM(s.quantity), 0) = 0
                 ORDER BY b.code ASC
                 LIMIT 2"
            );
            $stmt->execute([$warehouseId, $zoneRow['zone']]);
            foreach ($stmt->fetchAll() as $row) {
                $suggestions[] = $row;
            }
        }

        // 3) Cualquier bin vacío
        $stmt = $db->prepare(
            "SELECT b.id, b.code, b.zone, 'EMPTY_BIN' AS strategy
             FROM bins b
             LEFT JOIN stock s ON s.bin_id = b.id AND s.quantity > 0
             WHERE b.warehouse_id = ? AND b.bin_type = 'STORAGE' AND b.is_active = 1
             GROUP BY b.id, b.code, b.zone
             HAVING COALESCE(SUM(s.quantity), 0) = 0
             ORDER BY b.code ASC
             LIMIT 3"
        );
        $stmt->execute([$warehouseId]);
        foreach ($stmt->fetchAll() as $row) {
            $suggestions[] = $row;
        }

        // Devolver la mejor sugerencia y las alternativas
        $best = !empty($suggestions) ? $suggestions[0] : null;

        return [
            'best_suggestion' => $best,
            'alternatives'    => array_slice($suggestions, 1, 4),
        ];
    }

    /**
     * Mueve TODO el stock de un bin a otro.
     */
    public function putawayByLocation(int $fromBinId, int $toBinId, int $userId): array
    {
        $db = Database::getConnection();

        $stocks = $this->stockRepo->getStockByBin($fromBinId);
        if (empty($stocks)) {
            throw new \RuntimeException('No hay stock en el bin origen');
        }

        // Obtener warehouse_id del bin
        $stmt = $db->prepare("SELECT warehouse_id FROM bins WHERE id = ?");
        $stmt->execute([$fromBinId]);
        $bin = $stmt->fetch();
        if (!$bin) {
            throw new \RuntimeException('Bin origen no encontrado');
        }
        $warehouseId = (int) $bin['warehouse_id'];

        $db->beginTransaction();
        try {
            $movedItems = [];
            foreach ($stocks as $stock) {
                $qty = (float) $stock['quantity'];
                if ($qty <= 0) continue;

                $status = $stock['stock_status'] ?? 'AVAILABLE';

                // Descontar del origen
                $this->stockRepo->subtractQuantity($stock['id'], $qty);

                // Agregar al destino
                $toStockId = $this->stockRepo->findOrCreatePosition(
                    $warehouseId, $toBinId, $stock['item_id'], $stock['batch_id'], $status
                );
                $this->stockRepo->addQuantity($toStockId, $qty);

                // Registrar movimiento
                $this->movementRepo->logMovement([
                    'warehouse_id'   => $warehouseId,
                    'movement_type'  => 'PUTAWAY_BY_LOCATION',
                    'item_id'        => $stock['item_id'],
                    'batch_id'       => $stock['batch_id'],
                    'from_bin_id'    => $fromBinId,
                    'to_bin_id'      => $toBinId,
                    'quantity'       => $qty,
                    'reference_type' => 'PUTAWAY',
                    'created_by'     => $userId,
                ]);

                $movedItems[] = [
                    'item_id'  => $stock['item_id'],
                    'batch_id' => $stock['batch_id'],
                    'quantity' => $qty,
                ];
            }

            $db->commit();

            return [
                'from_bin_id' => $fromBinId,
                'to_bin_id'   => $toBinId,
                'moved_items' => $movedItems,
                'total_lines' => count($movedItems),
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Modo libre: mueve una cantidad específica de un item entre bins.
     */
    public function freePutaway(array $data, int $userId): array
    {
        $db = Database::getConnection();

        $itemId    = (int) $data['item_id'];
        $batchId   = isset($data['batch_id']) ? (int) $data['batch_id'] : null;
        $fromBinId = (int) $data['from_bin_id'];
        $toBinId   = (int) $data['to_bin_id'];
        $qty       = (float) $data['quantity'];

        // Obtener warehouse_id del bin origen
        $stmt = $db->prepare("SELECT warehouse_id FROM bins WHERE id = ?");
        $stmt->execute([$fromBinId]);
        $bin = $stmt->fetch();
        if (!$bin) {
            throw new \RuntimeException('Bin origen no encontrado');
        }
        $warehouseId = (int) $bin['warehouse_id'];

        $db->beginTransaction();
        try {
            // Descontar del origen
            $fromStockId = $this->stockRepo->findOrCreatePosition(
                $warehouseId, $fromBinId, $itemId, $batchId, 'AVAILABLE'
            );
            $this->stockRepo->subtractQuantity($fromStockId, $qty);

            // Agregar al destino
            $toStockId = $this->stockRepo->findOrCreatePosition(
                $warehouseId, $toBinId, $itemId, $batchId, 'AVAILABLE'
            );
            $this->stockRepo->addQuantity($toStockId, $qty);

            // Registrar movimiento
            $this->movementRepo->logMovement([
                'warehouse_id'   => $warehouseId,
                'movement_type'  => 'PUTAWAY_FREE',
                'item_id'        => $itemId,
                'batch_id'       => $batchId,
                'from_bin_id'    => $fromBinId,
                'to_bin_id'      => $toBinId,
                'quantity'       => $qty,
                'reference_type' => 'PUTAWAY',
                'created_by'     => $userId,
            ]);

            $db->commit();

            return [
                'item_id'     => $itemId,
                'batch_id'    => $batchId,
                'from_bin_id' => $fromBinId,
                'to_bin_id'   => $toBinId,
                'quantity'    => $qty,
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Confirma un putaway: mueve stock del bin origen al bin destino.
     */
    public function confirmPutaway(int $taskId, array $data, int $userId): array
    {
        $task = $this->taskRepo->findById($taskId);
        if (!$task || $task['task_type'] !== 'PUTAWAY') {
            throw new \RuntimeException('Tarea de putaway no válida');
        }
        if ($task['status'] === 'COMPLETED' || $task['status'] === 'CANCELLED') {
            throw new \RuntimeException('Esta tarea ya fue procesada');
        }

        $confirmedQty = (float) ($data['confirmed_qty'] ?? $task['quantity']);
        $toBinId = (int) ($data['to_bin_id'] ?? $task['to_bin_id']);

        if (!$toBinId) {
            throw new \RuntimeException('Debe indicar el bin destino');
        }

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

        try {
            // Descontar del bin origen
            $fromStockId = $this->stockRepo->findOrCreatePosition(
                $task['warehouse_id'],
                $task['from_bin_id'],
                $task['item_id'],
                $task['batch_id'],
                'QA' // En recepción entra como QA
            );
            $this->stockRepo->subtractQuantity($fromStockId, $confirmedQty);

            // Agregar al bin destino (mantiene estado QA hasta liberación)
            $toStockId = $this->stockRepo->findOrCreatePosition(
                $task['warehouse_id'],
                $toBinId,
                $task['item_id'],
                $task['batch_id'],
                'QA'
            );
            $this->stockRepo->addQuantity($toStockId, $confirmedQty);

            // Registrar movimiento
            $this->movementRepo->logMovement([
                'warehouse_id'  => $task['warehouse_id'],
                'movement_type' => 'PUTAWAY',
                'item_id'       => $task['item_id'],
                'batch_id'      => $task['batch_id'],
                'from_bin_id'   => $task['from_bin_id'],
                'to_bin_id'     => $toBinId,
                'from_status'   => 'QA',
                'to_status'     => 'QA',
                'quantity'      => $confirmedQty,
                'reference_type'=> 'TASK',
                'reference_id'  => $taskId,
                'created_by'    => $userId,
            ]);

            // Completar tarea
            $this->taskRepo->completeTask($taskId, $confirmedQty);

            $db->commit();

            return $this->taskRepo->findById($taskId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }
}
