"""
Descubrimiento de red local para el Agent CLI.

Usa subprocess con `arp` y `ping` para descubrir hosts en la red local.
NO usa scapy para evitar dependencias pesadas.
Soporta macOS, Linux y Windows.
"""
import platform
import re
import subprocess
from typing import Any

from jama_agent.logger import log

SYSTEM = platform.system().lower()


def _parse_arp_unix(output: str) -> list[dict[str, str]]:
    """Parsea la salida de `arp -an` en macOS/Linux.

    Formato: ? (IP) at MAC on interface
    """
    hosts: list[dict[str, str]] = []
    pattern = re.compile(
        r"(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:.-]+)"
    )
    for line in output.splitlines():
        match = pattern.search(line)
        if match:
            hostname = match.group(1)
            ip = match.group(2)
            mac = match.group(3)
            if mac.lower() not in ("(incomplete)", "ff:ff:ff:ff:ff:ff"):
                hosts.append({
                    "ip": ip,
                    "mac": mac,
                    "hostname": hostname if hostname != "?" else "",
                })
    return hosts


def _parse_arp_windows(output: str) -> list[dict[str, str]]:
    """Parsea la salida de `arp -a` en Windows.

    Formato:
      Interface: 192.168.1.100 --- 0x5
        Internet Address      Physical Address      Type
        192.168.1.1           84-d8-1b-23-f5-3c     dynamic
    """
    hosts: list[dict[str, str]] = []
    pattern = re.compile(
        r"(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F]{2}(?:-[0-9a-fA-F]{2}){5})\s+(\w+)"
    )
    for line in output.splitlines():
        match = pattern.search(line)
        if match:
            ip = match.group(1)
            mac = match.group(2).replace("-", ":")
            entry_type = match.group(3).lower()
            if entry_type != "static" and mac.lower() != "ff:ff:ff:ff:ff:ff":
                hosts.append({
                    "ip": ip,
                    "mac": mac,
                    "hostname": "",
                })
    return hosts


def _parse_arp_output(output: str) -> list[dict[str, str]]:
    """Parsea la salida de ARP según el SO."""
    if SYSTEM == "windows":
        return _parse_arp_windows(output)
    return _parse_arp_unix(output)


def _get_arp_command() -> list[str]:
    """Retorna el comando ARP apropiado para el SO."""
    if SYSTEM == "windows":
        return ["arp", "-a"]
    # macOS/Linux: -an evita DNS reverse lookup (mucho más rápido)
    return ["arp", "-an"]


def _ping_sweep(subnet: str, count: int = 1, timeout: int = 1) -> list[str]:
    """Hace ping sweep de una subnet /24 y retorna IPs que responden.

    Args:
        subnet: Prefijo de la subnet (ej: "192.168.1")
        count: Número de pings por host
        timeout: Timeout por ping en segundos

    Returns:
        Lista de IPs que respondieron al ping
    """
    alive: list[str] = []

    for i in range(1, 255):
        ip = f"{subnet}.{i}"
        try:
            if SYSTEM == "windows":
                cmd = ["ping", "-n", str(count), "-w", str(timeout * 1000), ip]
            else:
                cmd = ["ping", "-c", str(count), "-W", str(timeout), ip]

            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=timeout + 2,
            )
            if result.returncode == 0:
                alive.append(ip)
        except (subprocess.TimeoutExpired, FileNotFoundError):
            continue

    return alive


def _get_local_subnet() -> str | None:
    """Detecta la subnet local del host.

    Intenta obtener la IP local y derivar el prefijo /24.
    """
    import socket

    try:
        # Truco para obtener la IP local sin enviar tráfico real
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            s.connect(("8.8.8.8", 80))
            local_ip = s.getsockname()[0]
        # Extraer prefijo /24
        parts = local_ip.split(".")
        if len(parts) == 4:
            return ".".join(parts[:3])
    except Exception:
        pass
    return None


def discover_network(
    subnet: str | None = None,
    use_ping: bool = False,
) -> dict[str, Any]:
    """Descubre hosts en la red local.

    Primero ejecuta `arp` para obtener hosts conocidos.
    Opcionalmente hace ping sweep si se solicita (más lento).

    Args:
        subnet: Prefijo /24 (ej: "192.168.1"). Si es None, auto-detecta.
        use_ping: Si True, hace ping sweep completo (lento pero exhaustivo).

    Returns:
        dict con "hosts" (lista), "subnet", "method", "total", "os"
    """
    log.info("Iniciando descubrimiento de red...")

    hosts: list[dict[str, str]] = []
    method = "arp"

    # 1. ARP table — rápido, hosts ya conocidos
    try:
        arp_cmd = _get_arp_command()
        result = subprocess.run(
            arp_cmd,
            capture_output=True,
            text=True,
            timeout=10,
        )
        if result.returncode == 0:
            hosts = _parse_arp_output(result.stdout)
            log.info(f"ARP: {len(hosts)} hosts encontrados")
    except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
        log.warning(f"ARP no disponible: {exc}")

    # 2. Ping sweep opcional — exhaustivo pero lento
    if use_ping:
        detected_subnet = subnet or _get_local_subnet()
        if detected_subnet:
            log.info(f"Ping sweep en {detected_subnet}.0/24...")
            alive_ips = _ping_sweep(detected_subnet, count=1, timeout=1)
            method = "arp+ping"

            # Agregar IPs que no están ya en la lista ARP
            known_ips = {h["ip"] for h in hosts}
            for ip in alive_ips:
                if ip not in known_ips:
                    hosts.append({"ip": ip, "mac": "", "hostname": ""})

            log.info(f"Ping sweep: {len(alive_ips)} hosts responden")
        else:
            log.warning("No se pudo detectar la subnet local")

    detected_subnet = subnet or _get_local_subnet() or "unknown"

    return {
        "hosts": hosts,
        "subnet": detected_subnet,
        "method": method,
        "total": len(hosts),
        "os": SYSTEM,
    }
