<?php

namespace WMS\Services;

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

/**
 * Servicio de Olas de Picking (Picking Waves).
 *
 * Flujo:
 * 1. Se crea una ola agrupando pick lists por criterio (ruta, cliente, prioridad)
 * 2. Al liberar la ola se verifica stock FEFO para cada linea
 * 3. Si hay stock suficiente: se reserva y se crean tareas PICK
 * 4. Si hay faltante: se crea registro de rechazo (picking_rejection)
 * 5. Los rechazos se pueden resolver manualmente
 */
class WaveService
{
    private StockRepository $stockRepo;
    private StockMovementRepository $movementRepo;
    private WarehouseTaskRepository $taskRepo;
    private PickListRepository $pickListRepo;

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

    /**
     * Crea una ola de picking agrupando pick lists.
     */
    public function createWave(array $data, int $userId): array
    {
        $warehouseId     = (int) $data['warehouse_id'];
        $releaseCriteria = $data['release_criteria'] ?? 'MANUAL';
        $pickListIds     = $data['pick_list_ids'] ?? [];
        $routeCode       = $data['route_code'] ?? null;
        $customerCode    = $data['customer_code'] ?? null;

        if (empty($pickListIds)) {
            throw new \RuntimeException('Debe incluir al menos una pick list');
        }

        $waveNumber = Database::nextDocNumber('WAVE');

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

        try {
            // Crear cabecera de ola
            $stmt = $db->prepare(
                "INSERT INTO picking_waves
                 (wave_number, warehouse_id, release_criteria, route_code, customer_code, status, total_pick_lists, created_by)
                 VALUES (?, ?, ?, ?, ?, 'OPEN', ?, ?)"
            );
            $stmt->execute([
                $waveNumber,
                $warehouseId,
                $releaseCriteria,
                $routeCode,
                $customerCode,
                count($pickListIds),
                $userId,
            ]);
            $waveId = (int) $db->lastInsertId();

            // Asociar pick lists a la ola
            $stmtUpdate = $db->prepare(
                "UPDATE pick_lists SET wave_id = ?, status = 'WAVE_ASSIGNED'
                 WHERE id = ? AND warehouse_id = ? AND status = 'OPEN'"
            );

            $assigned = 0;
            foreach ($pickListIds as $plId) {
                $stmtUpdate->execute([$waveId, (int) $plId, $warehouseId]);
                if ($stmtUpdate->rowCount() > 0) {
                    $assigned++;
                }
            }

            if ($assigned === 0) {
                throw new \RuntimeException('Ninguna pick list valida para asignar a la ola');
            }

            // Actualizar total real
            $db->prepare("UPDATE picking_waves SET total_pick_lists = ? WHERE id = ?")
                ->execute([$assigned, $waveId]);

            $db->commit();

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

    /**
     * Libera la ola: verifica stock FEFO, reserva y crea tareas.
     * Si hay faltante, crea registros de rechazo.
     *
     * @return array {released: int, rejected: int, rejections: array}
     */
    public function releaseWave(int $waveId, int $userId): array
    {
        $db = Database::getConnection();

        $wave = $this->findWave($waveId);
        if (!$wave) {
            throw new \RuntimeException('Ola no encontrada');
        }
        if ($wave['status'] !== 'OPEN') {
            throw new \RuntimeException('La ola ya fue liberada o cerrada');
        }

        // Obtener pick lists de la ola
        $stmt = $db->prepare(
            "SELECT id, warehouse_id FROM pick_lists WHERE wave_id = ? AND status = 'WAVE_ASSIGNED'"
        );
        $stmt->execute([$waveId]);
        $pickLists = $stmt->fetchAll();

        if (empty($pickLists)) {
            throw new \RuntimeException('No hay pick lists asignadas a esta ola');
        }

        $db->beginTransaction();

        try {
            $totalReleased  = 0;
            $totalRejected  = 0;
            $rejections     = [];

            foreach ($pickLists as $pl) {
                $pickListId  = (int) $pl['id'];
                $warehouseId = (int) $pl['warehouse_id'];

                // Obtener lineas de la pick list
                $stmtLines = $db->prepare(
                    "SELECT pll.*, i.item_code, i.item_name
                     FROM pick_list_lines pll
                     JOIN items i ON pll.item_id = i.id
                     WHERE pll.pick_list_id = ? AND pll.status IN ('PENDING','SHORT')"
                );
                $stmtLines->execute([$pickListId]);
                $lines = $stmtLines->fetchAll();

                foreach ($lines as $line) {
                    $itemId       = (int) $line['item_id'];
                    $requestedQty = (float) $line['requested_qty'];

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

                    $remainingQty = $requestedQty;
                    $allocatedQty = 0;

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

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

                        // Reservar stock
                        $reserved = $this->stockRepo->reserve($stock['id'], $allocateQty);
                        if (!$reserved) continue;

                        // 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' => 'WAVE',
                            'reference_id'   => $waveId,
                            'created_by'     => $userId,
                        ]);

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

                    if ($allocatedQty > 0) {
                        $totalReleased++;
                    }

                    // Stock insuficiente: crear rechazo
                    if ($remainingQty > 0) {
                        $totalRejected++;

                        $stmtRej = $db->prepare(
                            "INSERT INTO picking_rejections
                             (wave_id, pick_list_id, item_id, item_code, requested_qty, available_qty, shortage_qty, status)
                             VALUES (?, ?, ?, ?, ?, ?, ?, 'PENDING')"
                        );
                        $stmtRej->execute([
                            $waveId,
                            $pickListId,
                            $itemId,
                            $line['item_code'],
                            $requestedQty,
                            $requestedQty - $remainingQty,
                            $remainingQty,
                        ]);

                        $rejection = [
                            'id'            => (int) $db->lastInsertId(),
                            'pick_list_id'  => $pickListId,
                            'item_code'     => $line['item_code'],
                            'item_name'     => $line['item_name'],
                            'requested_qty' => $requestedQty,
                            'available_qty' => $requestedQty - $remainingQty,
                            'shortage_qty'  => $remainingQty,
                        ];
                        $rejections[] = $rejection;
                    }

                    // Actualizar status de la linea
                    $newStatus = $remainingQty <= 0 ? 'RELEASED' : ($allocatedQty > 0 ? 'PARTIAL' : 'SHORT');
                    $db->prepare("UPDATE pick_list_lines SET status = ? WHERE id = ?")
                        ->execute([$newStatus, $line['id']]);
                }

                // Actualizar status del pick list
                $plStatus = $totalRejected === 0 ? 'RELEASED' : 'PARTIAL';
                $db->prepare("UPDATE pick_lists SET status = ? WHERE id = ?")
                    ->execute([$plStatus, $pickListId]);
            }

            // Actualizar ola
            $waveStatus = $totalRejected === 0 ? 'RELEASED' : 'PARTIAL';
            $db->prepare(
                "UPDATE picking_waves SET status = ?, released_at = NOW(), released_by = ? WHERE id = ?"
            )->execute([$waveStatus, $userId, $waveId]);

            $db->commit();

            return [
                'wave_id'    => $waveId,
                'status'     => $waveStatus,
                'released'   => $totalReleased,
                'rejected'   => $totalRejected,
                'rejections' => $rejections,
            ];
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Obtiene los rechazos de una ola.
     */
    public function getRejections(int $waveId): array
    {
        $db = Database::getConnection();

        $stmt = $db->prepare(
            "SELECT pr.*, i.item_name, pl.pick_number,
                    u.full_name AS resolved_by_name
             FROM picking_rejections pr
             JOIN items i ON pr.item_id = i.id
             JOIN pick_lists pl ON pr.pick_list_id = pl.id
             LEFT JOIN users u ON pr.resolved_by = u.id
             WHERE pr.wave_id = ?
             ORDER BY pr.id ASC"
        );
        $stmt->execute([$waveId]);
        return $stmt->fetchAll();
    }

    /**
     * Resuelve un rechazo de picking.
     */
    public function resolveRejection(int $rejectionId, string $resolution, int $userId): array
    {
        $db = Database::getConnection();

        $stmt = $db->prepare("SELECT * FROM picking_rejections WHERE id = ?");
        $stmt->execute([$rejectionId]);
        $rejection = $stmt->fetch();

        if (!$rejection) {
            throw new \RuntimeException('Rechazo no encontrado');
        }
        if ($rejection['status'] !== 'PENDING') {
            throw new \RuntimeException('Este rechazo ya fue resuelto');
        }

        $db->prepare(
            "UPDATE picking_rejections
             SET status = 'RESOLVED', resolution = ?, resolved_by = ?, resolved_at = NOW()
             WHERE id = ?"
        )->execute([$resolution, $userId, $rejectionId]);

        $stmt = $db->prepare("SELECT * FROM picking_rejections WHERE id = ?");
        $stmt->execute([$rejectionId]);
        return $stmt->fetch();
    }

    /**
     * Obtiene una ola con sus pick lists.
     */
    public function getWaveWithDetails(int $waveId): ?array
    {
        $db = Database::getConnection();

        $stmt = $db->prepare(
            "SELECT pw.*, u.full_name AS created_by_name, ur.full_name AS released_by_name
             FROM picking_waves pw
             LEFT JOIN users u ON pw.created_by = u.id
             LEFT JOIN users ur ON pw.released_by = ur.id
             WHERE pw.id = ?"
        );
        $stmt->execute([$waveId]);
        $wave = $stmt->fetch();

        if (!$wave) {
            return null;
        }

        // Obtener pick lists de la ola
        $stmt = $db->prepare(
            "SELECT pl.*, c.full_name AS customer_name_display
             FROM pick_lists pl
             LEFT JOIN users c ON pl.created_by = c.id
             WHERE pl.wave_id = ?
             ORDER BY pl.id"
        );
        $stmt->execute([$waveId]);
        $wave['pick_lists'] = $stmt->fetchAll();

        // Obtener rechazos
        $wave['rejections'] = $this->getRejections($waveId);

        return $wave;
    }

    /**
     * Lista olas filtradas por almacen y/o estado.
     */
    public function listWaves(?int $warehouseId = null, ?string $status = null): array
    {
        $db = Database::getConnection();

        $sql = "SELECT pw.*, u.full_name AS created_by_name,
                       (SELECT COUNT(*) FROM picking_rejections pr WHERE pr.wave_id = pw.id AND pr.status = 'PENDING') AS pending_rejections
                FROM picking_waves pw
                LEFT JOIN users u ON pw.created_by = u.id
                WHERE 1=1";
        $params = [];

        if ($warehouseId) {
            $sql .= " AND pw.warehouse_id = ?";
            $params[] = $warehouseId;
        }
        if ($status) {
            $sql .= " AND pw.status = ?";
            $params[] = $status;
        }

        $sql .= " ORDER BY pw.created_at DESC LIMIT 100";
        $stmt = $db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }

    /**
     * Busca una ola por ID.
     */
    private function findWave(int $id): ?array
    {
        $db = Database::getConnection();
        $stmt = $db->prepare("SELECT * FROM picking_waves WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch() ?: null;
    }
}
