#!/usr/bin/env python3
"""
DR Network & DR Wi-Fi Analyzer — Agente (Linux/Windows/macOS) v1.1.0
Coleta Wi-Fi (SSID, RSSI, canal, scan), ping e envia snapshots para o servidor SaaS.
Sem dependências externas (stdlib pura).

Uso (1ª vez):
  python3 drwifi_agent.py --server http://SEU_IP --enroll-code XXXX-XXXX-XX

Uso contínuo (token já salvo):
  python3 drwifi_agent.py --server http://SEU_IP --interval 30

Teste único:
  python3 drwifi_agent.py --server http://SEU_IP --once
"""
import argparse
import gzip
import json
import os
import re
import socket
import subprocess
import sys
import time
import uuid
import urllib.request
import urllib.error

VERSION = "1.1.0"
CONFIG_FILE = "agent_config.json"


# ─────────────────────────── utilidades ────────────────────────────

def _platform() -> str:
    p = sys.platform
    if p.startswith("linux"):   return "linux"
    if p.startswith("win"):     return "windows"
    if p.startswith("darwin"):  return "darwin"
    return p


def _load_config() -> dict:
    try:
        with open(CONFIG_FILE) as f:
            return json.load(f)
    except Exception:
        return {}


def _post(url, data, headers):
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, r.read().decode()
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode()
    except Exception as e:
        return 0, str(e)


def _get(url, headers):
    req = urllib.request.Request(url, headers=headers, method="GET")
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, r.read().decode()
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode()
    except Exception as e:
        return 0, str(e)


# ─────────────────────────── enrollment ────────────────────────────

def enroll(server, code):
    body = json.dumps({
        "enroll_code": code, "platform": _platform(),
        "agent_version": VERSION, "hostname": socket.gethostname(),
    }).encode()
    st, txt = _post(server + "/api/v1/agents/enroll", body,
                    {"content-type": "application/json"})
    if st != 200:
        raise SystemExit(f"[enroll] falhou HTTP {st}: {txt}")
    tok = json.loads(txt)["agent_token"]
    print(f"[enroll] OK — agente registrado em {server}")
    return tok


# ─────────────────────────── ping ──────────────────────────────────

def _ping(host, count=4):
    r = {}
    try:
        flag = "-n" if _platform() == "windows" else "-c"
        out = subprocess.run(["ping", flag, str(count), host],
                             capture_output=True, text=True, timeout=15,
                             errors="replace").stdout
        if _platform() == "windows":
            # Loss: "( 0% loss)" EN or "(0% de perda)" PT — locale-independent
            loss_m = re.search(r"\(\s*(\d+)\s*%", out)
            if loss_m:
                r["loss"] = float(loss_m.group(1))
            # RTT: last 3 "= Xms" values are always min, max, avg regardless of locale
            ms_vals = [float(v) for v in re.findall(r"=\s*(\d+)\s*ms", out)]
            if len(ms_vals) >= 3:
                mn, mx, av = ms_vals[-3], ms_vals[-2], ms_vals[-1]
                r["min"], r["max"], r["avg"] = mn, mx, av
                r["jitter"] = round((mx - mn) / 2, 1)
        else:
            # Linux/macOS: "0% packet loss"
            loss_m = re.search(r"(\d+(?:\.\d+)?)%\s*packet loss", out)
            if loss_m:
                r["loss"] = float(loss_m.group(1))
            # "rtt min/avg/max/mdev = 14.0/15.2/16.1/0.5 ms"
            rtt = re.search(r"=\s*([\d.]+)/([\d.]+)/([\d.]+)(?:/([\d.]+))?", out)
            if rtt:
                r["min"], r["avg"], r["max"] = (
                    float(rtt.group(1)), float(rtt.group(2)), float(rtt.group(3)))
                if rtt.group(4):
                    r["jitter"] = float(rtt.group(4))
    except Exception:
        pass
    return r


def _default_gateway():
    try:
        if _platform() == "windows":
            out = subprocess.run(["ipconfig"], capture_output=True, text=True, timeout=8,
                                 errors="replace").stdout
            # match any line with "gateway" regardless of locale (EN/PT/ES)
            for line in out.splitlines():
                if "gateway" in line.lower():
                    ip_m = re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", line)
                    if ip_m and not ip_m.group(1).endswith(".0"):
                        return ip_m.group(1)
        out = subprocess.run(["ip", "route", "show", "default"],
                             capture_output=True, text=True, timeout=8).stdout
        m = re.search(r"default via ([\d.]+)", out)
        return m.group(1) if m else None
    except Exception:
        return None


def collect_ping(host="8.8.8.8", count=4):
    d = {"host": host}
    d.update(_ping(host, count))
    gw = _default_gateway()
    if gw:
        d["gateway"] = gw
        g = _ping(gw, count)
        if g.get("avg") is not None:
            d["gateway_avg"] = g["avg"]
    return d


# ─────────────────────────── Wi-Fi — interface ─────────────────────

def _default_iface():
    try:
        out = subprocess.run(["ip", "route", "get", "8.8.8.8"],
                             capture_output=True, text=True, timeout=8).stdout
        m = re.search(r"dev (\S+)", out)
        return m.group(1) if m else None
    except Exception:
        return None


def _iface_ip(iface):
    try:
        out = subprocess.run(["ip", "-4", "addr", "show", iface],
                             capture_output=True, text=True, timeout=8).stdout
        m = re.search(r"inet (\d+\.\d+\.\d+\.\d+)", out)
        return m.group(1) if m else None
    except Exception:
        return None


def _wifi_iface_linux():
    """Encontra a interface Wi-Fi ativa no Linux."""
    try:
        out = subprocess.run(["iw", "dev"], capture_output=True, text=True, timeout=5).stdout
        ifaces = re.findall(r"Interface\s+(\S+)", out)
        if ifaces:
            return ifaces[0]
    except Exception:
        pass
    try:
        for name in sorted(os.listdir("/sys/class/net")):
            if re.match(r"wl", name):
                return name
    except Exception:
        pass
    iface = _default_iface()
    return iface if iface and re.match(r"wl", iface) else None


# ─────────────────────────── Wi-Fi — conexão ───────────────────────

def _nmcli_split(line):
    """Split de linha nmcli -t respeitando colons escapados."""
    return re.split(r"(?<!\\):", line)


def _nmcli_unescape(s):
    return s.replace("\\:", ":")


def _wifi_connection_linux():
    result = {}
    iface = _wifi_iface_linux()
    if iface:
        result["interface"] = iface

    # iw dev IFACE link — RSSI em dBm, SSID, BSSID, tx rate
    if iface:
        try:
            out = subprocess.run(["iw", "dev", iface, "link"],
                                 capture_output=True, text=True, timeout=6).stdout
            if "Connected to" in out:
                result["type"] = "wifi"
                m = re.search(r"SSID:\s*(.+)", out)
                if m:
                    result["ssid"] = m.group(1).strip()
                m = re.search(r"Connected to\s+([\w:]+)", out)
                if m:
                    result["bssid"] = m.group(1)
                m = re.search(r"signal:\s*([-\d]+)\s*dBm", out)
                if m:
                    result["signal"] = int(m.group(1))
                m = re.search(r"tx bitrate:\s*([\d.]+)\s*MBit", out)
                if m:
                    result["link_speed"] = float(m.group(1))
                m = re.search(r"freq:\s*(\d+)", out)
                if m:
                    freq = int(m.group(1))
                    result["band"] = "5 GHz" if freq >= 5000 else "2.4 GHz"
        except Exception:
            pass

    # nmcli — channel, security, band
    try:
        out = subprocess.run(
            ["nmcli", "-t", "-f", "active,ssid,bssid,signal,chan,security,freq",
             "dev", "wifi", "list"],
            capture_output=True, text=True, timeout=8
        ).stdout
        for line in out.splitlines():
            parts = _nmcli_split(line.strip())
            if len(parts) < 7 or parts[0] != "*":
                continue
            if not result.get("ssid"):
                result["ssid"] = _nmcli_unescape(parts[1])
            if not result.get("bssid"):
                result["bssid"] = _nmcli_unescape(parts[2])
            if result.get("signal") is None:
                try:
                    result["signal"] = round(int(parts[3]) / 2 - 100)
                except Exception:
                    pass
            try:
                result["channel"] = int(parts[4])
            except Exception:
                pass
            sec = _nmcli_unescape(parts[5])
            if sec:
                result["security"] = sec
            if not result.get("band"):
                try:
                    freq = int(parts[6])
                    result["band"] = "5 GHz" if freq >= 5000 else "2.4 GHz"
                except Exception:
                    pass
            result.setdefault("type", "wifi")
            break
    except Exception:
        pass

    # iwconfig — fallback final
    if not result.get("ssid") and iface:
        try:
            out = subprocess.run(["iwconfig", iface],
                                 capture_output=True, text=True, timeout=5).stdout
            m = re.search(r'ESSID:"([^"]+)"', out)
            if m:
                result["ssid"] = m.group(1)
                result.setdefault("type", "wifi")
            m = re.search(r"Signal level[=:]\s*([-\d]+)", out)
            if m and result.get("signal") is None:
                result["signal"] = int(m.group(1))
            m = re.search(r"Bit Rate[=:]\s*([\d.]+)\s*Mb", out)
            if m and not result.get("link_speed"):
                result["link_speed"] = float(m.group(1))
        except Exception:
            pass

    if not result.get("type"):
        iface = iface or _default_iface()
        result["type"] = "wired" if iface else "unknown"
        if iface:
            result["interface"] = iface

    if iface:
        ip = _iface_ip(iface)
        if ip:
            result["ip"] = ip
    gw = _default_gateway()
    if gw:
        result["gateway"] = gw
    return result


def _wifi_connection_windows():
    result = {"type": "wifi"}
    try:
        out = subprocess.run(
            ["netsh", "wlan", "show", "interfaces"],
            capture_output=True, text=True, timeout=10,
            encoding="utf-8", errors="ignore"
        ).stdout
        for line in out.splitlines():
            line = line.strip()
            m = re.match(r"^SSID\s*:\s*(.+)", line)
            if m:
                result["ssid"] = m.group(1).strip()
                continue
            m = re.match(r"BSSID\s*:\s*(.+)", line)
            if m:
                result["bssid"] = m.group(1).strip()
                continue
            m = re.match(r"Signal\s*:\s*(\d+)%", line)
            if m:
                pct = int(m.group(1))
                result["signal"] = round(pct / 2 - 100)
                result["signal_pct"] = pct
                continue
            m = re.match(r"Channel\s*:\s*(\d+)", line)
            if m:
                ch = int(m.group(1))
                result["channel"] = ch
                result["band"] = "5 GHz" if ch > 14 else "2.4 GHz"
                continue
            m = re.match(r"Authentication\s*:\s*(.+)", line)
            if m:
                result["security"] = m.group(1).strip()
                continue
            m = re.match(r"Receive rate.*?:\s*([\d.]+)", line)
            if m:
                result["link_speed"] = float(m.group(1))
                continue
            m = re.match(r"Name\s*:\s*(.+)", line)
            if m:
                result["interface"] = m.group(1).strip()
    except Exception:
        pass
    try:
        out = subprocess.run(["ipconfig"], capture_output=True, text=True, timeout=8).stdout
        m = re.search(r"IPv4 Address.*?:\s*([\d.]+)", out)
        if m:
            result["ip"] = m.group(1)
    except Exception:
        pass
    gw = _default_gateway()
    if gw:
        result["gateway"] = gw
    return result


def _wifi_connection_macos():
    result = {"type": "wifi"}
    airport = (
        "/System/Library/PrivateFrameworks/Apple80211.framework"
        "/Versions/Current/Resources/airport"
    )
    try:
        out = subprocess.run([airport, "-I"],
                             capture_output=True, text=True, timeout=8).stdout
        for line in out.splitlines():
            line = line.strip()
            m = re.match(r"SSID:\s*(.+)", line)
            if m:
                result["ssid"] = m.group(1).strip()
                continue
            m = re.match(r"BSSID:\s*(.+)", line)
            if m:
                result["bssid"] = m.group(1).strip()
                continue
            m = re.match(r"agrCtlRSSI:\s*([-\d]+)", line)
            if m:
                result["signal"] = int(m.group(1))
                continue
            m = re.match(r"channel:\s*(\d+)", line)
            if m:
                ch = int(m.group(1))
                result["channel"] = ch
                result["band"] = "5 GHz" if ch > 14 else "2.4 GHz"
                continue
            m = re.match(r"lastTxRate:\s*(\d+)", line)
            if m:
                result["link_speed"] = float(m.group(1))
                continue
            m = re.match(r"link auth:\s*(.+)", line)
            if m:
                result["security"] = m.group(1).strip().upper()
    except Exception:
        pass
    try:
        out = subprocess.run(["ifconfig", "en0"],
                             capture_output=True, text=True, timeout=5).stdout
        m = re.search(r"inet ([\d.]+)", out)
        if m:
            result["ip"] = m.group(1)
    except Exception:
        pass
    gw = _default_gateway()
    if gw:
        result["gateway"] = gw
    return result


def _wifi_connection():
    plat = _platform()
    if plat == "linux":     conn = _wifi_connection_linux()
    elif plat == "windows": conn = _wifi_connection_windows()
    elif plat == "darwin":  conn = _wifi_connection_macos()
    else:                   conn = {}
    conn.setdefault("type", "unknown")
    conn.setdefault("interface", "")
    conn.setdefault("ssid", "")
    return conn


# ─────────────────────────── Wi-Fi — scan ──────────────────────────

def _wifi_scan_linux():
    networks = []
    try:
        out = subprocess.run(
            ["nmcli", "-t", "-f", "ssid,bssid,signal,chan,security,freq",
             "dev", "wifi", "list"],
            capture_output=True, text=True, timeout=15
        ).stdout
        for line in out.splitlines():
            line = line.strip()
            if not line:
                continue
            parts = _nmcli_split(line)
            if len(parts) < 4:
                continue
            net = {
                "ssid":  _nmcli_unescape(parts[0]),
                "bssid": _nmcli_unescape(parts[1]) if len(parts) > 1 else "",
            }
            try:
                pct = int(parts[2])
                net["signal"] = round(pct / 2 - 100)
                net["signal_pct"] = pct
            except Exception:
                pass
            try:
                net["channel"] = int(parts[3])
            except Exception:
                pass
            if len(parts) > 4:
                sec = _nmcli_unescape(parts[4])
                net["security"] = sec if sec else "Open"
            try:
                freq = int(parts[5]) if len(parts) > 5 else 0
                net["band"] = "5 GHz" if freq >= 5000 else "2.4 GHz"
                net["freq"] = freq
            except Exception:
                pass
            if net.get("ssid") or net.get("bssid"):
                networks.append(net)
    except Exception:
        pass
    return networks


def _wifi_scan_windows():
    networks = []
    try:
        out = subprocess.run(
            ["netsh", "wlan", "show", "networks", "mode=bssid"],
            capture_output=True, text=True, timeout=15,
            encoding="utf-8", errors="ignore"
        ).stdout
        cur_ssid = ""
        cur_net = {}
        for line in out.splitlines():
            line = line.strip()
            m = re.match(r"SSID\s+\d+\s*:\s*(.+)", line)
            if m:
                if cur_net:
                    networks.append(cur_net)
                cur_ssid = m.group(1).strip()
                cur_net = {}
                continue
            m = re.match(r"BSSID\s+\d+\s*:\s*(.+)", line)
            if m:
                if cur_net:
                    networks.append(cur_net)
                cur_net = {"ssid": cur_ssid, "bssid": m.group(1).strip()}
                continue
            m = re.match(r"Signal\s*:\s*(\d+)%", line)
            if m and cur_net:
                pct = int(m.group(1))
                cur_net["signal"] = round(pct / 2 - 100)
                cur_net["signal_pct"] = pct
                continue
            m = re.match(r"Channel\s*:\s*(\d+)", line)
            if m and cur_net:
                ch = int(m.group(1))
                cur_net["channel"] = ch
                cur_net["band"] = "5 GHz" if ch > 14 else "2.4 GHz"
                continue
            m = re.match(r"Authentication\s*:\s*(.+)", line)
            if m and cur_net:
                cur_net["security"] = m.group(1).strip()
        if cur_net:
            networks.append(cur_net)
    except Exception:
        pass
    return networks


def _wifi_scan_macos():
    networks = []
    airport = (
        "/System/Library/PrivateFrameworks/Apple80211.framework"
        "/Versions/Current/Resources/airport"
    )
    try:
        out = subprocess.run([airport, "-s"],
                             capture_output=True, text=True, timeout=20).stdout
        for line in out.splitlines()[1:]:  # pula header
            m = re.search(r"([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})", line)
            if not m:
                continue
            bssid = m.group(1)
            ssid = line[:m.start()].strip()
            after = line[m.end():].split()
            try:
                rssi = int(after[0])
                ch = int(after[1])
                sec = " ".join(after[3:]) if len(after) > 3 else "Open"
                networks.append({
                    "ssid": ssid, "bssid": bssid, "signal": rssi,
                    "channel": ch,
                    "band": "5 GHz" if ch > 14 else "2.4 GHz",
                    "security": sec,
                })
            except Exception:
                continue
    except Exception:
        pass
    return networks


def _wifi_scan():
    plat = _platform()
    if plat == "linux":     return _wifi_scan_linux()
    if plat == "windows":   return _wifi_scan_windows()
    if plat == "darwin":    return _wifi_scan_macos()
    return []


# ─────────────────────────── score Wi-Fi ───────────────────────────

def _wifi_score(rssi, ping_loss=None, ping_jitter=None, ping_avg=None):
    """Score 0-100: base no RSSI com penalidades de latência e perda."""
    if rssi is None:
        return None
    score = 100
    if rssi >= -55:     pass
    elif rssi >= -65:   score -= 10
    elif rssi >= -75:   score -= 25
    elif rssi >= -85:   score -= 45
    else:               score -= 65
    if ping_loss is not None:
        if ping_loss > 20:    score -= 30
        elif ping_loss > 5:   score -= 15
        elif ping_loss > 1:   score -= 5
    if ping_jitter is not None:
        if ping_jitter > 30:  score -= 10
        elif ping_jitter > 10: score -= 5
    if ping_avg is not None:
        if ping_avg > 200:    score -= 10
        elif ping_avg > 100:  score -= 5
    return max(0, min(100, score))


def _score_label(score):
    if score is None:   return ""
    if score >= 90:     return "Excelente"
    if score >= 75:     return "Bom"
    if score >= 55:     return "Razoável"
    if score >= 35:     return "Ruim"
    return "Crítico"


# ─────────────────────────── snapshot ──────────────────────────────

def build_snapshot():
    ping = collect_ping()
    conn = _wifi_connection()
    scan = _wifi_scan()

    score = _wifi_score(
        conn.get("signal"),
        ping_loss=ping.get("loss"),
        ping_jitter=ping.get("jitter"),
        ping_avg=ping.get("avg"),
    )
    if score is not None:
        conn["score"] = score
        conn["score_label"] = _score_label(score)

    caps = ["connection", "ping"]
    if scan:
        caps.append("scan")

    return {
        "schema_version": "1.0",
        "snapshot_id": str(uuid.uuid4()),
        "captured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "platform": _platform(),
        "capabilities": caps,
        "connection": conn,
        "ping": ping,
        "scan": {"networks": scan},
    }


# ─────────────────────────── iPerf3 ────────────────────────────────

def _iperf_one(host, port, duration, reverse):
    import statistics
    cmd = ["iperf3", "-c", host, "-p", str(port), "-t", str(duration),
           "-J", "--connect-timeout", "3000"]
    if reverse:
        cmd.append("-R")
    out = subprocess.run(cmd, capture_output=True, text=True,
                         timeout=duration + 25).stdout
    j = json.loads(out)
    intervals = []
    for i in j.get("intervals", []):
        s = i.get("sum", {})
        intervals.append({"start": round(s.get("start", 0), 1),
                          "mbps": round(s.get("bits_per_second", 0) / 1e6, 1)})
    end = j.get("end", {})
    s = end.get("sum_received") or end.get("sum") or {}
    avg = round(s.get("bits_per_second", 0) / 1e6, 1)
    mbps = [iv["mbps"] for iv in intervals] or [avg]
    retr = (end.get("sum_sent") or {}).get("retransmits")
    stability = None
    if len(mbps) > 1 and sum(mbps):
        cv = statistics.pstdev(mbps) / (sum(mbps) / len(mbps))
        stability = round(max(0.0, 100.0 - cv * 100.0), 1)
    return {
        "summary": {
            "avg_mbps": avg, "max_mbps": round(max(mbps), 1),
            "min_mbps": round(min(mbps), 1),
            "total_retransmits": retr, "stability": stability,
        },
        "intervals": intervals,
    }


def run_iperf(params):
    host = (params.get("server") or "").strip()
    port = int(params.get("port") or 5201)
    dur  = int(params.get("duration") or 10)
    res  = {"server": host, "port": port, "duration": dur,
            "download": _iperf_one(host, port, dur, True)}
    if params.get("bidirectional", True):
        res["upload"] = _iperf_one(host, port, dur, False)
    return res


# ─────────────────────────── utils de rede ─────────────────────────

def _resolv_server():
    try:
        for line in open("/etc/resolv.conf"):
            if line.startswith("nameserver"):
                return line.split()[1]
    except Exception:
        pass
    return None


def _tcp_check(host, port, timeout=2.0):
    t0 = time.time()
    try:
        s = socket.create_connection((host, port), timeout=timeout)
        s.close()
        return True, round((time.time() - t0) * 1000, 1)
    except Exception:
        return False, None


def _dns_resolve_time(server, name="google.com"):
    t0 = time.time()
    try:
        out = subprocess.run(["nslookup", name, server],
                             capture_output=True, text=True, timeout=5)
        ok = "Address" in out.stdout and "can't find" not in out.stdout.lower()
        return ok, round((time.time() - t0) * 1000, 1)
    except Exception:
        ok, ms = _tcp_check(server, 53)
        return ok, ms


def _ethernet_info(iface):
    base = f"/sys/class/net/{iface}"

    def r(p):
        try:
            return open(base + "/" + p).read().strip()
        except Exception:
            return None

    speed = r("speed")
    info = {
        "iface": iface,
        "speed": (speed + " Mbps") if speed and speed.isdigit() else (speed or "—"),
        "duplex": r("duplex") or "—",
        "errors": None, "drops": None,
    }
    try:
        info["errors"] = (int(r("statistics/rx_errors") or 0)
                          + int(r("statistics/tx_errors") or 0))
        info["drops"]  = (int(r("statistics/rx_dropped") or 0)
                          + int(r("statistics/tx_dropped") or 0))
    except Exception:
        pass
    return info


def _nmap_scan(subnet):
    if not subnet:
        return None
    try:
        out = subprocess.run(["nmap", "-sn", subnet],
                             capture_output=True, text=True, timeout=60).stdout
    except Exception:
        return None
    hosts, cur = [], None
    for line in out.splitlines():
        m = re.search(r"Nmap scan report for (.+)", line)
        if m:
            tgt = m.group(1).strip()
            # extrai IP: preferir número entre parênteses; fallback = primeiro octeto completo
            ipm = re.search(r"\((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\)", tgt) \
               or re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", tgt)
            if not ipm:
                cur = None
                continue
            cur = {
                "ip": ipm.group(1),
                "hostname": tgt.split("(")[0].strip() if "(" in tgt else "",
                "type": "—", "os": "—", "ports": "—", "mac": "—", "vendor": "",
            }
            hosts.append(cur)
        elif cur and "MAC Address:" in line:
            mm = re.search(r"MAC Address: (\S+)\s*(\((.+)\))?", line)
            if mm:
                cur["mac"] = mm.group(1)
                cur["vendor"] = (mm.group(3) or "").strip()
    return {"subnet": subnet, "hosts": hosts} if hosts else None


# ─────────────────────────── MTR / traceroute ──────────────────────

def _traceroute(target):
    """Fallback de MTR: traceroute (Linux/macOS) ou tracert (Windows).
    Parse locale-independent (só regex de IP e de 'ms', nunca nomes EN)."""
    plat = _platform()
    cmd = (["tracert", "-d", "-w", "1000", target] if plat == "windows"
           else ["traceroute", "-n", "-q", "3", target])
    try:
        out = subprocess.run(cmd, capture_output=True, text=True,
                             timeout=90, errors="replace").stdout
    except Exception:
        return None
    hops = []
    for line in out.splitlines():
        m = re.match(r"\s*(\d+)\s+(.*)", line)
        if not m:
            continue
        n, rest = int(m.group(1)), m.group(2)
        rtts = [float(v) for v in re.findall(r"([\d.]+)\s*ms", rest)]
        ipm = re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", rest)
        probes = 3  # traceroute -q 3 / tracert padrão = 3 sondas por hop
        hop = {"hop": n, "ip": ipm.group(1) if ipm else "*",
               "min": None, "avg": None, "max": None, "jitter": None}
        if rtts:
            mn, mx = min(rtts), max(rtts)
            hop["min"] = round(mn, 1)
            hop["avg"] = round(sum(rtts) / len(rtts), 1)
            hop["max"] = round(mx, 1)
            # jitter = (max-min)/2 — mesma convenção do _ping deste agente
            hop["jitter"] = round((mx - mn) / 2, 1)
        # perda real por sondas perdidas (parcial vira % correto, não 0%)
        hop["loss"] = round(max(0, probes - len(rtts)) / probes * 100, 1)
        hops.append(hop)
    return {"target": target, "hops": hops} if hops else None


def _mtr(target="8.8.8.8", count=10):
    """MTR via 'mtr --json' se houver; senão traceroute/tracert.
    Retorna {target, hops:[{hop,ip,loss,min,avg,max,jitter}]} ou None."""
    try:
        out = subprocess.run(
            ["mtr", "--report", "--json", "-n", "-c", str(count), target],
            capture_output=True, text=True, timeout=count * 2 + 30).stdout
        hubs = (json.loads(out).get("report", {}) or {}).get("hubs") or []
        hops = []
        for h in hubs:
            hops.append({
                "hop":    h.get("count"),
                "ip":     h.get("host", "*"),
                "loss":   round(h.get("Loss%", 0), 1),
                "min":    round(h.get("Best", 0), 1),
                "avg":    round(h.get("Avg", 0), 1),
                "max":    round(h.get("Wrst", 0), 1),
                "jitter": round(h.get("StDev", 0), 1),
            })
        if hops:
            return {"target": target, "hops": hops}
    except Exception:
        pass
    return _traceroute(target)


# ─────────────────────────── speedtest ─────────────────────────────

def _speedtest():
    """Ookla 'speedtest' (--format=json) ou 'speedtest-cli' (--json).
    Retorna {download, upload, ping} em Mbps/ms ou None (sem quebrar)."""
    # 1) Ookla CLI — bandwidth em bytes/s. A saída pode ser um único objeto
    #    JSON ou NDJSON (uma linha por evento) — tolerar os dois.
    try:
        out = subprocess.run(
            ["speedtest", "--format=json", "--accept-license", "--accept-gdpr"],
            capture_output=True, text=True, timeout=120).stdout
        j = None
        try:
            j = json.loads(out)
        except Exception:
            for ln in reversed(out.splitlines()):
                if '"download"' in ln and '"bandwidth"' in ln:
                    j = json.loads(ln)
                    break
        dl = (j or {}).get("download", {})
        if isinstance(dl, dict) and "bandwidth" in dl:
            ul = j.get("upload", {}) or {}
            return {
                "download": round(float(dl["bandwidth"]) * 8 / 1e6, 1),
                "upload":   round(float(ul.get("bandwidth", 0)) * 8 / 1e6, 1),
                "ping":     round(float((j.get("ping", {}) or {}).get("latency", 0)), 1),
            }
    except Exception:
        pass
    # 2) speedtest-cli (Python) — download/upload em bits/s (campos podem vir
    #    como string em alguns builds — coagir para float).
    try:
        out = subprocess.run(["speedtest-cli", "--json"],
                             capture_output=True, text=True, timeout=120).stdout
        j = json.loads(out)
        dl = j.get("download")
        if dl is not None:
            return {
                "download": round(float(dl) / 1e6, 1),
                "upload":   round(float(j.get("upload") or 0) / 1e6, 1),
                "ping":     round(float(j.get("ping") or 0), 1),
            }
    except Exception:
        pass
    return None


# ─────────────────────────── diagnóstico ───────────────────────────

def run_diagnostic(params):
    iface = _default_iface()
    medium = ("wireless" if iface and re.match(r"(wl|wlan|wlp|wlo)", iface)
              else ("wired" if iface else "unknown"))
    d = {"medium": medium, "interface": iface or "—"}

    gw = _default_gateway()
    if gw:
        g = _ping(gw, 3)
        d["gateway"] = {"ip": gw, "ok": g.get("avg") is not None,
                        "latency_ms": g.get("avg")}
    else:
        d["gateway"] = {"ip": None, "ok": False, "latency_ms": None}

    inet = _ping("8.8.8.8", 3)
    d["internet"] = {"ok": inet.get("avg") is not None,
                     "host": "8.8.8.8", "latency_ms": inet.get("avg")}

    dns_srv = _resolv_server() or "8.8.8.8"
    dok, dms = _dns_resolve_time(dns_srv)
    d["dns"] = {"ok": dok, "server": dns_srv, "latency_ms": dms}

    mtu = None
    if iface:
        try:
            mtu = int(open(f"/sys/class/net/{iface}/mtu").read().strip())
        except Exception:
            pass
    d["mtu"] = {"mtu": mtu, "fragmentation": False}

    for name, addr in [("Google", "8.8.8.8"), ("Cloudflare", "1.1.1.1"), ("Quad9", "9.9.9.9")]:
        sok, sms = _dns_resolve_time(addr)
        d.setdefault("dns_servers", []).append(
            {"name": name, "address": addr, "ok": sok, "latency_ms": sms})

    if iface:
        d["ethernet"] = [_ethernet_info(iface)]

    d["ports"] = []
    for svc, host, port in [("HTTP", "google.com", 80),
                             ("HTTPS", "google.com", 443),
                             ("DNS", dns_srv, 53)]:
        pok, pms = _tcp_check(host, port)
        d["ports"].append({"service": svc, "host": host, "port": port,
                           "open": pok, "latency_ms": pms})

    ip = _iface_ip(iface) if iface else None
    if ip:
        subnet = ".".join(ip.split(".")[:3]) + ".0/24"
        nm = _nmap_scan(subnet)
        if nm:
            d["nmap"] = nm

    mtr = _mtr("8.8.8.8")
    if mtr:
        d["mtr"] = mtr

    sp = _speedtest()
    if sp:
        d["speedtest"] = sp

    score = 100
    reasons = []
    if d["gateway"]["ok"]:
        reasons.append({"type": "ok", "icon": "🚪",
                        "text": f"Gateway {d['gateway']['latency_ms']}ms"})
    else:
        score -= 40
        reasons.append({"type": "error", "icon": "🚪", "text": "Gateway inacessível"})
    if d["internet"]["ok"]:
        reasons.append({"type": "ok", "icon": "🌐",
                        "text": f"Internet {d['internet']['latency_ms']}ms"})
    else:
        score -= 40
        reasons.append({"type": "error", "icon": "🌐", "text": "Sem internet"})
    if d["dns"]["ok"]:
        reasons.append({"type": "ok", "icon": "🔤", "text": "DNS OK"})
    else:
        score -= 25
        reasons.append({"type": "error", "icon": "🔤", "text": "DNS falhou"})
    if d["gateway"]["ok"] and (d["gateway"]["latency_ms"] or 0) > 50:
        score -= 10
        reasons.append({"type": "warning", "icon": "⏱",
                        "text": "Latência alta no gateway"})

    d["score"] = max(0, min(100, score))
    d["reasons"] = reasons
    d["summary"] = (
        f"Gateway {'OK' if d['gateway']['ok'] else 'FALHA'}"
        f" · Internet {'OK' if d['internet']['ok'] else 'FALHA'}"
        f" · DNS {'OK' if d['dns']['ok'] else 'FALHA'}"
        f" · MTU {mtu or '—'}"
    )
    return d


def run_lan(params):
    """Diagnóstico LAN: saúde de interfaces + scan de hosts + portas TCP."""
    from concurrent.futures import ThreadPoolExecutor, as_completed

    iface = _default_iface()
    ip = _iface_ip(iface) if iface else None
    gw = _default_gateway()

    gw_info = {"ip": None, "ok": False, "latency_ms": None}
    if gw:
        g = _ping(gw, 3)
        gw_info = {"ip": gw, "ok": g.get("avg") is not None, "latency_ms": g.get("avg")}

    subnet = (".".join(ip.split(".")[:3]) + ".0/24") if ip else None

    LAN_PORTS = [(22, "SSH"), (80, "HTTP"), (443, "HTTPS"),
                 (445, "SMB"), (8080, "HTTP-alt"), (3389, "RDP")]

    def _check_host(h):
        out = dict(h)
        out["ports"] = []
        for port, svc in LAN_PORTS:
            ok, ms = _tcp_check(h["ip"], port, timeout=0.3)
            if ok:
                out["ports"].append({"port": port, "service": svc,
                                     "open": True, "latency_ms": ms})
        return out

    hosts = []
    if subnet:
        nm = _nmap_scan(subnet)
        if nm and nm.get("hosts"):
            with ThreadPoolExecutor(max_workers=8) as ex:
                futs = {ex.submit(_check_host, h): h for h in nm["hosts"]}
                for f in as_completed(futs):
                    try:
                        hosts.append(f.result())
                    except Exception:
                        pass
            hosts.sort(key=lambda h: (
                [int(p) for p in h["ip"].split(".") if p.isdigit()]
                or [999, 999, 999, 999]))

    interfaces = []
    try:
        ifaces = sorted(os.listdir("/sys/class/net/"))
    except Exception:
        ifaces = [iface] if iface else []
    for ifc in ifaces:
        info = _ethernet_info(ifc)
        try:
            info["operstate"] = open(f"/sys/class/net/{ifc}/operstate").read().strip()
        except Exception:
            info["operstate"] = "unknown"
        try:
            info["mtu"] = int(open(f"/sys/class/net/{ifc}/mtu").read().strip())
        except Exception:
            info["mtu"] = None
        interfaces.append(info)

    return {
        "interface": iface or "—",
        "ip": ip or "—",
        "subnet": subnet or "—",
        "gateway": gw_info,
        "hosts": hosts,
        "host_count": len(hosts),
        "interfaces": interfaces,
    }


# ─────────────────────────── loop & diagnóstico ────────────────────

def _ld_arp():
    """ARP: detecta conflitos. Usa set de pares (ip,mac) para deduplicar dual-interface."""
    r = {"entries": 0, "conflicts": [], "conflict_count": 0}
    if _platform() != "linux":
        r["note"] = "só Linux"; return r
    try:
        lines = open("/proc/net/arp").read().splitlines()[1:]
    except Exception:
        r["error"] = "não disponível"; return r
    mac_to_ips, ip_to_macs, seen_pairs = {}, {}, set()
    for line in lines:
        parts = line.split()
        if len(parts) < 4:
            continue
        ip, mac = parts[0], parts[3].lower()
        if mac in ("00:00:00:00:00:00", ""):
            continue
        if (ip, mac) in seen_pairs:
            continue
        seen_pairs.add((ip, mac))
        r["entries"] += 1
        mac_to_ips.setdefault(mac, set()).add(ip)
        ip_to_macs.setdefault(ip, set()).add(mac)
    conflicts = []
    for ip, macs in ip_to_macs.items():
        if len(macs) > 1:
            conflicts.append({"type": "ip_multi_mac", "ip": ip, "macs": sorted(macs)})
    for mac, ips in mac_to_ips.items():
        if len(ips) > 1:
            conflicts.append({"type": "mac_multi_ip", "mac": mac, "ips": sorted(ips)})
    r["conflicts"] = conflicts
    r["conflict_count"] = len(conflicts)
    return r


def _ld_interfaces():
    """Saúde de todas as interfaces (exceto lo) via /sys/class/net."""
    ifaces = []
    try:
        names = sorted(os.listdir("/sys/class/net"))
    except Exception:
        return ifaces
    for name in names:
        if name == "lo":
            continue
        info = _ethernet_info(name)
        try:
            info["operstate"] = open(f"/sys/class/net/{name}/operstate").read().strip()
        except Exception:
            info["operstate"] = "unknown"
        try:
            info["mtu"] = int(open(f"/sys/class/net/{name}/mtu").read().strip())
        except Exception:
            info["mtu"] = None
        ifaces.append(info)
    return ifaces


def _ld_broadcast(iface, duration=3):
    """Mede taxa de broadcast/multicast em `duration` segundos."""
    result = {"iface": iface, "duration": duration,
              "rx_bcast_pps": None, "rx_mcast_pps": None, "storm": False}
    if not iface or _platform() != "linux":
        return result
    base = f"/sys/class/net/{iface}/statistics"

    def _rd(name):
        try: return int(open(f"{base}/{name}").read().strip())
        except Exception: return None

    b1, m1, p1 = _rd("rx_broadcast"), _rd("rx_multicast"), _rd("rx_packets")
    time.sleep(duration)
    b2, m2, p2 = _rd("rx_broadcast"), _rd("rx_multicast"), _rd("rx_packets")

    def _rate(a, b):
        return round((b - a) / duration, 1) if a is not None and b is not None else None

    bcast_pps = _rate(b1, b2)
    mcast_pps = _rate(m1, m2)
    pkt_pps   = _rate(p1, p2)
    result["rx_bcast_pps"] = bcast_pps
    result["rx_mcast_pps"] = mcast_pps
    if bcast_pps is not None:
        result["storm"] = bcast_pps > 500 or bool(
            pkt_pps and pkt_pps > 0 and bcast_pps / pkt_pps > 0.5)
    return result


def _ld_dupip(iface):
    """Detecta IPs duplicados via arping. NUNCA confia no exit code (BSD arping retorna 1
    mesmo sem duplicata). Parseia 'reply' procurando MAC diferente do nosso."""
    result = {"checked": [], "duplicates": []}
    if _platform() != "linux":
        result["note"] = "só Linux"; return result

    our_mac = None
    if iface:
        try:
            our_mac = open(f"/sys/class/net/{iface}/address").read().strip().lower()
        except Exception:
            pass

    ips = []
    try:
        for line in open("/proc/net/arp").read().splitlines()[1:]:
            parts = line.split()
            if len(parts) >= 4 and parts[3].lower() not in ("00:00:00:00:00:00", ""):
                ips.append(parts[0])
    except Exception:
        return result

    for ip in list(dict.fromkeys(ips))[:10]:
        result["checked"].append(ip)
        try:
            cmd = ["arping", "-c", "2", "-w", "1"]
            if iface:
                cmd += ["-I", iface]
            cmd.append(ip)
            out = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
            text = out.stdout + out.stderr
        except Exception:
            continue
        reply_macs = set()
        for line in text.splitlines():
            m = re.search(r"\[([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})\]", line)
            if m and "reply" in line.lower():
                reply_macs.add(m.group(1).lower())
        foreign = {m for m in reply_macs if our_mac and m != our_mac}
        if foreign:
            result["duplicates"].append({"ip": ip, "foreign_macs": sorted(foreign)})
    return result


def _ld_routing(target="8.8.8.8"):
    """Traceroute com detecção de loop (IPs repetidos na rota)."""
    try:
        cmd = (["tracert", "-d", "-h", "15", target] if _platform() == "windows"
               else ["traceroute", "-n", "-m", "15", target])
        out = subprocess.run(cmd, capture_output=True, text=True, timeout=35).stdout
    except Exception:
        return {"target": target, "loop_detected": False, "hops": []}
    hops, seen_ips, loop_detected = [], set(), False
    for line in out.splitlines():
        m = re.match(r"\s*(\d+)\s+([\d.]+|\*)", line)
        if not m:
            continue
        hop_n, ip = int(m.group(1)), m.group(2)
        rtt_m = re.search(r"([\d.]+)\s*ms", line)
        rtt = float(rtt_m.group(1)) if rtt_m else None
        repeated = ip != "*" and ip in seen_ips
        if repeated:
            loop_detected = True
        if ip != "*":
            seen_ips.add(ip)
        hops.append({"hop": hop_n, "ip": ip, "rtt_ms": rtt, "repeated": repeated})
    return {"target": target, "loop_detected": loop_detected, "hops": hops}


def _ld_stp():
    """Verifica bridges Linux e STP via /sys/class/net/*/bridge."""
    bridges = []
    if _platform() != "linux":
        return {"bridges": bridges, "detected": False}
    try:
        for name in sorted(os.listdir("/sys/class/net")):
            bdir = f"/sys/class/net/{name}/bridge"
            if not os.path.isdir(bdir):
                continue
            info = {"bridge": name}
            for key in ("root_id", "bridge_id", "stp_state", "hello_time", "forward_delay"):
                try:
                    info[key] = open(f"{bdir}/{key}").read().strip()
                except Exception:
                    pass
            bridges.append(info)
    except Exception:
        pass
    return {"bridges": bridges, "detected": len(bridges) > 0}


def _ld_kernel():
    """Contadores do kernel: ICMP redirects, fragmentação IP, TCP abortadas."""
    result = {}
    try:
        text = open("/proc/net/snmp").read()
        lines = text.splitlines()
        for i in range(0, len(lines) - 1, 2):
            prefix = lines[i].split(":")[0]
            keys = lines[i].split()[1:]
            vals = lines[i + 1].split()[1:]
            if len(keys) != len(vals):
                continue
            kv = {k: int(v) for k, v in zip(keys, vals)}
            if prefix == "Icmp":
                result["icmp_redirects_in"]  = kv.get("InRedirects", 0)
                result["icmp_redirects_out"] = kv.get("OutRedirects", 0)
            elif prefix == "Ip":
                result["ip_fragfails"]  = kv.get("FragFails", 0)
                result["ip_reasmfails"] = kv.get("ReasmFails", 0)
    except Exception:
        pass
    try:
        acc = 0
        for line in open("/proc/net/stat/tcp").read().splitlines()[1:]:
            vals = [int(v, 16) for v in line.split()]
            if len(vals) > 9:
                acc += vals[9]
        result["tcp_aborted"] = acc
    except Exception:
        pass
    try:
        result["accept_redirects"] = int(
            open("/proc/sys/net/ipv4/conf/all/accept_redirects").read().strip())
    except Exception:
        pass
    return result


def run_loopdiag(params):
    """Loop & Diagnóstico: 7 fases — ARP, interfaces, broadcast, IPs duplicados,
    roteamento, STP/bridges e contadores do kernel."""
    iface = _default_iface()
    r = {}
    r["arp"]        = _ld_arp()
    r["interfaces"] = _ld_interfaces()
    r["broadcast"]  = _ld_broadcast(iface, duration=3)
    r["dupip"]      = _ld_dupip(iface)
    r["routing"]    = _ld_routing("8.8.8.8")
    r["stp"]        = _ld_stp()
    r["kernel"]     = _ld_kernel()

    issues = []
    if r["arp"].get("conflict_count", 0):
        issues.append(f"{r['arp']['conflict_count']} conflito(s) ARP")
    if r["broadcast"].get("storm"):
        issues.append("broadcast storm detectado")
    if r["dupip"].get("duplicates"):
        issues.append(f"{len(r['dupip']['duplicates'])} IP(s) duplicado(s)")
    if r["routing"].get("loop_detected"):
        issues.append("loop de roteamento detectado")
    r["summary"] = {"ok": len(issues) == 0, "issues": issues}
    return r


# ─────────────────────────── scan / ping sob demanda ───────────────

def run_scan(_params):
    """Scan Wi-Fi sob demanda — retorna lista de redes detectadas."""
    networks = _wifi_scan() or []
    return {"networks": networks}


def run_ping(params):
    """Ping sob demanda — pinga host externo + gateway."""
    host = (params or {}).get("host", "8.8.8.8")
    count = int((params or {}).get("count", 10))
    return collect_ping(host, count)


# ─────────────────────────── comandos (polling) ────────────────────

def poll_commands(server, token):
    """Busca o próximo comando pendente e executa."""
    hdr = {"Authorization": "Bearer " + token}
    st, body = _get(server + "/api/v1/agent/commands/next", hdr)
    if st != 200:
        return
    cmd = (json.loads(body) if body else {}).get("command")
    if not cmd:
        return
    cid, ctype = cmd["id"], cmd["type"]
    params = cmd.get("params") or {}
    rhdr = {"Authorization": "Bearer " + token, "Content-Type": "application/json"}
    rurl = server + f"/api/v1/agent/commands/{cid}/result"
    try:
        if ctype == "iperf":
            result = run_iperf(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] iperf concluído")
        elif ctype == "diagnostic":
            result = run_diagnostic(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] diagnóstico concluído")
        elif ctype == "lan":
            result = run_lan(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] LAN Doctor concluído")
        elif ctype == "loopdiag":
            result = run_loopdiag(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] Loop & Diagnóstico concluído")
        elif ctype == "scan":
            result = run_scan(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] scan Wi-Fi concluído ({len(result.get('networks', []))} redes)")
        elif ctype == "ping":
            result = run_ping(params)
            _post(rurl, json.dumps({"status": "done", "result": result}).encode(), rhdr)
            print(f"  [cmd {cid}] ping concluído")
        else:
            _post(rurl,
                  json.dumps({"status": "error",
                              "error": f"tipo não suportado: {ctype}"}).encode(), rhdr)
    except Exception as e:
        _post(rurl, json.dumps({"status": "error", "error": str(e)}).encode(), rhdr)
        print(f"  [cmd {cid}] erro: {e}")


# ─────────────────────────── push ──────────────────────────────────

def push(server, token, snap):
    raw = gzip.compress(json.dumps(snap).encode())
    return _post(server + "/api/v1/ingest", raw, {
        "content-type": "application/json",
        "content-encoding": "gzip",
        "authorization": "Bearer " + token,
    })


# ─────────────────────────── main ──────────────────────────────────

def main():
    ap = argparse.ArgumentParser(
        description="DR Network & DR Wi-Fi Analyzer — Agente v" + VERSION)
    ap.add_argument("--server", help="URL do servidor (ex: http://1.2.3.4)")
    ap.add_argument("--enroll-code", help="código de registro (1ª vez)")
    ap.add_argument("--token-file", default="agent_token")
    ap.add_argument("--interval", type=int, default=30,
                    help="segundos entre envios (padrão: 30)")
    ap.add_argument("--once", action="store_true",
                    help="envia um snapshot e sai")
    a = ap.parse_args()

    cfg = _load_config()
    server = (a.server or cfg.get("server", "")).rstrip("/")
    if not server:
        raise SystemExit(
            "Informe o servidor com --server ou defina 'server' em agent_config.json")

    token = None
    if os.path.exists(a.token_file):
        token = open(a.token_file).read().strip()
    if not token:
        if not a.enroll_code:
            raise SystemExit("Primeira execução exige --enroll-code")
        token = enroll(server, a.enroll_code)
        with open(a.token_file, "w") as f:
            f.write(token)
        os.chmod(a.token_file, 0o600)

    print(f"DR Wi-Fi Analyzer v{VERSION} | servidor: {server} | intervalo: {a.interval}s")
    print("-" * 60)

    while True:
        snap = build_snapshot()
        conn = snap.get("connection", {})
        ping = snap.get("ping", {})
        n_redes = len((snap.get("scan") or {}).get("networks") or [])

        st, txt = push(server, token, snap)

        ssid      = conn.get("ssid") or "—"
        rssi      = conn.get("signal")
        score     = conn.get("score")
        ping_avg  = ping.get("avg")
        loss      = ping.get("loss")
        rssi_s    = f"{rssi} dBm" if rssi is not None else "—"
        score_s   = f"{score} ({_score_label(score)})" if score is not None else "—"
        ping_s    = f"{ping_avg} ms" if ping_avg is not None else "—"
        loss_s    = f"{loss}%" if loss is not None else "—"

        print(f"[{time.strftime('%H:%M:%S')}] "
              f"SSID={ssid} | RSSI={rssi_s} | Score={score_s} | "
              f"Ping={ping_s} | Perda={loss_s} | Redes={n_redes} | HTTP {st}")

        try:
            poll_commands(server, token)
        except Exception:
            pass

        if a.once:
            break
        time.sleep(a.interval)


if __name__ == "__main__":
    main()
