"""Utilidades compartidas por las pruebas."""

from __future__ import annotations

import base64

import pytest
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from django.contrib.auth import get_user_model
from django.core.cache import cache
from rest_framework.test import APIClient

from authentication.models.account import Account
from authentication.models.roles import Roles
from keys.models.access_token import AccessToken
from keys.models.api_keys import ApiKeys

User = get_user_model()


@pytest.fixture(autouse=True)
def limpiar_cache():
    """
    Vacia la cache entre pruebas.

    Los contadores de limite de peticiones viven en la cache; sin esto, una
    prueba que agota el limite hace fallar a la siguiente.
    """
    cache.clear()
    yield
    cache.clear()


@pytest.fixture
def usuario(db):
    return User.objects.create_user(username="servicio", password="x" * 20)


@pytest.fixture
def cuenta(usuario):
    rol = Roles.objects.create(description="Sistema")
    return Account.objects.create(
        name="Ventanilla Notarial", active=True, rol=rol, user=usuario
    )


@pytest.fixture
def clave_api(cuenta):
    """
    Clave de API activa.

    `issue()` devuelve la clave en claro una sola vez; se cuelga del registro
    para que las pruebas puedan usarla. En la base de datos solo queda el hash.
    """
    registro, clave = ApiKeys.issue(cuenta, active=True)
    registro.raw_key = clave
    return registro


@pytest.fixture
def api() -> APIClient:
    return APIClient()


@pytest.fixture
def autenticar(clave_api):
    """
    Devuelve una funcion que pone un token valido en el cliente.

    Los tokens de esta API son de un solo uso: se consumen al autenticar. Por
    eso hace falta uno nuevo antes de cada peticion.

    El token se emite a nombre de una clave concreta, asi que pasando `clave`
    se puede simular a otro cliente distinto.
    """

    def _autenticar(cliente: APIClient, clave: ApiKeys | None = None) -> APIClient:
        _, token = AccessToken.issue(clave or clave_api)
        cliente.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")
        return cliente

    return _autenticar


@pytest.fixture
def api_autenticada(api, autenticar):
    global _autenticador_actual
    _autenticador_actual = autenticar
    return autenticar(api)


#: Referencia al autenticador de la prueba en curso, para poder renovar el
#: token desde una prueba que hace varias peticiones seguidas.
_autenticador_actual = None


def _reautenticar(cliente):
    """Pone un token nuevo: los tokens son de un solo uso."""
    if _autenticador_actual is None:
        raise RuntimeError("Usa la fixture `api_autenticada` antes de reautenticar.")
    return _autenticador_actual(cliente)


@pytest.fixture
def claves_webhook():
    """
    Par de claves ECDSA P-256 para firmar eventos de webhook.

    Devuelve la privada y la publica en el formato en el que la muestra el
    panel de SendGrid: DER SubjectPublicKeyInfo en base64, sin lineas PEM.
    """
    privada = ec.generate_private_key(ec.SECP256R1())
    der = privada.public_key().public_bytes(
        Encoding.DER, PublicFormat.SubjectPublicKeyInfo
    )
    return privada, base64.b64encode(der).decode("ascii")


def firmar_webhook(privada, timestamp: str, cuerpo: bytes) -> str:
    """Firma un lote de eventos igual que lo hace SendGrid."""
    firma = privada.sign(
        timestamp.encode("utf-8") + cuerpo, ec.ECDSA(hashes.SHA256())
    )
    return base64.b64encode(firma).decode("ascii")


#: PNG de 1x1 pixel, valido, para las pruebas de adjuntos.
PNG_MINIMO = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
)
