"""
Distingue « MON tunnel est mort » de « CE pair est injoignable ».

Sans cette distinction, une panne locale se présentait comme une panne de
chaque pair : tous les pings échouaient en même temps, et au bout de
`RELAY_FALLBACK_FAILURE_THRESHOLD` cycles le client demandait un relais de
secours **pour chaque pair**. Trois conséquences, toutes mauvaises :

  - le relais ne peut rien réparer, puisque c'est notre propre interface qui
    est morte ;
  - chaque allocation ajoute un `relay_port` à la liste de pairs, donc
    **réinstalle le tunnel chez tout le monde** — pathologie déjà payée aux
    points 26 et 28 ;
  - l'utilisateur lit « connexion instable » alors que la cause est locale et
    parfaitement diagnosticable.

C'est le scénario du driver cassé par une mise à jour Windows, ou du port
d'écoute volé par un autre tunnel (point 32). L'état de l'interface se lit sans
aucun privilège sur les deux OS (`ip link show` / `sc query`), donc ce
diagnostic est gratuit.

Logique pure : aucun appel réseau ni système ici, l'état de l'interface est
fourni par l'appelant.
"""

from __future__ import annotations

from dataclasses import dataclass

# Verdicts possibles.
NORMAL = "normal"
TUNNEL_MORT = "tunnel_mort"
PAIRS_INJOIGNABLES = "pairs_injoignables"


@dataclass(frozen=True)
class Verdict:
    """Diagnostic d'un cycle de ping, formulé pour l'utilisateur."""

    etat: str
    message: str = ""

    @property
    def relais_pertinent(self) -> bool:
        """Vrai si demander un relais de secours peut encore servir à quelque
        chose. Faux quand la panne est chez nous : le relais ne ressuscite pas
        une interface locale morte."""
        return self.etat != TUNNEL_MORT


def evaluer(
    nombre_de_pairs: int,
    pairs_injoignables: int,
    tunnel_actif: bool,
    changement_recent: bool = False,
) -> Verdict:
    """
    Verdict à partir d'un cycle de pings.

    `changement_recent` reprend la fenêtre de grâce du point 28 : pendant une
    réinstallation, l'interface n'existe pas et TOUS les pings échouent, ce qui
    est parfaitement normal. Conclure à la panne pendant cette fenêtre
    déclencherait une réparation... juste après une réparation, donc une boucle.
    """
    if changement_recent:
        return Verdict(NORMAL)

    if nombre_de_pairs == 0:
        # Aucun pair à mesurer : on ne peut rien conclure. Surtout ne pas
        # « réparer » un tunnel qui n'a simplement personne à joindre.
        return Verdict(NORMAL)

    tous_muets = pairs_injoignables >= nombre_de_pairs

    if not tunnel_actif and tous_muets:
        return Verdict(
            TUNNEL_MORT,
            "Le tunnel Meshamoto n'est plus actif sur cette machine : aucun pair "
            "n'est joignable. Tentative de remise en service...",
        )

    if not tunnel_actif:
        # Interface absente mais des pairs répondent encore : incohérent, donc
        # on ne touche à rien. C'est typiquement une lecture d'état faite au
        # milieu d'une réinstallation que la grâce n'a pas couverte.
        return Verdict(NORMAL)

    if tous_muets and nombre_de_pairs > 1:
        # L'interface est là et pourtant plus personne ne répond. Le relais
        # reste pertinent (NAT symétrique des deux côtés, panne du réseau
        # local), mais ça vaut la peine de le dire : avec un seul pair on ne
        # peut pas distinguer ce cas d'un pair simplement éteint.
        #
        # **Le message disait « vérifiez votre connexion Internet », et c'était
        # FAUX dans le cas le plus intéressant.** Ce verdict n'est atteint que si
        # la liste de pairs nous parvient, donc si la WebSocket de signalisation
        # est vivante : Internet fonctionne, par construction. La signature
        # « signalisation vivante + tous les pairs muets » est au contraire celle
        # d'un réseau qui bloque l'UDP, et c'est exactement ce que le mode réseau
        # restreint sert à contourner (point 108). Envoyer vérifier Internet fait
        # chercher au mauvais endroit — ce que le point 62 a déjà coûté une fois.
        #
        # C'est aussi ce qui rend l'option TROUVABLE : un réglage manuel que rien
        # ne nomme au moment où il servirait n'existe pas pour l'utilisateur.
        return Verdict(
            PAIRS_INJOIGNABLES,
            "Plus aucun pair ne répond alors que le tunnel est actif et que la "
            "connexion au serveur fonctionne. Si vous êtes sur un Wi-Fi d'hôtel, "
            "un réseau d'entreprise ou derrière un portail captif, ce réseau "
            "bloque probablement l'UDP : essayez le « Mode réseau restreint ».",
        )

    return Verdict(NORMAL)
