"""
Localisation du fichier de favoris Steam et écriture, autour de la logique pure de
steam_favorites.py.

Branche `sys.platform` en ligne plutôt que backends séparés : même choix que
file_browser.py pour une fonctionnalité qui se réduit à des chemins et à une détection
de processus. La règle des backends du projet vise les fonctionnalités dont le
COMPORTEMENT diffère par OS (tunnel, pare-feu, fichier hosts), pas celles qui se
contentent de chercher un fichier ailleurs.

**Steam doit être fermé pendant l'écriture.** Il conserve son état en mémoire et réécrit
`serverbrowser_hist.vdf` en quittant : une modification faite pendant qu'il tourne serait
purement et simplement perdue. `is_steam_running()` permet de le dire à l'utilisateur au
lieu de le laisser constater que rien ne s'est passé.

Validé face à un vrai Steam le 2026-07-30 — y compris la déduplication par chemin résolu
(deux racines détectées sur cette machine, un seul fichier retenu) et la préservation
d'un favori de l'utilisateur à travers une mise à jour puis un retrait. Voir
steam_favorites.py pour ce que la réécriture par Steam fait réellement (mesuré, plus
nuancé qu'anticipé).
"""

from __future__ import annotations

import logging
import os
import subprocess
import sys
from pathlib import Path

import steam_favorites

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

# `7` est l'identifiant applicatif du navigateur de serveurs Steam lui-même ; `remote`
# est le dossier synchronisé par Steam Cloud (voir remotecache.vdf, qui en mémorise
# taille et SHA — une modification faite Steam fermé y est vue comme locale et remonte).
_RELATIVE_PATH = Path("7") / "remote" / "serverbrowser_hist.vdf"


def _userdata_roots() -> list[Path]:
    """Emplacements possibles du dossier userdata de Steam, selon l'OS et le mode
    d'installation (natif, Flatpak, chemin personnalisé)."""
    if sys.platform == "win32":
        candidats = [
            Path(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")) / "Steam" / "userdata",
            Path(os.environ.get("ProgramFiles", r"C:\Program Files")) / "Steam" / "userdata",
        ]
        try:  # chemin réellement enregistré par l'installateur, s'il diffère
            import winreg

            with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam") as cle:
                chemin, _ = winreg.QueryValueEx(cle, "SteamPath")
                candidats.insert(0, Path(chemin) / "userdata")
        except OSError:
            pass
        return [c for c in candidats if c.is_dir()]

    maison = Path.home()
    candidats = [
        maison / ".local" / "share" / "Steam" / "userdata",
        maison / ".steam" / "steam" / "userdata",
        maison / ".var" / "app" / "com.valvesoftware.Steam" / ".local" / "share" / "Steam" / "userdata",
    ]
    return [c for c in candidats if c.is_dir()]


def find_favorites_files() -> list[Path]:
    """
    Fichiers de favoris de tous les comptes Steam trouvés.

    Plusieurs comptes sur une machine sont normaux (compte personnel et compte de test) :
    on écrit dans tous, plutôt que d'obliger l'utilisateur à choisir. Les fichiers
    inexistants sont inclus — Steam ne les crée qu'après un premier usage du navigateur,
    et écrire un fichier neuf fonctionne.
    """
    trouves: list[Path] = []
    vus: set[Path] = set()
    for racine in _userdata_roots():
        for compte in sorted(racine.iterdir()):
            if not compte.is_dir() or compte.name == "0":
                continue
            chemin = compte / _RELATIVE_PATH
            if not chemin.parent.is_dir():
                continue
            # Déduplication par chemin RÉSOLU : `~/.steam/steam` est un lien symbolique
            # vers `~/.local/share/Steam` sur une installation Linux standard, donc les
            # deux racines désignent le même fichier. Sans ça on l'écrirait deux fois.
            reel = chemin.resolve()
            if reel in vus:
                continue
            vus.add(reel)
            trouves.append(chemin)
    return trouves


def is_steam_running() -> bool:
    """
    Vrai si Steam tourne — auquel cas toute écriture serait annulée à sa fermeture.

    Sur Linux, `~/.steam/steam.pid` est fiable et ne coûte aucun sous-processus ; on
    vérifie que le PID correspond à un processus vivant, un fichier périmé subsistant
    après un arrêt brutal.
    """
    if sys.platform == "win32":
        try:
            sortie = subprocess.run(
                ["tasklist", "/FI", "IMAGENAME eq steam.exe", "/NH"],
                capture_output=True, text=True, timeout=5,
                creationflags=subprocess.CREATE_NO_WINDOW,
            ).stdout
            return "steam.exe" in sortie.lower()
        except (OSError, subprocess.SubprocessError):
            return False

    fichier_pid = Path.home() / ".steam" / "steam.pid"
    try:
        return Path(f"/proc/{int(fichier_pid.read_text().strip())}").is_dir()
    except (OSError, ValueError):
        return False


def update_favorites(
    peers: dict[str, str],
    virtual_cidr: str,
    game_port: int,
    peer_ports: dict[str, int] | None = None,
) -> int:
    """
    Écrit les pairs dans les favoris de chaque compte Steam. Retourne le nombre de
    fichiers modifiés — 0 si aucun n'existe ou si rien n'a changé.

    `peer_ports` porte le port du service hébergé annoncé par un pair, qui l'emporte sur
    `game_port` (voir steam_favorites.build_updated_favorites).

    N'écrit pas quand le contenu est identique : les diffusions de liste de pairs sont
    fréquentes et réécrire un fichier de Steam pour rien n'a aucun intérêt (même
    raisonnement que pour le fichier hosts).
    """
    modifies = 0
    for chemin in find_favorites_files():
        try:
            existant = chemin.read_text(encoding="utf-8") if chemin.exists() else ""
            nouveau = steam_favorites.build_updated_favorites(
                existant, peers, virtual_cidr, game_port, peer_ports=peer_ports
            )
            if nouveau == existant:
                continue
            chemin.write_text(nouveau, encoding="utf-8")
            modifies += 1
        except OSError:
            logger.exception("Ecriture des favoris Steam impossible : %s", chemin)
    return modifies


def clear_favorites(virtual_cidr: str) -> int:
    """Retire nos entrées de tous les comptes. Retourne le nombre de fichiers modifiés."""
    modifies = 0
    for chemin in find_favorites_files():
        if not chemin.exists():
            continue
        try:
            existant = chemin.read_text(encoding="utf-8")
            nouveau = steam_favorites.build_cleared_favorites(existant, virtual_cidr)
            if nouveau == existant:
                continue
            chemin.write_text(nouveau, encoding="utf-8")
            modifies += 1
        except OSError:
            logger.exception("Nettoyage des favoris Steam impossible : %s", chemin)
    return modifies
