import psutil
import platform
import socket
import json
import subprocess
import time
import requests
from datetime import datetime, timedelta
import os
import re
import threading
import sys
from ping3 import ping

class SystemReporter:
    def __init__(self):
        self.sistema = platform.system()
        self.computer_name = socket.gethostname()
        self.config = self.cargar_configuracion()
        self.configuracion_remota = None
        self.ultima_actualizacion_config = None
        self.ejecutando = True
       
        # Obtener configuración inicial del servidor
        self.obtener_configuracion_remota()
       
    def cargar_configuracion(self):
        """Carga la configuración desde config.json - FALLA si no existe"""
        config_path = os.path.join(os.path.dirname(__file__), 'config.json')
       
        if not os.path.exists(config_path):
            print("ERROR CRITICO: Archivo config.json no encontrado")
            print(f"Ruta esperada: {config_path}")
            sys.exit(1)
           
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
        except json.JSONDecodeError as e:
            print(f"ERROR CRITICO: config.json tiene formato JSON inválido: {e}")
            sys.exit(1)
        except Exception as e:
            print(f"ERROR CRITICO: No se puede leer config.json: {e}")
            sys.exit(1)
           
        # Validar campos requeridos básicos
        campos_requeridos = ['baseUrl', 'dispositivoId']
        campos_faltantes = [campo for campo in campos_requeridos if campo not in config]
       
        if campos_faltantes:
            print(f"ERROR CRITICO: Campos faltantes en config.json: {campos_faltantes}")
            sys.exit(1)
           
        return config
   
    def obtener_configuracion_remota(self):
        """Obtiene la configuración del servidor remoto"""
        max_intentos = 3
       
        for intento in range(max_intentos):
            try:
                url = f"{self.config['baseUrl']}/v1/configuracion/{self.config['dispositivoId']}"
               
                response = requests.get(url, timeout=10)
                response.raise_for_status()
               
                data = response.json()
               
                if data.get('response') == 'SUCCESS' and 'data' in data:
                    self.configuracion_remota = data['data']
                    self.ultima_actualizacion_config = datetime.now()
                   
                    # Actualizar config.json con el nuevo intervalo
                    self.config['intervalMinutos'] = self.configuracion_remota['conexionMinutos']
                    self.guardar_configuracion_local()
                   
                    print(f"Configuración remota obtenida exitosamente")
                    print(f"Intervalo actualizado: {self.configuracion_remota['conexionMinutos']} minutos")
                    print(f"Servicios configurados: {len(self.configuracion_remota.get('servicios', []))}")
                    return True
                else:
                    print(f"Respuesta inesperada del servidor: {data}")
                   
            except requests.exceptions.Timeout:
                print(f"Timeout obteniendo configuración - intento {intento + 1}/{max_intentos}")
               
            except requests.exceptions.ConnectionError:
                print(f"Error de conexión obteniendo configuración - intento {intento + 1}/{max_intentos}")
               
            except requests.exceptions.RequestException as e:
                print(f"Error HTTP obteniendo configuración: {e}")
               
            except Exception as e:
                print(f"Error inesperado obteniendo configuración: {e}")
           
            if intento < max_intentos - 1:
                time.sleep(2 ** intento)  # Backoff exponencial
       
        print("ERROR: No se pudo obtener la configuración remota")
        if self.configuracion_remota is None:
            # Si es la primera vez y no hay configuración, usar valores por defecto
            print("Usando configuración local por defecto")
            self.configuracion_remota = {
                'conexionMinutos': self.config.get('intervalMinutos', 5),
                'servicios': []
            }
           
        return False
   
    def guardar_configuracion_local(self):
        """Guarda la configuración actualizada en config.json"""
        try:
            config_path = os.path.join(os.path.dirname(__file__), 'config.json')
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=4, ensure_ascii=False)
        except Exception as e:
            print(f"Error guardando configuración local: {e}")
   
    def verificar_actualizacion_configuracion(self):
        """Verifica si hay actualizaciones en la configuración"""
        config_anterior = self.configuracion_remota.copy() if self.configuracion_remota else None
       
        if self.obtener_configuracion_remota():
            # Verificar si cambió el intervalo
            if (config_anterior and
                config_anterior.get('conexionMinutos') != self.configuracion_remota.get('conexionMinutos')):
                print(f"Intervalo actualizado de {config_anterior.get('conexionMinutos')} a {self.configuracion_remota.get('conexionMinutos')} minutos")
                return True
               
            # Verificar si cambiaron los servicios
            if (config_anterior and
                config_anterior.get('servicios') != self.configuracion_remota.get('servicios')):
                print("Servicios de monitoreo actualizados")
                return True
       
        return False
   
    def obtener_marca_computadora(self):
        """Obtiene la marca de la computadora según el sistema operativo"""
        try:
            if self.sistema == "Windows":
                # Opción 1: Usar PowerShell (disponible en todos los Windows modernos)
                try:
                    resultado = subprocess.run(
                        ['powershell', '-Command', 'Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty Manufacturer'],
                        capture_output=True,
                        text=True,
                        timeout=10
                    )
                    
                    if resultado.returncode == 0:
                        manufacturer = resultado.stdout.strip()
                        if manufacturer and manufacturer != "To be filled by O.E.M.":
                            return manufacturer
                except:
                    pass
                
                # Opción 2: Usar registro de Windows con Python
                try:
                    import winreg
                    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 
                                        r"HARDWARE\DESCRIPTION\System\BIOS")
                    manufacturer, _ = winreg.QueryValueEx(key, "SystemManufacturer")
                    winreg.CloseKey(key)
                    if manufacturer and manufacturer != "To be filled by O.E.M.":
                        return manufacturer
                except:
                    pass
            
            elif self.sistema == "Linux":
                # Intentar con dmidecode (sin sudo primero)
                try:
                    resultado = subprocess.run(
                        ['dmidecode', '-s', 'system-manufacturer'],
                        capture_output=True,
                        text=True,
                        timeout=10
                    )
                    if resultado.returncode == 0:
                        manufacturer = resultado.stdout.strip()
                        if manufacturer and manufacturer != "To be filled by O.E.M.":
                            return manufacturer
                except:
                    pass
                
                # Alternativa: leer desde /sys/class/dmi/id/
                try:
                    with open('/sys/class/dmi/id/sys_vendor', 'r') as f:
                        manufacturer = f.read().strip()
                        if manufacturer and manufacturer != "To be filled by O.E.M.":
                            return manufacturer
                except:
                    pass
            
            elif self.sistema == "Darwin":  # macOS
                resultado = subprocess.run(
                    ["sysctl", "-n", "hw.model"],
                    capture_output=True,
                    text=True,
                    timeout=5
                )
                if resultado.returncode == 0:
                    return resultado.stdout.strip()
        
        except Exception as e:
            print(f"Error obteniendo marca: {e}")
        
        return "Desconocido"
   
    def obtener_version_sistema_operativo(self):
        """Obtiene la versión detallada del sistema operativo.
        En Windows: Build.UBR (ej: 26200.7462)
        En Linux: Versión del kernel o distribución
        """
        try:
            if self.sistema == "Windows":
                # Obtener Build Number y UBR desde el registro de Windows
                try:
                    import winreg
                    key = winreg.OpenKey(
                        winreg.HKEY_LOCAL_MACHINE,
                        r"SOFTWARE\Microsoft\Windows NT\CurrentVersion"
                    )

                    # Obtener CurrentBuild (ej: 26200)
                    current_build, _ = winreg.QueryValueEx(key, "CurrentBuild")

                    # Obtener UBR - Update Build Revision (ej: 7462)
                    try:
                        ubr, _ = winreg.QueryValueEx(key, "UBR")
                        version_completa = f"{current_build}.{ubr}"
                    except WindowsError:
                        version_completa = str(current_build)

                    winreg.CloseKey(key)
                    return version_completa
                except Exception as e:
                    print(f"Error leyendo registro de Windows: {e}")

                # Fallback: usar PowerShell
                try:
                    resultado = subprocess.run(
                        ['powershell', '-Command',
                         '(Get-ItemProperty "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion").CurrentBuild + "." + (Get-ItemProperty "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion").UBR'],
                        capture_output=True,
                        text=True,
                        timeout=10
                    )
                    if resultado.returncode == 0 and resultado.stdout.strip():
                        return resultado.stdout.strip()
                except Exception:
                    pass

                # Último fallback: platform.version()
                return platform.version()

            elif self.sistema == "Linux":
                # Intentar obtener versión desde /etc/os-release
                try:
                    with open('/etc/os-release', 'r') as f:
                        for line in f:
                            if line.startswith('VERSION_ID='):
                                version = line.split('=')[1].strip().strip('"')
                                # Complementar con kernel version
                                kernel = platform.release()
                                return f"{version} (kernel {kernel})"
                except FileNotFoundError:
                    pass

                # Fallback: usar uname -r (versión del kernel)
                try:
                    resultado = subprocess.run(
                        ['uname', '-r'],
                        capture_output=True,
                        text=True,
                        timeout=5
                    )
                    if resultado.returncode == 0:
                        return resultado.stdout.strip()
                except Exception:
                    pass

                return platform.release()

            elif self.sistema == "Darwin":  # macOS
                try:
                    resultado = subprocess.run(
                        ['sw_vers', '-productVersion'],
                        capture_output=True,
                        text=True,
                        timeout=5
                    )
                    if resultado.returncode == 0:
                        return resultado.stdout.strip()
                except Exception:
                    pass

                return platform.mac_ver()[0]

        except Exception as e:
            print(f"Error obteniendo versión del SO: {e}")

        return platform.version()

    def actualizar_version_so(self):
        """Actualiza la versión del sistema operativo en el servidor"""
        max_intentos = 3

        version_so = self.obtener_version_sistema_operativo()
        print(f"Actualizando versión del SO: {version_so}")

        for intento in range(max_intentos):
            try:
                url = f"{self.config['baseUrl']}/v1/dispositivo/version-so/{self.config['dispositivoId']}"
                headers = {'Content-Type': 'application/json'}
                body = {"versionSistemaOperativo": version_so}

                response = requests.put(url, json=body, headers=headers, timeout=10)
                response.raise_for_status()

                print(f"Versión del SO actualizada exitosamente - Status: {response.status_code}")
                return True

            except requests.exceptions.Timeout:
                print(f"Timeout actualizando versión SO - intento {intento + 1}/{max_intentos}")

            except requests.exceptions.ConnectionError:
                print(f"Error de conexión actualizando versión SO - intento {intento + 1}/{max_intentos}")

            except requests.exceptions.RequestException as e:
                print(f"Error HTTP actualizando versión SO: {e}")

            except Exception as e:
                print(f"Error inesperado actualizando versión SO: {e}")

            if intento < max_intentos - 1:
                time.sleep(2 ** intento)

        print("ADVERTENCIA: No se pudo actualizar la versión del SO")
        return False

    def obtener_numero_serie(self):
        """Obtiene el número de serie del sistema"""
        try:
            if self.sistema == "Windows":
                # Opción 1: Usar PowerShell (disponible en todos los Windows modernos)
                try:
                    resultado = subprocess.run(
                        ['powershell', '-Command', 'Get-CimInstance -ClassName Win32_BIOS | Select-Object -ExpandProperty SerialNumber'],
                        capture_output=True,
                        text=True,
                        timeout=10
                    )
                    
                    if resultado.returncode == 0:
                        serial = resultado.stdout.strip()
                        if serial and serial not in ["To be filled by O.E.M.", "Default string", "0"]:
                            return serial
                except:
                    pass
                
                # Opción 2: Usar registro de Windows con Python
                try:
                    import winreg
                    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 
                                        r"HARDWARE\DESCRIPTION\System\BIOS")
                    serial, _ = winreg.QueryValueEx(key, "SystemProductName")
                    winreg.CloseKey(key)
                    if serial and serial not in ["To be filled by O.E.M.", "Default string"]:
                        return serial
                except:
                    pass
            
            elif self.sistema == "Linux":
                # Intentar con dmidecode (sin sudo primero)
                try:
                    resultado = subprocess.run(
                        ['dmidecode', '-s', 'system-serial-number'],
                        capture_output=True,
                        text=True,
                        timeout=10
                    )
                    if resultado.returncode == 0:
                        serial = resultado.stdout.strip()
                        if serial and serial not in ["To be filled by O.E.M.", "Default string"]:
                            return serial
                except:
                    pass
                
                # Alternativa: leer desde /sys/class/dmi/id/
                try:
                    with open('/sys/class/dmi/id/product_serial', 'r') as f:
                        serial = f.read().strip()
                        if serial and serial not in ["To be filled by O.E.M.", "Default string"]:
                            return serial
                except:
                    pass
            
            elif self.sistema == "Darwin":  # macOS
                resultado = subprocess.run(
                    ["system_profiler", "SPHardwareDataType"],
                    capture_output=True,
                    text=True,
                    timeout=5
                )
                if resultado.returncode == 0:
                    for line in resultado.stdout.split('\n'):
                        if 'Serial Number' in line:
                            return line.split(':')[1].strip()
        
        except Exception as e:
            print(f"Error obteniendo número de serie: {e}")
        
        return "Desconocido"
   
    def obtener_info_basica(self):
        """Obtiene información básica del sistema"""
        try:
            # Intentar usar cpuinfo para procesador
            try:
                import cpuinfo
                cpu_info = cpuinfo.get_cpu_info()
                procesador_modelo = cpu_info.get('brand_raw', 'Desconocido')
            except ImportError:
                procesador_modelo = platform.processor() or 'Desconocido'
       
            return {
                "soTecnico": f"{platform.system()} {platform.release()}",
                "hostnameTecnico": self.computer_name,
                "marcaTecnico": self.obtener_marca_computadora(),
                "serialTecnico": self.obtener_numero_serie(),
                "procesadorTecnico": procesador_modelo
            }
        except Exception as e:
            print(f"Error obteniendo información básica: {e}")
            return {
                "soTecnico": "Desconocido",
                "hostnameTecnico": self.computer_name,
                "marcaTecnico": "Desconocido",
                "serialTecnico": "Desconocido",
                "procesadorTecnico": "Desconocido"
            }
   
    def actualizar_info_tecnica(self):
        """Actualiza la información técnica del dispositivo en el servidor"""
        max_intentos = 3
        
        print("Actualizando información técnica del dispositivo...")
        info_basica = self.obtener_info_basica()
        
        print(f"  SO: {info_basica['soTecnico']}")
        print(f"  Hostname: {info_basica['hostnameTecnico']}")
        print(f"  Marca: {info_basica['marcaTecnico']}")
        print(f"  Serial: {info_basica['serialTecnico']}")
        print(f"  Procesador: {info_basica['procesadorTecnico']}")
        
        for intento in range(max_intentos):
            try:
                url = f"{self.config['baseUrl']}/v1/dispositivo/tecnico/{self.config['dispositivoId']}"
                headers = {'Content-Type': 'application/json'}
                
                response = requests.put(url, json=info_basica, headers=headers, timeout=10)
                response.raise_for_status()
                
                print(f"Información técnica actualizada exitosamente - Status: {response.status_code}")
                return True
                
            except requests.exceptions.Timeout:
                print(f"Timeout actualizando info técnica - intento {intento + 1}/{max_intentos}")
                
            except requests.exceptions.ConnectionError:
                print(f"Error de conexión actualizando info técnica - intento {intento + 1}/{max_intentos}")
                
            except requests.exceptions.RequestException as e:
                print(f"Error HTTP actualizando info técnica: {e}")
                
            except Exception as e:
                print(f"Error inesperado actualizando info técnica: {e}")
            
            if intento < max_intentos - 1:
                time.sleep(2 ** intento)  # Backoff exponencial
        
        print("ADVERTENCIA: No se pudo actualizar la información técnica")
        return False
   
    def obtener_info_procesador(self):
        """Obtiene información del procesador - optimizado"""
        try:
            return {
                "procesadorNucleos": str(psutil.cpu_count(logical=False) or psutil.cpu_count()),
                "procesadorUsoPorcentaje": str(round(psutil.cpu_percent(interval=0.1), 1))
            }
        except Exception:
            return {"procesadorNucleos": "0", "procesadorUsoPorcentaje": "0.0"}
   
    def obtener_info_ram(self):
        """Obtiene información de RAM - optimizado"""
        try:
            vm = psutil.virtual_memory()
            return {
                "ramCapacidadGb": str(round(vm.total / (1024**3), 4)),
                "ramUsoPorcentaje": str(round(vm.percent, 1))
            }
        except Exception:
            return {"ramCapacidadGb": "0.0", "ramUsoPorcentaje": "0.0"}
   
    def obtener_info_bateria(self):
        """Obtiene información de batería - optimizado"""
        try:
            battery = psutil.sensors_battery()
            if battery:
                return {
                    "bateriaInstalada": True,
                    "bateriaCargaPorcentaje": str(round(battery.percent, 1)),
                    "bateriaConectada": battery.power_plugged
                }
        except Exception:
            pass
       
        return {
            "bateriaInstalada": False,
            "bateriaCargaPorcentaje": "0",
            "bateriaConectada": False
        }
   
    def obtener_estres_disco_rapido(self):
        """Obtiene estrés de disco con muestra rápida"""
        try:
            disk_io_inicial = psutil.disk_io_counters()
            time.sleep(0.5)  # Reducido para ser más rápido
            disk_io_final = psutil.disk_io_counters()
           
            if disk_io_inicial and disk_io_final:
                bytes_read = disk_io_final.read_bytes - disk_io_inicial.read_bytes
                bytes_write = disk_io_final.write_bytes - disk_io_inicial.write_bytes
               
                read_speed_mbs = (bytes_read / 0.5) / (1024 * 1024)
                write_speed_mbs = (bytes_write / 0.5) / (1024 * 1024)
               
                max_speed_typical = 100
                read_stress = min(100, (read_speed_mbs / max_speed_typical) * 100)
                write_stress = min(100, (write_speed_mbs / max_speed_typical) * 100)
               
                return round(read_stress, 1), round(write_stress, 1)
        except Exception:
            pass
       
        return 0.0, 0.0
   
    def obtener_info_discos(self):
        """Obtiene información de discos - optimizado"""
        discos = []
        try:
            partitions = psutil.disk_partitions()
            avg_read, avg_write = self.obtener_estres_disco_rapido()
           
            for partition in partitions:
                try:
                    # Solo incluir particiones principales
                    if partition.fstype in ['', 'squashfs', 'tmpfs']:
                        continue
                       
                    usage = psutil.disk_usage(partition.mountpoint)
                    discos.append({
                        "sistemaArchivos": partition.fstype,
                        "capacidadGb": str(round(usage.total / (1024**3), 4)),
                        "usoPorcentaje": str(round((usage.used / usage.total) * 100, 4)),
                        "lecturaPorcentaje": str(avg_read),
                        "escrituraPorcentaje": str(avg_write)
                    })
                except (PermissionError, OSError):
                    continue
        except Exception:
            pass
       
        return discos
   
    def obtener_velocidad_red_rapida(self, interfaz):
        """Obtiene velocidad de red con medición rápida"""
        try:
            stats_inicial = psutil.net_io_counters(pernic=True)[interfaz]
            time.sleep(0.5)  # Reducido para ser más rápido
            stats_final = psutil.net_io_counters(pernic=True)[interfaz]
           
            bytes_up = stats_final.bytes_sent - stats_inicial.bytes_sent
            bytes_down = stats_final.bytes_recv - stats_inicial.bytes_recv
           
            mbps_up = round((bytes_up * 8) / (1024 * 1024 * 0.5), 4)
            mbps_down = round((bytes_down * 8) / (1024 * 1024 * 0.5), 4)
           
            return mbps_up, mbps_down
        except Exception:
            return 0.0, 0.0
   
    def obtener_ip_interfaz(self, interfaz):
        """Obtiene IP de una interfaz - optimizado"""
        try:
            addrs = psutil.net_if_addrs().get(interfaz, [])
            for addr in addrs:
                if addr.family == socket.AF_INET:
                    if addr.netmask:
                        try:
                            cidr = sum([bin(int(x)).count('1') for x in addr.netmask.split('.')])
                            return f"{addr.address}/{cidr}"
                        except:
                            return addr.address
                    return addr.address
        except Exception:
            pass
        return "N/A"
   
    def obtener_mac_interfaz(self, interfaz):
        """Obtiene MAC de una interfaz - optimizado"""
        try:
            addrs = psutil.net_if_addrs().get(interfaz, [])
            for addr in addrs:
                if addr.family == psutil.AF_LINK:
                    return addr.address
        except Exception:
            pass
        return "N/A"
   
    def obtener_info_redes(self):
        """Obtiene información de interfaces de red - optimizado"""
        interfaces = []
        try:
            net_if_stats = psutil.net_if_stats()
           
            for interfaz, stats in net_if_stats.items():
                # Filtrar interfaces irrelevantes
                if any(skip in interfaz.lower() for skip in [
                    'loopback', 'vmware', 'virtualbox', 'docker', 'vethernet',
                    'teredo', 'isatap', '6to4'
                ]):
                    continue
               
                # Solo incluir interfaces que estén UP o tengan IP válida
                ip = self.obtener_ip_interfaz(interfaz)
                if not stats.isup and ip == "N/A":
                    continue
               
                velocidad_up, velocidad_down = self.obtener_velocidad_red_rapida(interfaz)
               
                interfaces.append({
                    "nombre": interfaz,
                    "mac": self.obtener_mac_interfaz(interfaz),
                    "ip": ip,
                    "estadoRed": stats.isup,
                    "velocidadSubidaMbps": str(velocidad_up),
                    "velocidadBajadaMbps": str(velocidad_down)
                })
        except Exception:
            pass
       
        return interfaces
   
    def hacer_ping_nmap(self, direccion):
        """Realiza ping usando ping3 (librería Python pura) y extrae la latencia real"""
        try:
            # Ejecutar ping con timeout de 10 segundos
            # ping() retorna latencia en segundos (float) o None si falla
            latencia = ping(direccion, timeout=10)

            if latencia is not None:
                # Convertir a string con 3 decimales (ej: "0.043")
                latencia_str = str(round(latencia, 3))
                print(f"Latencia encontrada para {direccion}: {latencia_str}s")
                return latencia_str
            else:
                # Host no responde
                print(f"Host {direccion} no responde")
                return "0.0"

        except PermissionError:
            print(f"Error de permisos al hacer ping a {direccion}. Se requieren permisos de administrador.")
            return "0.0"
        except OSError as e:
            # Error de red o host no alcanzable
            print(f"Error de red al hacer ping a {direccion}: {e}")
            return "0.0"
        except Exception as e:
            print(f"Error en ping para {direccion}: {e}")
            return "0.0"
   
    def verificar_puerto(self, direccion, puerto):
        """Verifica si un puerto está abierto usando socket (TCP connect)"""
        try:
            print(f"    Verificando puerto {puerto} en {direccion}")

            # Crear socket TCP
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2)  # Timeout de 2 segundos

            # Intentar conectar al puerto
            # connect_ex() retorna 0 si la conexión es exitosa, error code si falla
            resultado = sock.connect_ex((direccion, puerto))
            sock.close()

            # Resultado 0 = conexión exitosa (puerto abierto)
            puerto_abierto = (resultado == 0)

            if puerto_abierto:
                print(f"    Puerto {puerto} en {direccion}: open")
            else:
                print(f"    Puerto {puerto} en {direccion}: closed/filtered")

            return puerto_abierto

        except socket.gaierror as e:
            # Error de DNS o resolución de nombre
            print(f"    Error DNS al verificar {direccion}: {e}")
            return False
        except socket.timeout:
            print(f"    Timeout verificando puerto {puerto} en {direccion}")
            return False
        except Exception as e:
            print(f"    Error verificando puerto {puerto} en {direccion}: {e}")
            return False
   
    def obtener_latencia_ping(self):
        """Obtiene latencia de ping y verificación de puertos basado en configuración remota"""
        latencias = []
       
        if not self.configuracion_remota or 'servicios' not in self.configuracion_remota:
            print("No hay servicios configurados para monitorear")
            return latencias
       
        servicios = self.configuracion_remota['servicios']
       
        for servicio in servicios:
            try:
                direccion = servicio.get('direccion', '')
                puerto = servicio.get('puerto', 0)
                tipo_servicio = servicio.get('tipoServicio', True)  # true = ping, false = port scan
                descripcion = servicio.get('descripcion', f'{direccion}:{puerto}')
                servicio_id = servicio.get('servicioId', 0)
               
                if not direccion:
                    continue
               
                latencia_entry = {
                    "nombreDestino": direccion,
                    "respuestaMs": "0.0",
                    "esRevision": True,
                    "nombrePuerto": str(puerto),
                    "respuestaPuerto": False,
                    "servicioId": servicio_id
                }
               
                if tipo_servicio:  # Ping primero, luego puerto opcional
                    latencia_ms = self.hacer_ping_nmap(direccion)
                    latencia_entry["respuestaMs"] = latencia_ms
                   
                    # También verificar el puerto si está especificado
                    if puerto > 0:
                        puerto_abierto = self.verificar_puerto(direccion, puerto)
                        latencia_entry["respuestaPuerto"] = puerto_abierto
                else:  # Puerto primero, pero SIEMPRE hacer ping
                    # Primero verificar el puerto
                    if puerto > 0:
                        puerto_abierto = self.verificar_puerto(direccion, puerto)
                        latencia_entry["respuestaPuerto"] = puerto_abierto
                   
                    # SIEMPRE hacer ping, sin importar el estado del puerto
                    latencia_ms = self.hacer_ping_nmap(direccion)
                    latencia_entry["respuestaMs"] = latencia_ms
               
                latencias.append(latencia_entry)
               
            except Exception as e:
                print(f"Error procesando servicio {servicio}: {e}")
                continue
       
        return latencias
   
    def enviar_datos_api(self, datos):
        """Envía los datos a la API con manejo robusto de errores"""
        max_intentos = 3
       
        for intento in range(max_intentos):
            try:
                url = f"{self.config['baseUrl']}/v1/revision"
                headers = {'Content-Type': 'application/json'}
               
                response = requests.post(url, json=datos, headers=headers, timeout=15)
                response.raise_for_status()
               
                print(f"Datos enviados exitosamente - Status: {response.status_code}")
                return True
               
            except requests.exceptions.Timeout:
                print(f"Timeout en intento {intento + 1}/{max_intentos}")
                if intento < max_intentos - 1:
                    time.sleep(2 ** intento)  # Backoff exponencial
                   
            except requests.exceptions.ConnectionError:
                print(f"Error de conexión en intento {intento + 1}/{max_intentos}")
                if intento < max_intentos - 1:
                    time.sleep(2 ** intento)
                   
            except requests.exceptions.RequestException as e:
                print(f"Error HTTP en intento {intento + 1}/{max_intentos}: {e}")
                if intento < max_intentos - 1:
                    time.sleep(2 ** intento)
                   
            except Exception as e:
                print(f"Error inesperado: {e}")
                break
       
        print("FALLO: No se pudieron enviar los datos después de todos los intentos")
        return False
   
    def recolectar_y_enviar(self):
        """Recolecta datos del sistema y los envía - optimizado"""
        try:
            print(f"Iniciando recolección - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
           
            momento = datetime.now().isoformat()
           
            # Recolección optimizada en paralelo donde sea posible
            info_procesador = self.obtener_info_procesador()
            info_ram = self.obtener_info_ram()
            info_bateria = self.obtener_info_bateria()
           
            # Estas operaciones son más lentas, hacerlas secuencialmente
            info_discos = self.obtener_info_discos()
            info_redes = self.obtener_info_redes()
            latencia_ping = self.obtener_latencia_ping()
           
            datos_api = {
                "dispositivoId": self.config['dispositivoId'],
                "momento": momento,
                "procesadorNucleos": info_procesador["procesadorNucleos"],
                "procesadorUsoPorcentaje": info_procesador["procesadorUsoPorcentaje"],
                "ramCapacidadGb": info_ram["ramCapacidadGb"],
                "ramUsoPorcentaje": info_ram["ramUsoPorcentaje"],
                "bateriaInstalada": info_bateria["bateriaInstalada"],
                "bateriaCargaPorcentaje": info_bateria["bateriaCargaPorcentaje"],
                "bateriaConectada": info_bateria["bateriaConectada"],
                "hostname": self.computer_name,
                "discos": info_discos,
                "interfacesRedes": info_redes,
                "latenciasPing": latencia_ping
            }
           
            print(f"Recolección completada. CPU: {info_procesador['procesadorUsoPorcentaje']}%, RAM: {info_ram['ramUsoPorcentaje']}%")
            print(f"Servicios monitoreados: {len(latencia_ping)}")
           
            # Enviar datos al servidor
            resultado = self.enviar_datos_api(datos_api)
           
            return resultado
           
        except Exception as e:
            print(f"ERROR en recolección: {e}")
            return False
   
    def ejecutar_loop(self):
        """Loop principal del agente"""
        if not self.configuracion_remota:
            print("ERROR: No se pudo obtener configuración inicial. Deteniendo agente.")
            return
       
        intervalo_segundos = self.configuracion_remota['conexionMinutos'] * 60
       
        print(f"AGENTE INICIADO")
        print(f"Dispositivo ID: {self.config['dispositivoId']}")
        print(f"Hostname: {self.computer_name}")
        print(f"Intervalo: {self.configuracion_remota['conexionMinutos']} minutos")
        print(f"API: {self.config['baseUrl']}")
        print(f"Servicios configurados: {len(self.configuracion_remota.get('servicios', []))}")
        print("-" * 60)
        
        # ACTUALIZAR INFORMACIÓN TÉCNICA Y VERSIÓN DEL SO ANTES DEL CICLO
        print("\n" + "=" * 60)
        self.actualizar_info_tecnica()
        self.actualizar_version_so()
        print("=" * 60 + "\n")
       
        # Primera ejecución inmediata
        self.recolectar_y_enviar()
       
        while self.ejecutando:
            try:
                print(f"Esperando {self.configuracion_remota['conexionMinutos']} minutos hasta próxima recolección...")
               
                # Dividir la espera en chunks para poder verificar self.ejecutando y configuración
                tiempo_espera = 0
                intervalo_check = 30  # Verificar configuración cada 30 segundos
               
                while tiempo_espera < intervalo_segundos and self.ejecutando:
                    tiempo_chunk = min(intervalo_check, intervalo_segundos - tiempo_espera)
                    time.sleep(tiempo_chunk)
                    tiempo_espera += tiempo_chunk
                   
                    # Verificar si hay cambios en la configuración cada cierto tiempo
                    if tiempo_espera % (intervalo_check * 2) == 0:  # Cada minuto
                        if self.verificar_actualizacion_configuracion():
                            # Si cambió la configuración, recalcular intervalo
                            nuevo_intervalo = self.configuracion_remota['conexionMinutos'] * 60
                            if nuevo_intervalo != intervalo_segundos:
                                print(f"Intervalo actualizado. Reiniciando ciclo con nuevo tiempo.")
                                intervalo_segundos = nuevo_intervalo
                                break  # Salir del loop de espera para empezar nuevo ciclo
               
                if self.ejecutando:
                    self.recolectar_y_enviar()
                   
            except KeyboardInterrupt:
                print("\nDeteniendo agente...")
                self.ejecutando = False
                break
            except Exception as e:
                print(f"ERROR en loop principal: {e}")
                print("Continuando ejecución...")
                time.sleep(10)  # Espera corta antes de reintentar
   
    def detener(self):
        """Detiene el agente de forma segura"""
        self.ejecutando = False

def main():
    """Función principal - simplificada"""
    try:
        reporter = SystemReporter()
        reporter.ejecutar_loop()
       
    except KeyboardInterrupt:
        print("\nAgente detenido por el usuario")
    except SystemExit:
        pass  # Exit codes ya manejados en cargar_configuracion
    except Exception as e:
        print(f"ERROR CRITICO: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()