import base64
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

from config.constants import Urls
from tools.http_client import http_post, ApiError


class GetServerTokenForDiscord:
    derived_key: bytes | None = None

    @staticmethod
    def authenticate(server: str | None = Urls.API_URL.value) -> str:
        if server is None:
            server = Urls.API_URL.value

        # ==========================
        # Génération clé ECDH client
        # ==========================
        client_private = ec.generate_private_key(ec.SECP256R1())
        client_public = client_private.public_key()

        client_pub_pem = client_public.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )

        # ==========================
        # AUTH
        # ==========================
        try:
            auth = http_post(
                f"{server}/api_v2/auth",
                json={"client_pub": base64.b64encode(client_pub_pem).decode()},
            ).json()
        except ApiError:
            raise
        except ValueError as exc:
            raise ApiError("Réponse JSON invalide lors de l'authentification.", url=f"{server}/api_v2/auth") from exc

        server_pub_b64 = auth.get("server_pub")
        session_id = auth.get("session_id")
        if not server_pub_b64 or not session_id:
            raise ApiError("Réponse d'authentification invalide : données manquantes.", url=f"{server}/api_v2/auth")

        server_pub = serialization.load_pem_public_key(base64.b64decode(server_pub_b64))
        shared_key = client_private.exchange(ec.ECDH(), server_pub)

        GetServerTokenForDiscord.derived_key = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=b"fivem-private-server"
        ).derive(shared_key)

        return auth["session_id"]

    @staticmethod
    def get_token(session_id: bytes, server: str | None = Urls.API_URL.value) -> bytes:
        # ==========================
        # DISCORD TOKEN
        # ==========================
        if server is None:
            server = Urls.API_URL.value

        if GetServerTokenForDiscord.derived_key is None:
            raise ApiError("Clé dérivée manquante : appelez authenticate() avant get_token().")

        try:
            download = http_post(
                f"{server}/api_v2/tkn_auth",
                headers={"x-session-id": session_id},
            ).json()
        except ApiError:
            raise
        except ValueError as exc:
            raise ApiError("Réponse JSON invalide lors de la récupération du token.",
                           url=f"{server}/api_v2/tkn_auth") from exc

        nonce_b64 = download.get("nonce")
        encrypted_data_b64 = download.get("data")
        if not nonce_b64 or not encrypted_data_b64:
            raise ApiError("Réponse de token invalide : nonce ou data manquant.", url=f"{server}/api_v2/tkn_auth")

        nonce = base64.b64decode(nonce_b64)
        encrypted_data = base64.b64decode(encrypted_data_b64)

        aesgcm = AESGCM(GetServerTokenForDiscord.derived_key) # type: ignore[arg-type]
        return aesgcm.decrypt(nonce, encrypted_data, None)

    @staticmethod
    def register_discord_user(
        discord_user_id: str, session_id: str, server: str | None = Urls.API_URL.value) -> bool:
        if server is None:
            server = Urls.API_URL.value

        try:
            registration_data = http_post(
                f"{server}/api_v2/connection/register",
                headers={"x-session-id": session_id},
                json={"discord_id": discord_user_id},
            ).json()
        except ApiError:
            raise
        except ValueError as exc:
            raise ApiError(
                "Réponse JSON invalide lors de l'enregistrement Discord.",
                url=f"{server}/api_v2/connection/register",
            ) from exc

        return bool(registration_data.get("success", False))
