<?php

namespace SiipAvailableIps;

class IpSearchService
{
    private $apiUrl;
    private $apiToken;
    private $logger;
    private $config;

    public function __construct($apiUrl, $apiToken, $logger = null, $config = [])
    {
        $this->apiUrl = $apiUrl;
        $this->apiToken = $apiToken;
        $this->logger = $logger;
        $this->config = $config;
    }

    /**
     * Determina si una IP es administrativa (no recomendada para clientes)
     * 
     * @param string $ip Dirección IP completa (ej: 172.16.13.5)
     * @param array $config Configuración del plugin con rangos admin
     * @return bool True si es IP administrativa
     */
    public static function isAdminIp($ip, $config = [])
    {
        // Si está habilitada la configuración personalizada por segmento
        if (!empty($config['useCustomAdminRanges']) && !empty($config['customAdminRangesJson'])) {
            // Intentar obtener límites del segmento desde JSON
            $parts = explode('.', $ip);
            if (count($parts) === 4) {
                $segment = $parts[2];
                $limits = AdminRangeHelper::getSegmentLimits($segment, $config['customAdminRangesJson']);
                
                // Si el segmento está configurado en JSON, usar esos rangos
                if ($limits !== null) {
                    $isCustom = AdminRangeHelper::isAdminIpCustom($ip, $config['customAdminRangesJson']);
                    // Log para depuración (temporal)
                    if (isset($config['debugMode']) && $config['debugMode']) {
                        error_log("Check Custom IP $ip (Seg $segment): " . ($isCustom ? 'ADMIN' : 'CLIENT'));
                    }
                    return $isCustom;
                }
                // Si el segmento NO está en JSON, usar rangos globales como fallback
            }
        } else {
             if (isset($config['debugMode']) && $config['debugMode']) {
                error_log("Custom ranges disabled or empty JSON. Config: " . print_r($config, true));
             }
        }
        
        // Usar configuración estándar (rangos globales)
        $parts = explode('.', $ip);
        if (count($parts) !== 4) {
            return false;
        }
        
        $lastOctet = (int)$parts[3];
        
        // Obtener rangos de configuración o usar valores por defecto
        $rangeStart = isset($config['adminRangeStart']) ? (int)$config['adminRangeStart'] : 1;
        $rangeEnd = isset($config['adminRangeEnd']) ? (int)$config['adminRangeEnd'] : 30;
        $finalStart = isset($config['adminRangeFinalStart']) ? (int)$config['adminRangeFinalStart'] : 254;
        $finalEnd = isset($config['adminRangeFinalEnd']) ? (int)$config['adminRangeFinalEnd'] : 254;
        
        return ($lastOctet >= $rangeStart && $lastOctet <= $rangeEnd) 
            || ($lastOctet >= $finalStart && $lastOctet <= $finalEnd);
    }

    /**
     * Obtiene el tipo de IP (administrativa o cliente)
     * 
     * @param string $ip Dirección IP completa
     * @param array $config Configuración del plugin con rangos admin
     * @return array ['type' => 'admin'|'client', 'label' => string, 'recommended' => bool]
     */
    public static function getIpType($ip, $config = [])
    {
        if (self::isAdminIp($ip, $config)) {
            return [
                'type' => 'admin',
                'label' => 'Administración',
                'recommended' => false,
                'description' => 'No recomendada para clientes'
            ];
        }
        
        return [
            'type' => 'client',
            'label' => 'Apta para cliente',
            'recommended' => true,
            'description' => 'Recomendada para asignar a clientes'
        ];
    }

    /**
     * Busca IPs disponibles en un segmento de red específico
     * 
     * @param string $segmento El tercer octeto del segmento (ej: "5" para 172.16.5.x)
     * @param bool $verifyPing Si es true, verifica las IPs con ping antes de reportarlas como disponibles
     * @return array Array con 'success', 'data' (IPs disponibles), 'used' (IPs en uso), 'message', 'ping_verified', 'ping_stats'
     */
    /**
     * Busca IPs disponibles en un segmento
     * 
     * @param string $segmento Tercer octeto de la IP (ej: 5 para 172.16.5.x)
     * @param bool $verifyPing Si es true, verifica disponibilidad con ping
     * @param int $pingLimit Límite de IPs a verificar (0 = todas)
     * @return array Resultado de la búsqueda
     */
    public function buscarIpsDisponibles($segmento, $verifyPing = false, $pingLimit = 0)
    {
        try {
            // Validar el segmento
            if (!is_numeric($segmento) || $segmento < 0 || $segmento > 255) {
                return [
                    'success' => false,
                    'message' => 'El segmento debe ser un número entre 0 y 255',
                    'data' => []
                ];
            }

            // Obtener IPs en uso desde la API
            $ipsEnUso = $this->obtenerIpsEnUso();
            
            if ($ipsEnUso === false) {
                return [
                    'success' => false,
                    'message' => 'Error al conectar con la API de UISP',
                    'data' => []
                ];
            }

            // NUEVO: Filtrar IPs de servicios CRM suspendidos/finalizados
            if ($this->logger) {
                $this->logger->appendLog(sprintf(
                    'DEBUG CRM: apitoken=%s, ipserver=%s',
                    !empty($this->config['apitoken']) ? 'SET' : 'EMPTY',
                    !empty($this->config['ipserver']) ? 'SET' : 'EMPTY'
                ));
            }
            
            if (!empty($this->config['apitoken']) && !empty($this->config['ipserver'])) {
                try {
                    // Construir URL de la API de CRM
                    $crmApiUrl = 'https://' . $this->config['ipserver'] . '/crm/api/v1.0';
                    
                    if ($this->logger) {
                        $this->logger->appendLog("CRM: Iniciando filtrado con URL: $crmApiUrl");
                    }
                    
                    $crmService = new \SiipAvailableIps\CrmService(
                        $crmApiUrl,
                        $this->config['apitoken'],
                        $this->logger
                    );
                    
                    $blockedSites = $crmService->getBlockedSiteIds();
                    
                    if (!empty($blockedSites)) {
                        // Obtener datos completos de IPs con siteId
                        $ipsWithSites = $this->obtenerIpsConSitios();
                        
                        if ($ipsWithSites !== false) {
                            // Filtrar IPs que pertenecen a sitios bloqueados
                            $ipsFiltradasPorCrm = [];
                            foreach ($ipsEnUso as $ip) {
                                $siteId = $ipsWithSites[$ip] ?? null;
                                if ($siteId && in_array($siteId, $blockedSites)) {
                                    $ipsFiltradasPorCrm[] = $ip;
                                }
                            }
                            
                            if (!empty($ipsFiltradasPorCrm)) {
                                if ($this->logger) {
                                    $this->logger->appendLog(sprintf(
                                        'CRM: Filtrando %d IPs de sitios con servicios suspendidos/finalizados: %s',
                                        count($ipsFiltradasPorCrm),
                                        implode(', ', array_slice($ipsFiltradasPorCrm, 0, 5)) . (count($ipsFiltradasPorCrm) > 5 ? '...' : '')
                                    ));
                                }
                                
                                // Agregar IPs filtradas a la lista de IPs en uso
                                $ipsEnUso = array_unique(array_merge($ipsEnUso, $ipsFiltradasPorCrm));
                            } else {
                                if ($this->logger) {
                                    $this->logger->appendLog('CRM: No se encontraron IPs en uso que coincidan con sitios bloqueados');
                                }
                            }
                        }
                    }
                } catch (\Exception $e) {
                    if ($this->logger) {
                        $this->logger->appendLog('CRM: Error al filtrar servicios: ' . $e->getMessage());
                    }
                    // Continuar sin filtrado de CRM en caso de error
                }
            }

            // Detectar IPs flotantes (dispositivos no autorizados / Pending Adoption)
            $floatingIps = [];
            try {
                $floatingIps = $this->obtenerIpsFlotantes($segmento);
                if (!empty($floatingIps)) {
                    // Agregar IPs flotantes a la lista de IPs en uso para excluirlas de disponibles
                    $floatingIpAddresses = array_column($floatingIps, 'ip');
                    $ipsEnUso = array_unique(array_merge($ipsEnUso, $floatingIpAddresses));
                    
                    if ($this->logger) {
                        $this->logger->appendLog(sprintf(
                            'Detectadas %d IPs flotantes (dispositivos no autorizados) en segmento %s: %s',
                            count($floatingIps),
                            $segmento,
                            implode(', ', $floatingIpAddresses)
                        ));
                    }
                }
            } catch (\Exception $e) {
                if ($this->logger) {
                    $this->logger->appendLog('Error al detectar IPs flotantes: ' . $e->getMessage());
                }
            }

            // Filtrar IPs del segmento solicitado
            $segmentoPrefix = "172.16.$segmento.";
            $segmentIps = [];
            
            foreach ($ipsEnUso as $ip) {
                if (strpos($ip, $segmentoPrefix) === 0) {
                    $segmentIps[] = $ip;
                }
            }

            // Ordenar IPs del segmento
            usort($segmentIps, function($a, $b) {
                return intval(explode('.', $a)[3]) - intval(explode('.', $b)[3]);
            });

            // Generar todas las IPs posibles del segmento (1-254)
            $todasLasIps = [];
            for ($i = 1; $i <= 254; $i++) {
                $todasLasIps[] = "172.16.$segmento.$i";
            }

            // Calcular IPs disponibles
            $ipsDisponibles = array_values(array_diff($todasLasIps, $segmentIps));

            // NOTA: La validación por búsqueda de sitios se hace de forma progresiva en el frontend
            // No se puede hacer en lote aquí porque causa timeout
            // El frontend llamará al endpoint API para validar IPs individuales

            // Verificar con ping si está habilitado
            $pingVerified = false;
            $pingStats = null;
            $pingResponding = [];
            
            if ($verifyPing) {
                if ($this->logger) {
                    $this->logger->appendLog('Verificación por ping habilitada, iniciando verificación...');
                }
                
                // Crear instancia de PingService
                $pingService = new PingService($this->logger, 1, 30);
                
                // Verificar si ping está disponible
                if (!$pingService->isAvailable()) {
                    if ($this->logger) {
                        $this->logger->appendLog('ADVERTENCIA: Comando ping no disponible, se omitirá verificación');
                    }
                } else {
                    // Preparar IPs para ping
                    $ipsToPing = $ipsDisponibles;
                    
                    // Si hay límite, filtrar IPs administrativas y limitar IPs de cliente
                    if ($pingLimit > 0) {
                        $adminIps = [];
                        $clientIps = [];
                        
                        foreach ($ipsDisponibles as $ip) {
                            // Usar función dinámica para verificar si es administrativa
                            if (self::isAdminIp($ip, $this->config)) {
                                $adminIps[] = $ip;
                            } else {
                                $clientIps[] = $ip;
                            }
                        }
                        
                        // Tomar solo las primeras N IPs de cliente
                        $clientIpsToPing = array_slice($clientIps, 0, $pingLimit);
                        
                        // IPs a verificar = IPs de cliente limitadas
                        // (Las administrativas NO se verifican con ping en este modo para ahorrar tiempo)
                        $ipsToPing = $clientIpsToPing;
                        
                        if ($this->logger) {
                            $this->logger->appendLog(sprintf(
                                'Aplicando límite de ping: %d IPs de cliente (Total disponibles: %d)', 
                                count($ipsToPing), 
                                count($clientIps)
                            ));
                        }
                    }

                    // Hacer ping a las IPs seleccionadas
                    $startTime = microtime(true);
                    $pingResults = $pingService->pingMultipleIps($ipsToPing);
                    $executionTime = microtime(true) - $startTime;
                    
                    // Procesar resultados
                    $processed = $pingService->processPingResults($pingResults);
                    $pingResponding = $processed['responding'];
                    
                    // Filtrar IPs que responden (están en uso pero no registradas en UISP)
                    if (!empty($pingResponding)) {
                        if ($this->logger) {
                            $this->logger->appendLog(sprintf(
                                'ADVERTENCIA: %d IPs responden a ping pero no están en UISP: %s',
                                count($pingResponding),
                                implode(', ', $pingResponding)
                            ));
                        }
                        
                        // Filtrar IPs que responden
                        if ($pingLimit > 0) {
                            // Si hay límite, reconstruimos la lista de disponibles:
                            // Admin IPs (sin verificar) + Client IPs verificadas (que no respondieron)
                            // Las Client IPs que excedieron el límite NO se incluyen en el resultado
                            $clientIpsVerified = array_diff($clientIpsToPing, $pingResponding);
                            $ipsDisponibles = array_merge($adminIps, $clientIpsVerified);
                            
                            // Reordenar para mantener consistencia
                            usort($ipsDisponibles, function($a, $b) {
                                return intval(explode('.', $a)[3]) - intval(explode('.', $b)[3]);
                            });
                        } else {
                            // Comportamiento normal: filtrar de la lista completa
                            $ipsDisponibles = array_values(array_diff($ipsDisponibles, $pingResponding));
                        }
                    }
                    
                    $pingVerified = true;
                    
                    // Calcular estadísticas
                    $totalChecked = count($ipsToPing);
                    $respondingCount = count($pingResponding);
                    $notRespondingCount = $totalChecked - $respondingCount;
                    
                    $pingStats = [
                        'total_checked' => $totalChecked,
                        'responding' => $respondingCount,
                        'not_responding' => $notRespondingCount,
                        'execution_time' => number_format($executionTime, 2) . 's',
                        'limit_applied' => $pingLimit > 0 ? $pingLimit : false
                    ];
                    
                    if ($this->logger) {
                        $this->logger->appendLog(sprintf(
                            'Verificación por ping completada: %d IPs verificadas, %d responden, %d no responden (%.2fs)',
                            $pingStats['total_checked'],
                            $pingStats['responding'],
                            $pingStats['not_responding'],
                            $executionTime
                        ));
                    }
                }
            }

            if ($this->logger) {
                $this->logger->appendLog(
                    sprintf(
                        'Búsqueda de IPs en segmento 172.16.%s.x - Disponibles: %d, En uso: %d%s',
                        $segmento,
                        count($ipsDisponibles),
                        count($segmentIps),
                        $pingVerified ? ' (verificado con ping)' : ''
                    )
                );
            }

            $result = [
                'success' => true,
                'message' => sprintf(
                    'Se encontraron %d IPs disponibles en el segmento 172.16.%s.x',
                    count($ipsDisponibles),
                    $segmento
                ),
                'data' => $ipsDisponibles,
                'used' => $segmentIps,
                'floating' => $floatingIps,
                'segment' => "172.16.$segmento.x",
                'ping_verified' => $pingVerified
            ];
            
            // Agregar información de ping si se verificó
            if ($pingVerified && $pingStats) {
                $result['ping_stats'] = $pingStats;
                $result['ping_responding'] = $pingResponding;
            }
            
            return $result;

        } catch (\Exception $e) {
            if ($this->logger) {
                $this->logger->appendLog('Error en búsqueda de IPs: ' . $e->getMessage());
            }
            
            return [
                'success' => false,
                'message' => 'Error interno: ' . $e->getMessage(),
                'data' => []
            ];
        }
    }

    /**
     * Obtiene todas las IPs en uso desde la API de UISP
     * 
     * @return array|false Array de IPs o false en caso de error
     */
    private function obtenerIpsEnUso()
    {
        if ($this->logger) {
            $this->logger->appendLog('Iniciando conexión a API: ' . $this->apiUrl);
        }
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'accept: application/json',
            "x-auth-token: {$this->apiToken}"
        ]);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Para entornos de desarrollo
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        
        $result = curl_exec($ch);
        $curlError = curl_error($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        if ($result === false) {
            if ($this->logger) {
                $this->logger->appendLog('Error cURL: ' . $curlError);
                $this->logger->appendLog('Código HTTP: ' . $httpCode);
            }
            curl_close($ch);
            return false;
        }
        
        curl_close($ch);
        
        if ($this->logger) {
            $this->logger->appendLog("Respuesta HTTP: $httpCode");
            $this->logger->appendLog('Longitud de respuesta: ' . strlen($result) . ' bytes');
        }
        
        if ($httpCode !== 200) {
            if ($this->logger) {
                $this->logger->appendLog("Error HTTP $httpCode. Respuesta: " . substr($result, 0, 500));
            }
            return false;
        }
        
        $ipsEnUso = json_decode($result, true);
        
        if (!is_array($ipsEnUso)) {
            if ($this->logger) {
                $this->logger->appendLog('Error: La respuesta no es un array válido. Respuesta: ' . substr($result, 0, 500));
            }
            return false;
        }
        
        if ($this->logger) {
            $this->logger->appendLog('IPs obtenidas exitosamente: ' . count($ipsEnUso) . ' direcciones');
        }
        
        return $ipsEnUso;
    }

    /**
     * Obtiene mapeo de IPs a Site IDs desde la API de UISP
     * 
     * @return array|false Array asociativo [ip => siteId] o false en caso de error
     */
    private function obtenerIpsConSitios()
    {
        // La API /devices/ips devuelve solo strings de IPs
        // Necesitamos consultar /devices para obtener la relación IP => siteId
        
        // Extraer la URL base sin parámetros
        $urlParts = parse_url($this->apiUrl);
        $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'];
        $devicesUrl = $baseUrl . '/nms/api/v2.1/devices';
        
        if ($this->logger) {
            $this->logger->appendLog('Obteniendo dispositivos con siteId: ' . $devicesUrl);
        }
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $devicesUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'accept: application/json',
            "x-auth-token: {$this->apiToken}"
        ]);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        
        $result = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode !== 200 || $result === false) {
            if ($this->logger) {
                $this->logger->appendLog("Error al obtener dispositivos: HTTP $httpCode");
            }
            return false;
        }
        
        $devices = json_decode($result, true);
        if (!is_array($devices)) {
            return false;
        }
        
        // Crear mapeo IP => siteId
        $ipToSite = [];
        foreach ($devices as $device) {
            $siteId = $device['identification']['site']['id'] ?? null;
            
            if (!$siteId) {
                continue;
            }
            
            // Obtener IP principal del dispositivo
            $primaryIp = $device['ipAddress'] ?? null;
            if ($primaryIp) {
                // Remover la máscara de red si existe (ej: 172.16.55.73/24 -> 172.16.55.73)
                $cleanIp = explode('/', $primaryIp)[0];
                $ipToSite[$cleanIp] = $siteId;
            }
            
            // Obtener TODAS las IPs del dispositivo desde ipAddressList
            $ipList = $device['ipAddressList'] ?? [];
            if (is_array($ipList)) {
                foreach ($ipList as $ip) {
                    if ($ip && filter_var($ip, FILTER_VALIDATE_IP)) {
                        $ipToSite[$ip] = $siteId;
                    }
                }
            }
        }
        
        if ($this->logger) {
            $this->logger->appendLog(sprintf(
                'Mapeadas %d IPs a sitios',
                count($ipToSite)
            ));
        }
        
        return $ipToSite;
    }

    /**
     * Obtiene IPs de dispositivos no autorizados (Pending Adoption) en un segmento
     * Estos dispositivos no aparecen en /devices/ips pero sí en /devices
     * 
     * @param string $segmento Tercer octeto del segmento
     * @return array Array de dispositivos flotantes [{ip, name, model, status}]
     */
    private function obtenerIpsFlotantes($segmento)
    {
        $urlParts = parse_url($this->apiUrl);
        $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'];
        $devicesUrl = $baseUrl . '/nms/api/v2.1/devices?authorized=false';
        
        if ($this->logger) {
            $this->logger->appendLog('Buscando dispositivos no autorizados (Pending Adoption)...');
        }
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $devicesUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'accept: application/json',
            "x-auth-token: {$this->apiToken}"
        ]);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        
        $result = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        // Si la API de authorized=false no devuelve resultados útiles,
        // intentar con /devices completo y filtrar
        $devices = [];
        
        if ($httpCode === 200 && $result !== false) {
            $parsed = json_decode($result, true);
            if (is_array($parsed) && !empty($parsed)) {
                $devices = $parsed;
            }
        }
        
        // Si no encontramos dispositivos no autorizados con el filtro,
        // consultar todos los dispositivos y filtrar manualmente
        if (empty($devices)) {
            $allDevicesUrl = $baseUrl . '/nms/api/v2.1/devices';
            
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $allDevicesUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'accept: application/json',
                "x-auth-token: {$this->apiToken}"
            ]);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, 60);
            
            $result = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode !== 200 || $result === false) {
                if ($this->logger) {
                    $this->logger->appendLog("Error al obtener dispositivos: HTTP $httpCode");
                }
                return [];
            }
            
            $allDevices = json_decode($result, true);
            if (!is_array($allDevices)) {
                return [];
            }
            
            // Filtrar solo dispositivos no autorizados
            $devices = array_filter($allDevices, function($device) {
                return isset($device['identification']['authorized']) 
                    && $device['identification']['authorized'] === false;
            });
        }
        
        // Filtrar por segmento y construir resultado
        $segmentoPrefix = "172.16.$segmento.";
        $floating = [];
        
        foreach ($devices as $device) {
            $primaryIp = $device['ipAddress'] ?? '';
            $cleanIp = explode('/', $primaryIp)[0];
            
            if (strpos($cleanIp, $segmentoPrefix) === 0) {
                $floating[] = [
                    'ip' => $cleanIp,
                    'name' => $device['identification']['name'] ?? 'Desconocido',
                    'model' => $device['identification']['model'] ?? 'N/A',
                    'status' => $device['overview']['status'] ?? 'unknown',
                ];
                continue;
            }
            
            // Buscar en ipAddressList
            foreach ($device['ipAddressList'] ?? [] as $listIp) {
                if (strpos($listIp, $segmentoPrefix) === 0) {
                    $floating[] = [
                        'ip' => $listIp,
                        'name' => $device['identification']['name'] ?? 'Desconocido',
                        'model' => $device['identification']['model'] ?? 'N/A',
                        'status' => $device['overview']['status'] ?? 'unknown',
                    ];
                }
            }
        }
        
        if ($this->logger) {
            $this->logger->appendLog(sprintf(
                'Dispositivos no autorizados en segmento %s: %d encontrados',
                $segmento,
                count($floating)
            ));
        }
        
        return $floating;
    }
}
