"""
Pruebas del hasheo de las claves de API.

Antes, `ApiKeys.key` guardaba la clave en claro y la autenticacion era un
`objects.get(key=apikey)`. Cualquier lectura de la base de datos entregaba
credenciales utilizables, y el `__str__` del modelo devolvia la clave entera,
asi que aparecia en el listado del admin.
"""

from __future__ import annotations

import pytest

from keys.models.api_keys import ApiKeys, hash_secret, split_key

pytestmark = pytest.mark.django_db


# ---------------------------------------------------------------------------
# Almacenamiento
# ---------------------------------------------------------------------------


def test_la_clave_en_claro_no_se_guarda(cuenta):
    registro, clave = ApiKeys.issue(cuenta)
    _, secreto = split_key(clave)

    guardado = ApiKeys.objects.get(pk=registro.pk)

    assert guardado.key_hash == hash_secret(secreto)
    assert secreto not in guardado.key_hash
    # El prefijo si es publico: sirve para localizar el registro.
    assert guardado.key_prefix in clave


def test_la_tabla_no_tiene_columna_de_clave():
    """La columna `key` se elimina en la migracion 0005."""
    columnas = {campo.name for campo in ApiKeys._meta.fields}
    assert "key" not in columnas
    assert {"key_prefix", "key_hash"} <= columnas


def test_el_texto_del_modelo_no_revela_la_clave(cuenta):
    registro, clave = ApiKeys.issue(cuenta)
    _, secreto = split_key(clave)

    assert secreto not in str(registro)
    assert registro.account.name in str(registro)


def test_dos_claves_nunca_coinciden(cuenta):
    _, primera = ApiKeys.issue(cuenta)
    _, segunda = ApiKeys.issue(cuenta)
    assert primera != segunda


# ---------------------------------------------------------------------------
# Verificacion
# ---------------------------------------------------------------------------


def test_una_clave_valida_autentica(cuenta):
    _, clave = ApiKeys.issue(cuenta)
    assert ApiKeys.authenticate(clave) is not None


@pytest.mark.parametrize(
    "clave",
    [
        "",
        "cualquier-cosa",
        "ApiKey_solodospartes",
        "ApiKey__secreto",
        "ApiKey_prefijo_",
        "OtraEtiqueta_prefijo_secreto",
    ],
)
def test_las_claves_mal_formadas_se_rechazan(clave):
    assert ApiKeys.authenticate(clave) is None


def test_conocer_el_prefijo_no_basta(cuenta):
    """El prefijo es publico; sin el secreto correcto no sirve."""
    registro, _ = ApiKeys.issue(cuenta)
    assert ApiKeys.authenticate(f"ApiKey_{registro.key_prefix}_inventado") is None


def test_una_clave_inactiva_no_autentica(cuenta):
    registro, clave = ApiKeys.issue(cuenta, active=False)
    assert ApiKeys.authenticate(clave) is None


def test_una_clave_revocada_no_autentica(cuenta):
    registro, clave = ApiKeys.issue(cuenta)
    assert ApiKeys.authenticate(clave) is not None

    registro.revoke()
    assert ApiKeys.authenticate(clave) is None


def test_rotar_invalida_la_clave_anterior(cuenta):
    registro, anterior = ApiKeys.issue(cuenta)
    nueva = registro.rotate()

    assert ApiKeys.authenticate(anterior) is None
    assert ApiKeys.authenticate(nueva) is not None


def test_se_registra_el_ultimo_uso(api, clave_api):
    assert clave_api.last_used_at is None

    api.get("/auth/v1/token/", HTTP_X_API_KEY=clave_api.raw_key)

    clave_api.refresh_from_db()
    assert clave_api.last_used_at is not None


def test_la_clave_queda_asociada_a_la_peticion(api, clave_api):
    """
    La auditoria necesita saber que credencial pidio el envio.

    `ApiKeyAuthentication` deja el registro en `request.api_key_record`.
    """
    from rest_framework.test import APIRequestFactory

    from core.authentications.api_key_auth import ApiKeyAuthentication

    peticion = APIRequestFactory().get("/", HTTP_X_API_KEY=clave_api.raw_key)
    usuario, _ = ApiKeyAuthentication().authenticate(peticion)

    assert usuario == clave_api.account.user
    assert peticion.api_key_record.pk == clave_api.pk
