"""Chiffrement applicatif des secrets d'intégration (architecture §7 « BYOK », §9).

La spec d'architecture a tranché le mécanisme en décrivant le refresh token GSC : « chiffré
(Fernet, clé en env) et stocké dans DATA ». Les clés d'API BYOK (Mangools ici, DataForSEO
ensuite) sont exactement le même objet — un secret appartenant au client, que l'api doit
pouvoir relire pour appeler le fournisseur, et que personne ne doit pouvoir lire dans un dump
de la base. On reprend donc le même mécanisme, sans en inventer un second.

**Fernet et pas « le chiffrement de Postgres »** : le secret doit rester illisible dans une
sauvegarde, dans une réplique et dans un `pg_dump` — donc chiffré *avant* d'atteindre la base,
avec une clé qui ne vit que dans l'environnement du process api. `CREDENTIALS_KEY` est donc
aussi précieuse que la base elle-même : la perdre rend les intégrations illisibles (les
clients doivent ressaisir leur clé), la divulguer avec un dump rend le chiffrement inutile.

La clé est **optionnelle dans les réglages** et exigée *au premier usage* : un déploiement ou
un worktree qui ne touche pas aux intégrations doit démarrer sans elle, et celui qui y touche
doit échouer avec une phrase qui dit quoi faire — jamais un `KeyError` opaque, jamais un
secret écrit en clair « en attendant ».
"""

from cryptography.fernet import Fernet, InvalidToken

from app.core.config import get_settings
from app.core.errors import AppError

# Ce qu'on montre à l'utilisateur à la place de sa clé (§ « la clé n'est jamais renvoyée en
# clair »). Assez pour reconnaître *laquelle* de ses clés est branchée, trop peu pour la
# rejouer : les quatre derniers caractères et rien d'autre.
HINT_TAIL = 4
_HINT_MASK = "•" * 6


class CredentialsKeyMissing(RuntimeError):
    """`CREDENTIALS_KEY` absente de l'environnement : erreur d'exploitation, pas d'entrée."""


def _fernet() -> Fernet:
    key = get_settings().credentials_key
    if not key:
        raise CredentialsKeyMissing(
            "CREDENTIALS_KEY absente : les intégrations BYOK ne peuvent ni chiffrer ni "
            "déchiffrer de clé. Générer une clé avec "
            '`python -c "from cryptography.fernet import Fernet; '
            "print(Fernet.generate_key().decode())\"` et la poser dans l'environnement."
        )
    try:
        return Fernet(key)
    except (ValueError, TypeError) as exc:
        raise CredentialsKeyMissing(
            "CREDENTIALS_KEY invalide : une clé Fernet est 32 octets encodés en base64 url-safe."
        ) from exc


def encrypt(secret: str) -> str:
    """Chiffre un secret. Le texte rendu est ce qui va en base, jamais le secret lui-même."""
    return _fernet().encrypt(secret.encode()).decode()


def decrypt(token: str) -> str:
    """Déchiffre un secret stocké.

    Un jeton illisible n'est pas une erreur de l'utilisateur mais un signe de rotation de clé
    (ou de restauration d'une base sans son `CREDENTIALS_KEY`) : le 409 le dit franchement et
    invite à ressaisir la clé, plutôt que de laisser un 500 opaque au bout d'un job.
    """
    try:
        return _fernet().decrypt(token.encode()).decode()
    except InvalidToken as exc:
        raise AppError(
            409,
            "Conflict",
            "La clé enregistrée est illisible (clé de chiffrement changée ou base restaurée "
            "sans elle) : ressaisissez-la.",
        ) from exc


def hint(secret: str) -> str:
    """Empreinte affichable d'un secret : `••••••cd12`. Jamais réversible, jamais loguée entière.

    Stockée en clair à côté du secret chiffré pour que l'écran Intégrations affiche « quelle
    clé » sans avoir à déchiffrer quoi que ce soit à chaque rendu de page.
    """
    tail = secret.strip()[-HINT_TAIL:]
    return f"{_HINT_MASK}{tail}"
