"""Ping y estadísticas de latencia/pérdida (Windows)."""
from __future__ import annotations

import re
import subprocess
from dataclasses import dataclass
from typing import Optional, Tuple


@dataclass
class PingResult:
    ok: bool
    rtt_ms: Optional[float] = None
    sent: int = 1
    recv: int = 0
    error: str = ""


def ping_host(ip: str, timeout_ms: int = 800, count: int = 1) -> PingResult:
    """Ping ICMP Windows. timeout_ms por eco."""
    timeout_s = max(1, int(round(timeout_ms / 1000.0)))
    cmd = ["ping", "-n", str(count), "-w", str(timeout_ms), ip]
    try:
        p = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_s * count + 3,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
            encoding="utf-8",
            errors="replace",
        )
        out = (p.stdout or "") + (p.stderr or "")
    except subprocess.TimeoutExpired:
        return PingResult(ok=False, sent=count, recv=0, error="timeout")
    except Exception as e:
        return PingResult(ok=False, sent=count, recv=0, error=str(e))

    # Español: "Tiempo=12ms" / "tiempo=12ms" / "time=12ms"
    rtts = []
    for m in re.finditer(r"(?:[Tt]iempo|[Tt]ime)\s*[=<]\s*(\d+)\s*ms", out, re.I):
        rtts.append(float(m.group(1)))
    # "Respuesta desde ...: bytes=32 tiempo=1ms"
    if not rtts:
        for m in re.finditer(r"tiempo[=<](\d+)ms", out, re.I):
            rtts.append(float(m.group(1)))

    lost_m = re.search(r"[Pp]erdidos?\s*=\s*(\d+)", out)
    recv_m = re.search(r"[Rr]ecibidos?\s*=\s*(\d+)", out)
    sent_m = re.search(r"[Ee]nviados?\s*=\s*(\d+)", out)
    # English fallback
    if not sent_m:
        sent_m = re.search(r"Sent\s*=\s*(\d+)", out, re.I)
        recv_m = re.search(r"Received\s*=\s*(\d+)", out, re.I)
        lost_m = re.search(r"Lost\s*=\s*(\d+)", out, re.I)

    sent = int(sent_m.group(1)) if sent_m else count
    recv = int(recv_m.group(1)) if recv_m else len(rtts)
    if lost_m and not recv_m:
        recv = max(0, sent - int(lost_m.group(1)))

    ok = recv > 0 and bool(rtts)
    rtt = sum(rtts) / len(rtts) if rtts else None
    return PingResult(ok=ok, rtt_ms=rtt, sent=sent, recv=recv)


def update_latency_stats(
    prev_avg: Optional[float],
    prev_min: Optional[float],
    prev_max: Optional[float],
    prev_jitter: Optional[float],
    last_rtt: Optional[float],
    new_rtt: Optional[float],
    n_recv: int,
) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]:
    if new_rtt is None:
        return prev_avg, prev_min, prev_max, prev_jitter
    rmin = new_rtt if prev_min is None else min(prev_min, new_rtt)
    rmax = new_rtt if prev_max is None else max(prev_max, new_rtt)
    if prev_avg is None or n_recv <= 1:
        avg = new_rtt
    else:
        avg = prev_avg + (new_rtt - prev_avg) / n_recv
    jitter = prev_jitter
    if last_rtt is not None:
        delta = abs(new_rtt - last_rtt)
        jitter = delta if prev_jitter is None else (prev_jitter * 0.7 + delta * 0.3)
    return avg, rmin, rmax, jitter
