"""
Petite factorisation partagée par wireguard_backend_linux.py et firewall_backend_linux.py :
exécute une commande nécessitant CAP_NET_ADMIN, en tentant pkexec (prompt graphique)
si le processus courant n'est pas déjà root.
"""

from __future__ import annotations

import logging
import os
import shutil
import subprocess
import threading

logger = logging.getLogger("meshamoto-client.linux_privilege")

# polkit n'accepte qu'UNE demande d'autorisation à la fois : un second pkexec lancé
# pendant qu'une invite est encore affichée est refusé immédiatement (~30 ms) avec
# "Error executing command as another user: Not authorized", sans même montrer de
# boîte de dialogue. Or les appels privilégiés partent de plusieurs threads : le timer
# de debounce de la liste de pairs (network_client), le thread worker de la GUI
# (isolation, DNS, nettoyage à la fermeture)... Un rafraîchissement de pairs qui tombe
# pendant que l'utilisateur tape son mot de passe suffisait donc à faire échouer le
# montage du tunnel, avec un message qui laisse croire à un refus d'authentification.
# Ce verrou global sérialise tous les appels privilégiés du processus : le second
# attend la fin du premier au lieu d'être rejeté.
_PRIVILEGE_LOCK = threading.Lock()


def run_privileged(
    *args: str, timeout: float = 120.0, stdin_data: str | None = None
) -> subprocess.CompletedProcess:
    """
    Exécute `args` avec les privilèges root.

    - Si déjà root (os.geteuid() == 0) : exécution directe.
    - Sinon, si `pkexec` est disponible (polkit) : élève uniquement cette commande,
      avec une invite graphique de mot de passe — la GUI elle-même ne tourne pas en root.
    - Sinon : lève PermissionError avec un message actionnable (relancer via sudo).

    `stdin_data` est transmis sur l'entrée standard de la commande. Sert à passer un
    secret — la clé privée WireGuard — au composant privilégié : un argument de ligne de
    commande serait lisible par n'importe quel utilisateur de la machine via
    /proc/<pid>/cmdline, une entrée standard ne l'est pas.

    Les appels sont sérialisés à l'échelle du processus (voir _PRIVILEGE_LOCK) : deux
    threads qui demandent des privilèges en même temps sont mis en file d'attente au
    lieu de se faire refuser par polkit.
    """
    if os.geteuid() == 0:
        cmd = list(args)
    elif shutil.which("pkexec"):
        cmd = ["pkexec", *args]
    else:
        raise PermissionError(
            "Privilèges root requis pour cette opération. Relancez le client avec "
            "'sudo python3 gui.py', ou installez 'polkit' pour une invite graphique (pkexec)."
        )

    # Le verrou est pris AVANT le log : deux threads qui se présentent en même temps
    # produisent alors deux lignes "Exécution" séparées par la durée réelle de la
    # première commande, ce qui rend la sérialisation lisible dans le journal.
    with _PRIVILEGE_LOCK:
        logger.info("Exécution : %s", " ".join(cmd))
        result = subprocess.run(
            cmd, capture_output=True, text=True, timeout=timeout, input=stdin_data,
        )
    if result.returncode != 0:
        logger.error("Commande échouée (code %d) : %s", result.returncode, result.stderr)
    return result
