"""
Tests pour le module de chiffrement.

Exigences : 17.4
"""

import base64
import json

import pytest
from cryptography.fernet import InvalidToken

from pipeline_mco_pmsi.security.encryption import AuditEncryptor, EncryptionKey


class TestEncryptionKey:
    """Tests pour EncryptionKey."""

    def test_generate_key(self):
        """Test génération de clé."""
        key = EncryptionKey.generate()
        assert key.key is not None
        assert len(key.key) > 0

    def test_generate_unique_keys(self):
        """Test que chaque génération produit une clé unique."""
        key1 = EncryptionKey.generate()
        key2 = EncryptionKey.generate()
        assert key1.key != key2.key

    def test_from_string(self):
        """Test création de clé depuis string."""
        key = EncryptionKey.generate()
        key_string = key.to_string()

        # Recréer depuis string
        key2 = EncryptionKey.from_string(key_string)
        assert key2.key == key.key

    def test_to_string(self):
        """Test conversion de clé en string."""
        key = EncryptionKey.generate()
        key_string = key.to_string()

        assert isinstance(key_string, str)
        assert len(key_string) > 0

    def test_roundtrip_string_conversion(self):
        """Test conversion aller-retour string."""
        key = EncryptionKey.generate()
        key_string = key.to_string()
        key2 = EncryptionKey.from_string(key_string)

        assert key.key == key2.key


class TestAuditEncryptor:
    """Tests pour AuditEncryptor."""

    @pytest.fixture
    def encryption_key(self):
        """Fixture pour clé de chiffrement."""
        return EncryptionKey.generate()

    @pytest.fixture
    def encryptor(self, encryption_key):
        """Fixture pour encryptor."""
        return AuditEncryptor(encryption_key)

    @pytest.fixture
    def sample_audit_data(self):
        """Fixture pour données d'audit de test."""
        return {
            "stay_id": "STAY001",
            "codes": [
                {"code": "I10", "label": "Hypertension essentielle", "type": "dp"},
                {"code": "E11.9", "label": "Diabète de type 2", "type": "das"},
            ],
            "timestamp": "2026-02-11T10:30:00",
            "user": "tim_user123",
        }

    def test_encrypt_audit_data(self, encryptor, sample_audit_data):
        """Test chiffrement de données d'audit."""
        encrypted = encryptor.encrypt_audit_data(sample_audit_data)

        assert isinstance(encrypted, bytes)
        assert len(encrypted) > 0
        # Les données chiffrées ne doivent pas contenir le texte original
        assert b"STAY001" not in encrypted
        assert b"I10" not in encrypted

    def test_decrypt_audit_data(self, encryptor, sample_audit_data):
        """Test déchiffrement de données d'audit."""
        encrypted = encryptor.encrypt_audit_data(sample_audit_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == sample_audit_data

    def test_roundtrip_encryption(self, encryptor, sample_audit_data):
        """Test chiffrement/déchiffrement aller-retour."""
        encrypted = encryptor.encrypt_audit_data(sample_audit_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == sample_audit_data
        assert decrypted["stay_id"] == "STAY001"
        assert len(decrypted["codes"]) == 2

    def test_encrypt_to_base64(self, encryptor, sample_audit_data):
        """Test chiffrement vers base64."""
        encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data)

        assert isinstance(encrypted_b64, str)
        assert len(encrypted_b64) > 0
        # Vérifier que c'est du base64 valide
        base64.b64decode(encrypted_b64)

    def test_decrypt_from_base64(self, encryptor, sample_audit_data):
        """Test déchiffrement depuis base64."""
        encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data)
        decrypted = encryptor.decrypt_from_base64(encrypted_b64)

        assert decrypted == sample_audit_data

    def test_roundtrip_base64(self, encryptor, sample_audit_data):
        """Test chiffrement/déchiffrement base64 aller-retour."""
        encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data)
        decrypted = encryptor.decrypt_from_base64(encrypted_b64)

        assert decrypted == sample_audit_data

    def test_different_keys_cannot_decrypt(self, sample_audit_data):
        """Test qu'une clé différente ne peut pas déchiffrer."""
        key1 = EncryptionKey.generate()
        key2 = EncryptionKey.generate()

        encryptor1 = AuditEncryptor(key1)
        encryptor2 = AuditEncryptor(key2)

        encrypted = encryptor1.encrypt_audit_data(sample_audit_data)

        # Tentative de déchiffrement avec une clé différente
        with pytest.raises(InvalidToken):
            encryptor2.decrypt_audit_data(encrypted)

    def test_tampered_data_fails_decryption(self, encryptor, sample_audit_data):
        """Test que des données altérées échouent au déchiffrement."""
        encrypted = encryptor.encrypt_audit_data(sample_audit_data)

        # Altérer les données
        tampered = encrypted[:-1] + b"X"

        with pytest.raises(InvalidToken):
            encryptor.decrypt_audit_data(tampered)

    def test_encrypt_empty_data(self, encryptor):
        """Test chiffrement de données vides."""
        empty_data = {}
        encrypted = encryptor.encrypt_audit_data(empty_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == empty_data

    def test_encrypt_complex_nested_data(self, encryptor):
        """Test chiffrement de données complexes imbriquées."""
        complex_data = {
            "stay_id": "STAY001",
            "metadata": {
                "admission_date": "2026-01-15",
                "discharge_date": "2026-01-20",
                "specialty": "Cardiologie",
            },
            "codes": [
                {
                    "code": "I10",
                    "evidence": [
                        {"document_id": "DOC001", "text": "HTA connue"},
                        {"document_id": "DOC002", "text": "Traitement antihypertenseur"},
                    ],
                }
            ],
            "versions": {
                "cim10": {"version": "2026", "hash": "abc123"},
                "ccam": {"version": "V81", "hash": "def456"},
            },
        }

        encrypted = encryptor.encrypt_audit_data(complex_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == complex_data
        assert decrypted["metadata"]["specialty"] == "Cardiologie"
        assert len(decrypted["codes"][0]["evidence"]) == 2

    def test_encrypt_unicode_data(self, encryptor):
        """Test chiffrement de données avec caractères Unicode."""
        unicode_data = {
            "stay_id": "STAY001",
            "diagnosis": "Œdème pulmonaire aigu",
            "notes": "Patient présentant dyspnée sévère",
            "symbols": "→ ≥ ≤ ± °",
        }

        encrypted = encryptor.encrypt_audit_data(unicode_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == unicode_data
        assert decrypted["diagnosis"] == "Œdème pulmonaire aigu"

    def test_encrypt_large_data(self, encryptor):
        """Test chiffrement de grandes quantités de données."""
        large_data = {
            "stay_id": "STAY001",
            "documents": [
                {
                    "document_id": f"DOC{i:03d}",
                    "content": "Lorem ipsum dolor sit amet " * 100,
                }
                for i in range(50)
            ],
        }

        encrypted = encryptor.encrypt_audit_data(large_data)
        decrypted = encryptor.decrypt_audit_data(encrypted)

        assert decrypted == large_data
        assert len(decrypted["documents"]) == 50

    def test_encrypted_data_is_different_each_time(self, encryptor, sample_audit_data):
        """Test que le chiffrement produit des résultats différents à chaque fois."""
        # Fernet utilise un IV aléatoire, donc même données = chiffrement différent
        encrypted1 = encryptor.encrypt_audit_data(sample_audit_data)
        encrypted2 = encryptor.encrypt_audit_data(sample_audit_data)

        # Les données chiffrées doivent être différentes
        assert encrypted1 != encrypted2

        # Mais les deux doivent déchiffrer vers les mêmes données
        decrypted1 = encryptor.decrypt_audit_data(encrypted1)
        decrypted2 = encryptor.decrypt_audit_data(encrypted2)
        assert decrypted1 == decrypted2 == sample_audit_data


class TestEncryptionIntegration:
    """Tests d'intégration pour le chiffrement."""

    def test_key_persistence_and_reuse(self):
        """Test persistance et réutilisation de clé."""
        # Générer une clé
        key = EncryptionKey.generate()
        key_string = key.to_string()

        # Simuler sauvegarde/chargement de clé
        # (dans un vrai système, on sauvegarderait dans un fichier sécurisé)

        # Chiffrer avec la clé originale
        encryptor1 = AuditEncryptor(key)
        data = {"test": "data"}
        encrypted = encryptor1.encrypt_audit_data(data)

        # Charger la clé depuis string et déchiffrer
        loaded_key = EncryptionKey.from_string(key_string)
        encryptor2 = AuditEncryptor(loaded_key)
        decrypted = encryptor2.decrypt_audit_data(encrypted)

        assert decrypted == data

    def test_multiple_encryptions_with_same_key(self):
        """Test multiples chiffrements avec la même clé."""
        key = EncryptionKey.generate()
        encryptor = AuditEncryptor(key)

        data_list = [
            {"stay_id": "STAY001", "code": "I10"},
            {"stay_id": "STAY002", "code": "E11.9"},
            {"stay_id": "STAY003", "code": "J18.9"},
        ]

        # Chiffrer toutes les données
        encrypted_list = [encryptor.encrypt_audit_data(data) for data in data_list]

        # Déchiffrer toutes les données
        decrypted_list = [
            encryptor.decrypt_audit_data(enc) for enc in encrypted_list
        ]

        assert decrypted_list == data_list
