"""Sondeo de disponibilidad ICMP de un host via el binario `ping` (Linux).

Funcion IMPURA: ejecuta `ping -c <count> -w <timeout_s> <host>` como subprocess
y parsea la salida (resumen de paquetes y linea rtt). Un host inalcanzable o
con ICMP filtrado NO es error: se reporta `status:"ok"` con `loss_pct=100` y
rtt None. Solo es error si el binario falla, el host esta vacio o hay timeout
duro del subprocess. El campo `raw` siempre esta presente.
"""

import re
import subprocess

_LOSS_RE = re.compile(r"([\d.]+)%\s+packet\s+loss")
_TX_RX_RE = re.compile(r"(\d+)\s+packets\s+transmitted,\s+(\d+)\s+(?:packets\s+)?received")
_RTT_RE = re.compile(
    r"(?:rtt|round-trip)\s+min/avg/max(?:/mdev)?\s*=\s*"
    r"([\d.]+)/([\d.]+)/([\d.]+)"
)


def ping_host(host: str, count: int = 4, timeout_s: int = 30) -> dict:
    """Hace ping ICMP a un host y parsea el resumen de paquetes y latencia.

    Args:
        host: Hostname o IP a sondear (ej. ``"1.1.1.1"`` o ``"google.com"``).
        count: Numero de echo requests a enviar (`ping -c`).
        timeout_s: Deadline total del comando ping (`ping -w`) y a la vez el
            timeout duro del subprocess (este ultimo con +5s de margen).

    Returns:
        Dict de estado. En exito (incluido host inalcanzable)::

            {
                "status": "ok",
                "host": <host>,
                "packets_sent": <int>,
                "packets_recv": <int>,
                "loss_pct": <float>,
                "rtt_avg_ms": <float|None>,
                "rtt_min_ms": <float|None>,
                "rtt_max_ms": <float|None>,
                "raw": <stdout>,
            }

        En fallo (binario ausente, host vacio, timeout duro)::

            {"status": "error", "error": <str>, "host": <host>, "raw": <stdout|"">}
    """
    if not host or not host.strip():
        return {"status": "error", "error": "ping_host: host vacio", "host": host, "raw": ""}

    host = host.strip()
    hard_timeout = float(timeout_s) + 5.0

    try:
        proc = subprocess.run(
            ["ping", "-c", str(count), "-w", str(timeout_s), host],
            capture_output=True,
            text=True,
            timeout=hard_timeout,
        )
    except FileNotFoundError:
        return {
            "status": "error",
            "error": "ping_host: binario `ping` no encontrado en PATH (paquete iputils-ping)",
            "host": host,
            "raw": "",
        }
    except subprocess.TimeoutExpired as exc:
        partial = exc.stdout or ""
        if isinstance(partial, bytes):
            partial = partial.decode(errors="replace")
        return {
            "status": "error",
            "error": f"ping_host: timeout duro del subprocess tras {hard_timeout}s",
            "host": host,
            "raw": partial,
        }

    raw = proc.stdout or ""

    packets_sent: int | None = None
    packets_recv: int | None = None
    loss_pct: float = 100.0
    rtt_min = rtt_avg = rtt_max = None

    m_txrx = _TX_RX_RE.search(raw)
    if m_txrx:
        packets_sent = int(m_txrx.group(1))
        packets_recv = int(m_txrx.group(2))

    m_loss = _LOSS_RE.search(raw)
    if m_loss:
        loss_pct = float(m_loss.group(1))

    m_rtt = _RTT_RE.search(raw)
    if m_rtt:
        rtt_min = float(m_rtt.group(1))
        rtt_avg = float(m_rtt.group(2))
        rtt_max = float(m_rtt.group(3))

    return {
        "status": "ok",
        "host": host,
        "packets_sent": packets_sent,
        "packets_recv": packets_recv,
        "loss_pct": loss_pct,
        "rtt_avg_ms": rtt_avg,
        "rtt_min_ms": rtt_min,
        "rtt_max_ms": rtt_max,
        "raw": raw,
    }


if __name__ == "__main__":
    try:
        result = ping_host("1.1.1.1", count=3, timeout_s=10)
        print(result["status"])
        if result["status"] == "ok":
            print(
                f"loss={result['loss_pct']}% "
                f"recv={result['packets_recv']}/{result['packets_sent']} "
                f"avg={result['rtt_avg_ms']}ms"
            )
            print("--- raw ---")
            print(result["raw"])
        else:
            print("error:", result.get("error"))
    except Exception as exc:  # smoke: tolera cualquier fallo de red sin romper
        print("smoke fallo (tolerado):", exc)
