"""
Clé de licence et jeton d'habilitation mémorisés localement.

**Deux valeurs de nature très différente dans le même fichier**, et il faut savoir
laquelle est laquelle :

  - la CLÉ est le porteur durable, saisi une fois par l'utilisateur. La perdre oblige
    à la ressaisir, donc à la retrouver dans le courriel ou la page d'inscription ;
  - le JETON est un cache, valable quelques jours. Le perdre ne coûte qu'une
    réactivation silencieuse auprès du service de licence.

Ce fichier n'existe que sur les machines qui se servent d'un serveur GÉRÉ. Quelqu'un
qui héberge son propre serveur n'a aucune clé, et le client n'en demande jamais —
c'est l'invariant posé côté serveur dans `habilitation.py`.

Le jeton est mis en cache pour une raison précise, et pas pour économiser une
requête : il permet de se connecter **même si le service de licence est en panne**.
Voir l'en-tête de `server/licence_common.py` pour le raisonnement complet.
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from pathlib import Path

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

_CLE = "cle"
_JETON = "jeton"


@dataclass(frozen=True)
class Licence:
    """Ce que la machine sait de son habilitation. Les deux champs sont facultatifs."""

    cle: str = ""
    jeton: str = ""

    @property
    def configuree(self) -> bool:
        return bool(self.cle)


def charger(path: Path) -> Licence:
    """
    Licence mémorisée, ou une licence vide si le fichier est absent ou illisible.

    **Un fichier corrompu ne doit jamais empêcher le client de démarrer** : le repli
    est « aucune licence », donc le comportement d'un auto-hébergeur. Au pire
    l'utilisateur ressaisit sa clé ; à l'inverse, lever ici bloquerait au lancement
    quelqu'un qui n'a peut-être même pas besoin d'habilitation.
    """
    if not path.exists():
        return Licence()
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return Licence(
            cle=str(data.get(_CLE, "") or ""), jeton=str(data.get(_JETON, "") or "")
        )
    except (OSError, json.JSONDecodeError, TypeError, ValueError):
        logger.warning("Impossible de lire %s, licence ignorée", path)
        return Licence()


def enregistrer(path: Path, licence: Licence) -> None:
    """
    Écrit clé et jeton.

    Le fichier est en 0600 sur les systèmes qui le permettent : la clé est un porteur
    d'autorisation, elle n'a pas à être lisible par les autres comptes de la machine.
    Sous Windows `chmod` est sans effet réel, et c'est acceptable — le fichier vit
    déjà dans `%APPDATA%` de l'utilisateur.
    """
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps({_CLE: licence.cle, _JETON: licence.jeton}), encoding="utf-8"
    )
    try:
        path.chmod(0o600)
    except OSError:  # système de fichiers qui ne gère pas les permissions
        pass


def enregistrer_cle(path: Path, cle: str) -> Licence:
    """
    Change la clé et **jette le jeton en cache**.

    Le jeton appartient à l'ancienne clé : le conserver ferait continuer à travailler
    sous une habilitation que l'utilisateur vient de remplacer, et masquerait une
    saisie fautive jusqu'à l'expiration du cache — plusieurs jours plus tard, quand
    plus personne ne ferait le lien avec le changement de clé.
    """
    licence = Licence(cle=cle.strip(), jeton="")
    enregistrer(path, licence)
    return licence


def enregistrer_jeton(path: Path, jeton: str) -> None:
    """Met à jour le seul jeton, en conservant la clé déjà mémorisée."""
    actuelle = charger(path)
    enregistrer(path, Licence(cle=actuelle.cle, jeton=jeton))


def oublier(path: Path) -> None:
    """Efface tout — l'utilisateur repasse en auto-hébergement."""
    try:
        path.unlink()
    except FileNotFoundError:
        pass
    except OSError:
        logger.warning("Impossible d'effacer %s", path)
