"""
Détermine la qualité affichée pour la connexion à un pair (vert/jaune/orange) à
partir d'un historique récent de pings et du statut relais — logique pure, aucune
dépendance GUI ni réseau (voir gui.py pour l'affichage, ping_client.py pour la mesure
elle-même). Réutilise l'historique de pings déjà collecté toutes les 5s par le
mécanisme existant plutôt que d'ajouter un nouvel échantillonnage réseau : la gigue et
la perte se calculent sur cette fenêtre glissante, sans overhead supplémentaire.

- Vert   : P2P direct, stable (pas de perte, gigue et latence sous les seuils).
- Jaune  : P2P direct, mais instable (perte partielle, gigue ou latence élevées) — le
           tunnel fonctionne, mais l'expérience de jeu risque d'être dégradée.
- Orange : relais UDP de secours actif — la connexion P2P directe a échoué, tout passe
           par le serveur de signalisation (latence plus élevée qu'en direct, normal).
- Aucune couleur (None) : pair injoignable sur tout l'historique récent, ou pas encore
  sondé — pas assez d'information pour distinguer P2P/relais, l'UI garde son affichage
  "injoignable" existant plutôt que d'inventer une couleur trompeuse. Ces deux cas
  partagent l'absence de couleur mais PAS le texte : ne pas les refondre en un seul.
"""

from __future__ import annotations

import statistics
from dataclasses import dataclass
from typing import Literal, Optional

Quality = Literal["green", "yellow", "orange"]

JITTER_THRESHOLD_MS = 15.0
LATENCY_THRESHOLD_MS = 120.0


@dataclass
class QualityResult:
    quality: Optional[Quality]
    description: str


def evaluate_quality(recent_latencies: list[Optional[float]], is_relay: bool) -> QualityResult:
    """
    `recent_latencies` : les dernières mesures de ping vers ce pair (None = échec),
    fenêtre glissante déjà maintenue par gui.py. `is_relay` : ce pair est-il
    actuellement basculé sur le relais UDP de secours (voir
    NetworkClient._relay_preferred_peer_ids) ?
    """
    if is_relay:
        return QualityResult("orange", "Relais UDP de secours actif (connexion directe indisponible)")

    if not recent_latencies:
        # « Jamais sondé » n'est pas « ne répond pas ». La distinction vient du
        # point 47, où un service de jeu jamais interrogé et un service muet
        # étaient rendus par le même libellé : affirmer le silence de quelqu'un
        # qu'on n'a pas encore appelé, c'est dire plus que ce qu'on sait.
        return QualityResult(None, "Pas encore sondé")

    successes = [v for v in recent_latencies if v is not None]
    if not successes:
        # Depuis le point 71, le salon essaie l'écho applicatif PUIS le ping ICMP :
        # arriver ici veut dire que les DEUX se taisent. Les causes possibles sont
        # de familles opposées, et rien ici ne permet de trancher entre elles — on
        # les nomme sans en choisir une, et on renvoie vers le seul outil qui sait
        # le faire. Le panneau de diagnostic teste un port en TCP, méthode qui a
        # tranché deux fois en conditions réelles (points 65 et 67bis) : une
        # poignée de main réussie prouve que le tunnel porte du trafic dans les
        # deux sens, donc que le silence vient du filtrage et non de la liaison.
        return QualityResult(
            None,
            "Pair injoignable : ni l'écho de vivacité ni le ping ICMP ne répondent. "
            "Pair éteint, tunnel tombé de son côté, ou pare-feu qui jette les deux "
            "(vu sur Windows et avec ufw). Le bouton Diagnostic teste un port pour "
            "distinguer les deux cas.",
        )

    loss_ratio = 1 - (len(successes) / len(recent_latencies))
    jitter = statistics.stdev(successes) if len(successes) >= 2 else 0.0
    mean_latency = statistics.mean(successes)

    if loss_ratio == 0 and jitter < JITTER_THRESHOLD_MS and mean_latency < LATENCY_THRESHOLD_MS:
        return QualityResult("green", f"Connexion directe stable ({mean_latency:.0f} ms)")

    reasons = []
    if loss_ratio > 0:
        reasons.append(f"perte {loss_ratio:.0%}")
    if jitter >= JITTER_THRESHOLD_MS:
        reasons.append(f"gigue {jitter:.0f} ms")
    if mean_latency >= LATENCY_THRESHOLD_MS:
        reasons.append(f"latence {mean_latency:.0f} ms")
    return QualityResult("yellow", "Connexion directe instable (" + ", ".join(reasons) + ")")
