"""
Adresse du serveur de signalisation (host + port), mémorisée localement pour éviter
de redéfinir MESHAMOTO_SERVER_HOST/_PORT à chaque lancement du client.

Rien de sensible ici (juste un nom d'hôte/IP et un port), pas besoin des mêmes
précautions de permissions que la clé WireGuard ou les mots de passe de réseaux.
"""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Optional

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


def parse_server_address(raw: str) -> Optional[tuple[str, int, bool]]:
    """
    Analyse `[https://]hôte[:port]` et retourne (hôte, port, tls), ou None si invalide.

    Le préfixe de schéma est le moyen pour l'utilisateur d'activer TLS : sans lui, le
    mot de passe du réseau circule en clair, ce qui n'est acceptable que sur un LAN de
    confiance. Logique pure, testable sans interface.

    Le port peut être omis QUAND un schéma est donné, auquel cas il en découle (443 en
    https/wss, 80 en http/ws) : `https://monreseau.duckdns.org` est la forme que tout le
    monde tape, et la refuser pour un `:443` sous-entendu n'apprend rien à personne. Sans
    schéma en revanche, on exige le port — le défaut du projet est 8765, pas 80, donc le
    deviner mènerait à un échec de connexion bien plus obscur que « adresse invalide ».
    """
    valeur = raw.strip()
    tls = False
    port_implicite = 0
    for prefixe, chiffre, defaut in (
        ("https://", True, 443), ("wss://", True, 443), ("http://", False, 80), ("ws://", False, 80),
    ):
        if valeur.lower().startswith(prefixe):
            tls, port_implicite = chiffre, defaut
            valeur = valeur[len(prefixe):]
            break
    valeur = valeur.rstrip("/")
    if port_implicite and ":" not in valeur:
        return (valeur, port_implicite, tls) if valeur else None
    host, _, port = valeur.rpartition(":")
    if not host or not port.isdigit():
        return None
    numero = int(port)
    if not 1 <= numero <= 65535:
        return None
    return host, numero, tls


def format_server_address(host: str, port: int, tls: bool) -> str:
    """Représentation affichée dans le champ, réversible par parse_server_address."""
    return f"{'https://' if tls else ''}{host}:{port}"


def load_server_address(path: Path) -> Optional[tuple[str, int]]:
    """Retourne (host, port) mémorisé, ou None si absent/corrompu."""
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return str(data["host"]), int(data["port"])
    except (OSError, json.JSONDecodeError, KeyError, ValueError, TypeError):
        logger.warning("Impossible de lire %s, adresse serveur mémorisée ignorée", path)
        return None


def load_server_tls(path: Path) -> bool:
    """TLS mémorisé pour la dernière adresse utilisée. Absent = False (compatibilité
    avec un réglage écrit par une version antérieure)."""
    if not path.exists():
        return False
    try:
        return bool(json.loads(path.read_text(encoding="utf-8")).get("tls", False))
    except (OSError, json.JSONDecodeError, TypeError):
        return False


def save_server_address(path: Path, host: str, port: int, tls: bool = False) -> None:
    """Écrit l'adresse en CONSERVANT les adresses locales déjà mémorisées : elles
    sont indexées par nom d'hôte et restent valables même si l'utilisateur change
    de serveur puis revient."""
    path.parent.mkdir(parents=True, exist_ok=True)
    donnees = {"host": host, "port": port, "tls": tls}
    existantes = _lire(path).get("lan_addresses")
    if existantes:
        donnees["lan_addresses"] = existantes
    path.write_text(json.dumps(donnees), encoding="utf-8")


def _lire(path: Path) -> dict:
    """Contenu du fichier, ou dict vide s'il est absent ou illisible."""
    try:
        contenu = json.loads(path.read_text(encoding="utf-8"))
        return contenu if isinstance(contenu, dict) else {}
    except (OSError, json.JSONDecodeError, TypeError):
        return {}


def load_lan_address(path: Path, host: str) -> Optional[str]:
    """
    Adresse du serveur sur le réseau local, mémorisée pour ce nom d'hôte.

    Indexée **par nom** et non globalement : quelqu'un qui utilise plusieurs
    serveurs (le sien chez lui, celui d'un ami) n'a aucune raison de voir
    l'adresse locale de l'un appliquée à l'autre — ce serait précisément le
    genre de substitution silencieuse qui envoie chercher une panne réseau
    inexistante.
    """
    adresses = _lire(path).get("lan_addresses")
    if not isinstance(adresses, dict):
        return None
    valeur = adresses.get(host)
    return str(valeur) if valeur else None


def save_lan_address(path: Path, host: str, lan_address: str) -> None:
    """Mémorise (ou efface, avec une chaîne vide) l'adresse locale de ce serveur."""
    path.parent.mkdir(parents=True, exist_ok=True)
    donnees = _lire(path)
    adresses = donnees.get("lan_addresses")
    if not isinstance(adresses, dict):
        adresses = {}
    if lan_address:
        adresses[host] = lan_address
    else:
        adresses.pop(host, None)
    donnees["lan_addresses"] = adresses
    path.write_text(json.dumps(donnees), encoding="utf-8")
