"""Netzwerk via nmcli (NetworkManager). UI-frei.

WiFi an/aus, Status, verfügbare Netze, Verbinden. Für den aktiven User ohne
root (NetworkManager-polkit erlaubt network-control). Schreib-Aktionen liefern
argv (der Aufrufer führt sie via ProcessRunner aus, mit Live-Output).
"""
from __future__ import annotations

import subprocess
from dataclasses import dataclass


def _run(args: list[str], timeout: int = 8) -> str:
    try:
        r = subprocess.run(["nmcli", *args], capture_output=True, text=True,
                           check=False, timeout=timeout)
        return r.stdout if r.returncode == 0 else ""
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return ""


def available() -> bool:
    return bool(_run(["-t", "-f", "RUNNING", "general"]))


def _split(line: str) -> list[str]:
    """nmcli -t Felder splitten, escaped '\\:' respektieren."""
    out: list[str] = []
    cur = ""
    i = 0
    while i < len(line):
        c = line[i]
        if c == "\\" and i + 1 < len(line):
            cur += line[i + 1]
            i += 2
            continue
        if c == ":":
            out.append(cur)
            cur = ""
            i += 1
            continue
        cur += c
        i += 1
    out.append(cur)
    return out


@dataclass
class WifiNet:
    ssid: str
    signal: int        # 0..100
    security: str      # "" = offen
    in_use: bool


def wifi_enabled() -> bool:
    return _run(["-t", "-f", "WIFI", "radio"]).strip() == "enabled"


def set_wifi_argv(on: bool) -> list[str]:
    return ["nmcli", "radio", "wifi", "on" if on else "off"]


def active_summary() -> str:
    """Kurzbeschreibung der aktiven Verbindung(en)."""
    out = _run(["-t", "-f", "NAME,TYPE,DEVICE", "connection", "show", "--active"])
    parts = []
    for line in out.splitlines():
        f = _split(line)
        if len(f) >= 3 and f[1] != "loopback":
            parts.append(f"{f[0]} ({f[2]})")
    return ", ".join(parts) if parts else "Not connected"


def wifi_networks(rescan: bool = False) -> list[WifiNet]:
    # rescan=False -> NetworkManagers Cache (instant); True -> aktiver Scan (langsam).
    out = _run(["-t", "-f", "IN-USE,SSID,SIGNAL,SECURITY", "device", "wifi", "list",
                "--rescan", "yes" if rescan else "no"])
    best: dict[str, WifiNet] = {}
    for line in out.splitlines():
        f = _split(line)
        if len(f) < 4:
            continue
        in_use, ssid, sig, sec = f[0], f[1], f[2], f[3]
        if not ssid:
            continue
        try:
            signal = int(sig)
        except ValueError:
            signal = 0
        net = WifiNet(ssid=ssid, signal=signal, security=sec.strip(),
                      in_use=in_use.strip() == "*")
        cur = best.get(ssid)
        if cur is None or net.signal > cur.signal or net.in_use:
            best[ssid] = net
    return sorted(best.values(), key=lambda n: (not n.in_use, -n.signal))


def connect_argv(ssid: str, password: str = "") -> list[str]:
    argv = ["nmcli", "device", "wifi", "connect", ssid]
    if password:
        argv += ["password", password]
    return argv


def saved_connections() -> set[str]:
    """Namen gespeicherter NetworkManager-Verbindungen."""
    out = _run(["-t", "-f", "NAME", "connection", "show"])
    names: set[str] = set()
    for line in out.splitlines():
        f = _split(line)
        if f and f[0]:
            names.add(f[0])
    return names


def wifi_device() -> str:
    """Erstes Wi-Fi-Device (z.B. 'wlp3s0'), sonst leer."""
    out = _run(["-t", "-f", "DEVICE,TYPE", "device"])
    for line in out.splitlines():
        f = _split(line)
        if len(f) >= 2 and f[1].strip() == "wifi":
            return f[0]
    return ""


def disconnect_argv() -> list[str]:
    """Disconnect des aktiven Wi-Fi via Device. Leer, wenn kein Device."""
    dev = wifi_device()
    if not dev:
        return []
    return ["nmcli", "device", "disconnect", dev]


def forget_argv(ssid: str) -> list[str]:
    """Gespeicherte Verbindung löschen."""
    return ["nmcli", "connection", "delete", ssid]


# ----------------------------------------------------------------- VPN

@dataclass
class Vpn:
    name: str
    vtype: str        # 'vpn' | 'wireguard'
    active: bool


def vpn_connections() -> list[Vpn]:
    active = set()
    for line in _run(["-t", "-f", "NAME", "connection", "show", "--active"]).splitlines():
        if line.strip():
            active.add(line.strip())
    out: list[Vpn] = []
    for line in _run(["-t", "-f", "NAME,TYPE", "connection", "show"]).splitlines():
        parts = _split(line)
        if len(parts) >= 2 and parts[1] in ("vpn", "wireguard"):
            out.append(Vpn(parts[0], parts[1], parts[0] in active))
    return out


def vpn_up_argv(name: str) -> list[str]:
    return ["nmcli", "connection", "up", name]


def vpn_down_argv(name: str) -> list[str]:
    return ["nmcli", "connection", "down", name]


def vpn_import_argv(path: str) -> list[str] | None:
    low = path.lower()
    if low.endswith(".ovpn"):
        vtype = "openvpn"
    elif low.endswith(".conf"):
        vtype = "wireguard"
    else:
        return None
    return ["nmcli", "connection", "import", "type", vtype, "file", path]


# ----------------------------------------------------------------- DNS

DNS_PRESETS = {
    "Automatic": "",
    "Cloudflare": "1.1.1.1 1.0.0.1",
    "Google": "8.8.8.8 8.8.4.4",
    "Quad9": "9.9.9.9 149.112.112.112",
}


def primary_connection() -> str | None:
    for line in _run(["-t", "-f", "NAME,TYPE", "connection", "show", "--active"]).splitlines():
        parts = _split(line)
        if len(parts) >= 2 and parts[1] in ("802-11-wireless", "802-3-ethernet"):
            return parts[0]
    return None


def get_dns(name: str) -> str:
    return _run(["-g", "ipv4.dns", "connection", "show", name]).strip().replace("|", " ")


def set_dns_cmds(name: str, servers: str) -> list[list[str]]:
    """modify + reaktivieren. servers='' = automatisch (DHCP)."""
    if servers:
        modify = ["nmcli", "connection", "modify", name,
                  "ipv4.dns", servers, "ipv4.ignore-auto-dns", "yes"]
    else:
        modify = ["nmcli", "connection", "modify", name,
                  "ipv4.dns", "", "ipv4.ignore-auto-dns", "no"]
    return [modify, ["nmcli", "connection", "up", name]]
