"""Steuerung von wpa_supplicant über seinen Control-Socket.

Das ist derselbe Kanal, den auch wpa_cli benutzt: ein UNIX-Datagramm-Socket,
über den Befehle im Klartext gehen. Ereignisse (Gerät gefunden, Gruppe
gestartet) kommen über eine zweite, angemeldete Verbindung herein.
"""

import logging
import os
import queue
import socket
import tempfile
import threading
from typing import Dict, List, Optional

log = logging.getLogger("wfd.wpa")

CONTROL_DIRS = ("/run/wpa_supplicant", "/var/run/wpa_supplicant")


def find_interfaces() -> List[str]:
    """Alle Interfaces, für die ein Control-Socket bereitsteht."""
    found = []
    for directory in CONTROL_DIRS:
        try:
            for name in sorted(os.listdir(directory)):
                path = os.path.join(directory, name)
                if os.path.exists(path) and path not in found:
                    found.append(path)
        except OSError:
            continue
    return found


def wireless_interfaces() -> List[str]:
    """Namen der WLAN-Geräte laut Kernel."""
    try:
        return sorted(os.listdir("/sys/class/net")) and [
            name for name in sorted(os.listdir("/sys/class/net"))
            if os.path.exists(f"/sys/class/net/{name}/wireless")
            or os.path.exists(f"/sys/class/net/{name}/phy80211")
        ]
    except OSError:
        return []


class WpaError(RuntimeError):
    pass


class WpaClient:
    """Eine Verbindung zum Control-Socket eines Interfaces."""

    def __init__(self, control_path: str):
        self.control_path = control_path
        self._local_path = os.path.join(
            tempfile.gettempdir(), f"wfd-ctrl-{os.getpid()}-{id(self)}"
        )
        self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
        try:
            self._sock.bind(self._local_path)
            self._sock.connect(control_path)
        except OSError as error:
            self._cleanup()
            raise WpaError(
                f"Kein Zugriff auf {control_path}: {error}. "
                "Meist fehlen Rechte - als root ausführen oder der Gruppe netdev beitreten."
            ) from error
        self._sock.settimeout(5)
        self._lock = threading.Lock()

        self.events: "queue.Queue[str]" = queue.Queue()
        self._attached = False
        self._reader: Optional[threading.Thread] = None
        self._stop = threading.Event()

    # ------------------------------------------------------------ Befehle

    def request(self, command: str, timeout: float = 5.0) -> str:
        """Schickt einen Befehl und liefert die Antwort."""
        with self._lock:
            self._sock.settimeout(timeout)
            self._sock.send(command.encode())
            while True:
                data = self._sock.recv(8192).decode("utf-8", "replace")
                # Ereignisse können dazwischenfunken - die gehören in die Queue.
                if data.startswith("<"):
                    self.events.put(data)
                    continue
                return data.strip()

    def ok(self, command: str) -> bool:
        try:
            return self.request(command).startswith("OK")
        except OSError as error:
            log.warning("Befehl '%s' fehlgeschlagen: %s", command, error)
            return False

    # ---------------------------------------------------------- Ereignisse

    def attach(self):
        """Meldet sich für Ereignisse an und liest sie im Hintergrund mit."""
        if self._attached:
            return
        if not self.request("ATTACH").startswith("OK"):
            raise WpaError("wpa_supplicant nimmt keine Ereignis-Anmeldung an")
        self._attached = True
        self._reader = threading.Thread(target=self._read_events, daemon=True,
                                        name="wpa-events")
        self._reader.start()

    def _read_events(self):
        while not self._stop.is_set():
            try:
                with self._lock:
                    self._sock.settimeout(0.4)
                    data = self._sock.recv(8192).decode("utf-8", "replace")
            except socket.timeout:
                continue
            except OSError:
                break
            for line in data.splitlines():
                if line.startswith("<"):
                    # Prioritätsangabe wie "<3>" abtrennen
                    self.events.put(line.split(">", 1)[-1])
                    log.debug("Ereignis: %s", line)

    def wait_for_event(self, *needles: str, timeout: float = 30.0) -> Optional[str]:
        """Wartet auf ein Ereignis, das eine der Zeichenketten enthält."""
        import time
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            try:
                event = self.events.get(timeout=0.3)
            except queue.Empty:
                continue
            if any(needle in event for needle in needles):
                return event
        return None

    def close(self):
        self._stop.set()
        if self._reader:
            self._reader.join(1.0)
        try:
            self._sock.close()
        except OSError:
            pass
        self._cleanup()

    def _cleanup(self):
        try:
            os.unlink(self._local_path)
        except OSError:
            pass


def parse_key_values(text: str) -> Dict[str, str]:
    """Wandelt die übliche 'schlüssel=wert'-Ausgabe in ein Wörterbuch."""
    result = {}
    for line in text.splitlines():
        if "=" in line:
            key, value = line.split("=", 1)
            result[key.strip()] = value.strip()
    return result
