<?php

namespace WMS\Services;

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

/**
 * Servicio de Picking.
 *
 * Flujo:
 * 1. Se crea pick list desde pedido SAP (o manual)
 * 2. Se asignan lotes por FEFO automáticamente
 * 3. Se generan tareas de PICK por cada línea
 * 4. El operador confirma picking escaneando bin + lote
 * 5. Se descuenta stock y se mueve a bin SHIPPING/STAGING
 */
class PickingService
{
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;
    private WarehouseTaskRepository $taskRepo;
    private PickListRepository $pickListRepo;
    private BinRepository $binRepo;

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

    /**
     * Genera una pick list y asigna lotes por FEFO.
     */
    public function createPickList(array $data, int $userId): array
    {
        $warehouseId = (int) $data['warehouse_id'];
        $pickNumber = Database::nextDocNumber('PICK');

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

        try {
            // Crear cabecera
            $pickListId = $this->pickListRepo->insert([
                'pick_number'   => $pickNumber,
                'warehouse_id'  => $warehouseId,
                'status'        => 'OPEN',
                'sap_doc_entry' => $data['sap_doc_entry'] ?? null,
                'sap_doc_num'   => $data['sap_doc_num'] ?? null,
                'customer_code' => $data['customer_code'] ?? null,
                'customer_name' => $data['customer_name'] ?? null,
                'ship_date'     => $data['ship_date'] ?? null,
                'notes'         => $data['notes'] ?? null,
                'created_by'    => $userId,
            ]);

            // Procesar líneas con asignación FEFO
            foreach ($data['lines'] as $i => $line) {
                $itemId = (int) $line['item_id'];
                $requestedQty = (float) $line['quantity'];

                // Obtener stock disponible ordenado por FEFO
                $available = $this->stockRepo->getAvailableForPicking($itemId, $warehouseId);

                $remainingQty = $requestedQty;
                $lineNum = $i + 1;

                foreach ($available as $stock) {
                    if ($remainingQty <= 0) break;

                    $availableQty = (float) $stock['available_qty'];
                    $allocateQty = min($remainingQty, $availableQty);

                    // Insertar línea de pick list
                    $stmt = $db->prepare(
                        "INSERT INTO pick_list_lines
                         (pick_list_id, line_num, item_id, batch_id, from_bin_id, requested_qty, uom, sap_line_num)
                         VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
                    );
                    $stmt->execute([
                        $pickListId,
                        $lineNum,
                        $itemId,
                        $stock['batch_id'],
                        $stock['bin_id'],
                        $allocateQty,
                        $stock['uom'] ?? 'UN',
                        $line['sap_line_num'] ?? null,
                    ]);

                    // Reservar stock
                    $this->stockRepo->reserve($stock['id'], $allocateQty);

                    // Crear tarea de picking
                    $this->taskRepo->insert([
                        'warehouse_id'   => $warehouseId,
                        'task_type'      => 'PICK',
                        'priority'       => 2,
                        'item_id'        => $itemId,
                        'batch_id'       => $stock['batch_id'],
                        'from_bin_id'    => $stock['bin_id'],
                        'quantity'       => $allocateQty,
                        'reference_type' => 'PICK_LIST',
                        'reference_id'   => $pickListId,
                        'created_by'     => $userId,
                    ]);

                    $remainingQty -= $allocateQty;
                    $lineNum++;
                }

                if ($remainingQty > 0) {
                    // Stock insuficiente: registrar short
                    $stmt = $db->prepare(
                        "INSERT INTO pick_list_lines
                         (pick_list_id, line_num, item_id, requested_qty, uom, status, notes)
                         VALUES (?, ?, ?, ?, 'UN', 'SHORT', 'Stock insuficiente')"
                    );
                    $stmt->execute([$pickListId, $lineNum, $itemId, $remainingQty]);
                }
            }

            $db->commit();

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

    /**
     * Confirma el picking de una tarea.
     * Descuenta stock del bin origen y registra movimiento.
     */
    public function confirmPick(int $taskId, array $data, int $userId): array
    {
        $task = $this->taskRepo->findById($taskId);
        if (!$task || $task['task_type'] !== 'PICK') {
            throw new \RuntimeException('Tarea de picking no válida');
        }
        if ($task['status'] === 'COMPLETED') {
            throw new \RuntimeException('Esta tarea ya fue confirmada');
        }

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

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

        try {
            // Descontar stock
            $stockId = $this->stockRepo->findOrCreatePosition(
                $task['warehouse_id'],
                $task['from_bin_id'],
                $task['item_id'],
                $task['batch_id'],
                'AVAILABLE'
            );

            // Liberar reserva y descontar
            $this->stockRepo->unreserve($stockId, $task['quantity']);
            $this->stockRepo->subtractQuantity($stockId, $confirmedQty);

            // Registrar movimiento
            $this->movementRepo->logMovement([
                'warehouse_id'  => $task['warehouse_id'],
                'movement_type' => 'PICK',
                'item_id'       => $task['item_id'],
                'batch_id'      => $task['batch_id'],
                'from_bin_id'   => $task['from_bin_id'],
                'from_status'   => 'AVAILABLE',
                'quantity'      => $confirmedQty,
                'reference_type'=> 'PICK_LIST',
                'reference_id'  => $task['reference_id'],
                'created_by'    => $userId,
            ]);

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

            // Actualizar línea del pick list
            if ($task['reference_id']) {
                $db->prepare(
                    "UPDATE pick_list_lines SET picked_qty = picked_qty + ?, status = 'PICKED'
                     WHERE pick_list_id = ? AND item_id = ? AND batch_id <=> ? AND status != 'PICKED'
                     LIMIT 1"
                )->execute([$confirmedQty, $task['reference_id'], $task['item_id'], $task['batch_id']]);
            }

            $db->commit();

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