"""
Cifrado de credenciales: ida, vuelta y rotación.

El test que más importa acá no es que descifre bien —eso lo garantiza la
biblioteca— sino que **el valor guardado no contenga el original**. Es la clase
de error que no se nota nunca: todo funciona igual, hasta que alguien mira la
base y encuentra las claves en claro.
"""

import pytest
from cryptography.fernet import Fernet
from django.core.exceptions import ImproperlyConfigured

from apps.credentials.crypto import DecryptionError, decrypt, encrypt, rotate

SECRET = '-----BEGIN PRIVATE KEY-----\nmaterial-que-no-debe-verse\n-----END PRIVATE KEY-----'


def test_round_trip():
    assert decrypt(encrypt(SECRET)).decode() == SECRET


def test_the_encrypted_value_does_not_contain_the_original():
    encrypted = encrypt(SECRET)
    assert b'material-que-no-debe-verse' not in encrypted
    assert b'BEGIN PRIVATE KEY' not in encrypted


def test_two_encryptions_of_the_same_value_are_different():
    """
    Fernet incluye un vector de inicialización aleatorio.

    Importa para el producto: si dos credenciales iguales produjeran el mismo
    material guardado, comparar dos filas revelaría que son la misma clave sin
    necesidad de descifrarlas.
    """
    assert encrypt(SECRET) != encrypt(SECRET)


def test_a_foreign_key_cannot_decrypt(settings):
    encrypted = encrypt(SECRET)
    settings.SETTINGS_ENCRYPTION_KEY = Fernet.generate_key().decode()
    settings.SETTINGS_ENCRYPTION_KEY_FALLBACKS = []

    with pytest.raises(DecryptionError):
        decrypt(encrypted)


def test_rotation_with_the_previous_key_declared(settings):
    """
    Rotar es cifrar de nuevo con la clave vigente, pudiendo leer con la anterior.

    Sin declarar la anterior como respaldo, cambiar la clave dejaría ilegible
    todo lo guardado hasta ese momento.
    """
    previous = settings.SETTINGS_ENCRYPTION_KEY
    encrypted_with_the_previous = encrypt(SECRET)

    new = Fernet.generate_key().decode()
    settings.SETTINGS_ENCRYPTION_KEY = new
    settings.SETTINGS_ENCRYPTION_KEY_FALLBACKS = [previous]

    rotated = rotate(encrypted_with_the_previous)
    assert rotated != encrypted_with_the_previous
    assert decrypt(rotated).decode() == SECRET

    # Ya rotado, la clave anterior deja de hacer falta.
    settings.SETTINGS_ENCRYPTION_KEY_FALLBACKS = []
    assert decrypt(rotated).decode() == SECRET


def test_a_malformed_key_fails_at_startup_and_not_when_used(settings):
    settings.SETTINGS_ENCRYPTION_KEY = 'esto-no-es-una-clave-fernet'
    with pytest.raises(ImproperlyConfigured):
        encrypt(SECRET)
