"""Adatbázisban tárolt TITKOS értékek (pl. az SMTP-jelszó) szimmetrikus titkosítása.

MIÉRT KELL?
    A rendszeradmin felületről beállítható SMTP-jelszót az adatbázisban (rendszer_beallitasok
    tábla) tároljuk. Ha ott nyers szövegként állna, egy adatbázis-mentés, egy elveszett dump
    vagy egy másik fejlesztő DB-hozzáférése azonnal felfedné. Ezért az érték TITKOSÍTVA kerül
    a DB-be, a visszafejtéséhez szükséges kulcs viszont NEM az adatbázisban, hanem a szerver
    környezetében (.env) él - a kettő együtt kell a titok megismeréséhez.

HOGYAN MŰKÖDIK?
    - Algoritmus: Fernet (cryptography csomag) = AES-128-CBC + HMAC-SHA256, hitelesített
      titkosítás: ha valaki belenyúl a titkosított szövegbe, a visszafejtés hibát ad (nem ad
      hamis eredményt).
    - Kulcs-források (MultiFernet, ebben a sorrendben):
        1. CONFIG_ENCRYPTION_KEY a .env-ből (OPCIONÁLIS, dedikált kulcs) - ezzel TITKOSÍTUNK, ha
           meg van adva, és ezt próbáljuk elsőként visszafejtéskor is.
        2. A SECRET_KEY-ből HKDF-fel SZÁRMAZTATOTT kulcs - ez mindig rendelkezésre áll, így
           a funkció új .env-változó nélkül is működik (ezzel titkosítunk, ha nincs dedikált kulcs).
      A két kulcs egyszerre aktív visszafejtéskor, ezért ha utólag felveszik a
      CONFIG_ENCRYPTION_KEY-t, a korábban a származtatott kulccsal titkosított értékek
      továbbra is olvashatók maradnak (a következő mentéskor már az új kulccsal íródnak újra).
    - Fontos következmény: ha a SECRET_KEY-t megváltoztatják (és nincs dedikált kulcs), a
      mentett titkos értékek NEM fejthetők vissza többé - ilyenkor a visszafejt() None-t ad, és a
      rendszeradmin felület jelzi, hogy a jelszót újra be kell írni (az alkalmazás nem omlik össze).

MIT NE TEGYÉL?
    - Ne naplózd/írd ki a visszafejtett értéket (print, hibaüzenet, sablon-kontextus).
    - Ne cseréld le a SECRET_KEY-t vagy a CONFIG_ENCRYPTION_KEY-t anélkül, hogy tudnád: a már
      titkosított értékeket újra be kell írni.

Részletes leírás: docs/EMAIL_RENDSZER.md
"""
import base64
from functools import lru_cache
from typing import Optional

from cryptography.fernet import Fernet, InvalidToken, MultiFernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

from config import SECRET_KEY, CONFIG_ENCRYPTION_KEY

# A származtatott kulcs "címkéje": ugyanabból a SECRET_KEY-ből más célra (pl. egy későbbi
# funkcióhoz) más "info" értékkel más, egymástól független kulcs állítható elő. A "v1" a
# későbbi, esetleges algoritmus-csere megkülönböztetésére szolgál - NE módosítsd, mert minden
# eddig mentett érték visszafejthetetlenné válna.
_HKDF_INFO = b"emelog-config-encryption-v1"


def _szarmaztatott_kulcs() -> bytes:
    """A SECRET_KEY-ből HKDF-SHA256-tal előállít egy Fernet-kompatibilis (url-safe base64) kulcsot."""
    nyers = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=_HKDF_INFO,
    ).derive(SECRET_KEY.encode("utf-8"))
    return base64.urlsafe_b64encode(nyers)


@lru_cache(maxsize=1)
def _fernet() -> MultiFernet:
    """Összeállítja (és memóriában tartja) a kulcskészletet. Az ELSŐ kulccsal titkosítunk,
    visszafejtéskor mindegyiket sorban kipróbáljuk."""
    kulcsok = []

    dedikalt = (CONFIG_ENCRYPTION_KEY or "").strip()
    if dedikalt:
        try:
            kulcsok.append(Fernet(dedikalt.encode("utf-8")))
        except (ValueError, TypeError) as e:
            # Szándékosan hangos hiba: egy elrontott kulcsot nem szabad csendben figyelmen kívül
            # hagyni (különben a rendszer észrevétlenül egy másik kulccsal titkosítana tovább).
            raise RuntimeError(
                "A CONFIG_ENCRYPTION_KEY (.env) nem érvényes Fernet-kulcs. Generálj újat: "
                "python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\""
            ) from e

    kulcsok.append(Fernet(_szarmaztatott_kulcs()))
    return MultiFernet(kulcsok)


def titkosit(szoveg: str) -> str:
    """Titkosít egy szöveget; a visszatérési érték DB-ben tárolható (ASCII) karakterlánc.
    Üres bemenetre üres szöveget ad (nincs értelme az üres értéket titkosítani)."""
    if not szoveg:
        return ""
    return _fernet().encrypt(szoveg.encode("utf-8")).decode("ascii")


def visszafejt(token: str) -> Optional[str]:
    """Visszafejt egy, a titkosit()-tal készült szöveget.

    Visszatérés:
        - ""   : üres bemenet esetén,
        - None : ha a visszafejtés nem sikerül (megváltozott kulcs, sérült/átírt adat) -
                 a hívónak ezt kezelnie kell (pl. "írd be újra a jelszót" üzenettel),
        - str  : a visszafejtett szöveg.
    """
    if not token:
        return ""
    try:
        return _fernet().decrypt(token.encode("ascii")).decode("utf-8")
    except (InvalidToken, ValueError, UnicodeError):
        return None
