"""
Tests unitaires pour le système de contrôle d'accès.

Ces tests vérifient l'authentification, l'autorisation RBAC,
et la gestion des sessions.
"""

import time
from datetime import datetime, timedelta

import pytest

from pipeline_mco_pmsi.security import AccessControl, Role, Permission


@pytest.fixture
def access_control():
    """Crée une instance d'AccessControl pour les tests."""
    return AccessControl(session_duration_hours=1)


@pytest.fixture
def sample_users(access_control):
    """Crée des utilisateurs de test."""
    tim = access_control.create_user(
        username="tim_user",
        password="password123",
        role=Role.TIM,
        email="tim@example.com",
        full_name="TIM User"
    )
    
    responsable = access_control.create_user(
        username="responsable_user",
        password="password456",
        role=Role.RESPONSABLE_DIM,
        email="responsable@example.com",
        full_name="Responsable DIM"
    )
    
    admin = access_control.create_user(
        username="admin_user",
        password="password789",
        role=Role.ADMINISTRATEUR,
        email="admin@example.com",
        full_name="Admin User"
    )
    
    return {"tim": tim, "responsable": responsable, "admin": admin}


class TestAccessControlInit:
    """Tests d'initialisation du système de contrôle d'accès."""
    
    def test_init_with_defaults(self):
        """Test l'initialisation avec valeurs par défaut."""
        ac = AccessControl()
        
        assert ac.session_duration_hours == 8
    
    def test_init_with_custom_duration(self):
        """Test l'initialisation avec durée personnalisée."""
        ac = AccessControl(session_duration_hours=12)
        
        assert ac.session_duration_hours == 12


class TestCreateUser:
    """Tests de création d'utilisateurs."""
    
    def test_create_user_success(self, access_control):
        """Test la création réussie d'un utilisateur."""
        user = access_control.create_user(
            username="test_user",
            password="test_password",
            role=Role.TIM,
            email="test@example.com",
            full_name="Test User"
        )
        
        assert user.username == "test_user"
        assert user.role == Role.TIM
        assert user.email == "test@example.com"
        assert user.full_name == "Test User"
        assert user.is_active is True
        assert user.password_hash != "test_password"  # Le mot de passe doit être hashé
        assert ":" in user.password_hash  # Format salt:hash
    
    def test_create_user_duplicate_username(self, access_control):
        """Test que la création échoue si l'utilisateur existe déjà."""
        access_control.create_user(
            username="duplicate_user",
            password="password",
            role=Role.TIM
        )
        
        with pytest.raises(ValueError, match="existe déjà"):
            access_control.create_user(
                username="duplicate_user",
                password="password2",
                role=Role.RESPONSABLE_DIM
            )
    
    def test_create_user_different_roles(self, access_control):
        """Test la création d'utilisateurs avec différents rôles."""
        tim = access_control.create_user("tim", "pass", Role.TIM)
        responsable = access_control.create_user("resp", "pass", Role.RESPONSABLE_DIM)
        admin = access_control.create_user("admin", "pass", Role.ADMINISTRATEUR)
        
        assert tim.role == Role.TIM
        assert responsable.role == Role.RESPONSABLE_DIM
        assert admin.role == Role.ADMINISTRATEUR


class TestAuthenticate:
    """Tests d'authentification."""
    
    def test_authenticate_success(self, access_control, sample_users):
        """Test l'authentification réussie."""
        session = access_control.authenticate("tim_user", "password123")
        
        assert session is not None
        assert session.user_id == sample_users["tim"].user_id
        assert session.session_id is not None
        assert len(session.session_id) > 0
        assert session.expires_at > datetime.now()
    
    def test_authenticate_wrong_password(self, access_control, sample_users):
        """Test l'authentification avec mauvais mot de passe."""
        session = access_control.authenticate("tim_user", "wrong_password")
        
        assert session is None
    
    def test_authenticate_unknown_user(self, access_control):
        """Test l'authentification avec utilisateur inconnu."""
        session = access_control.authenticate("unknown_user", "password")
        
        assert session is None
    
    def test_authenticate_inactive_user(self, access_control, sample_users):
        """Test l'authentification avec compte inactif."""
        # Désactiver l'utilisateur
        access_control.deactivate_user(sample_users["tim"].user_id)
        
        # Tenter de se connecter
        session = access_control.authenticate("tim_user", "password123")
        
        assert session is None
    
    def test_authenticate_with_ip_address(self, access_control, sample_users):
        """Test l'authentification avec adresse IP."""
        session = access_control.authenticate(
            "tim_user",
            "password123",
            ip_address="192.168.1.1"
        )
        
        assert session is not None
        assert session.ip_address == "192.168.1.1"


class TestGetUserFromSession:
    """Tests de récupération d'utilisateur depuis une session."""
    
    def test_get_user_from_valid_session(self, access_control, sample_users):
        """Test la récupération avec session valide."""
        session = access_control.authenticate("tim_user", "password123")
        user = access_control.get_user_from_session(session.session_id)
        
        assert user is not None
        assert user.username == "tim_user"
        assert user.role == Role.TIM
    
    def test_get_user_from_invalid_session(self, access_control):
        """Test la récupération avec session invalide."""
        user = access_control.get_user_from_session("invalid_session_id")
        
        assert user is None
    
    def test_get_user_from_expired_session(self, access_control, sample_users):
        """Test la récupération avec session expirée."""
        # Créer un AccessControl avec durée très courte
        ac = AccessControl(session_duration_hours=0)
        ac.create_user("test", "pass", Role.TIM)
        
        # Créer une session qui expire immédiatement
        session = ac.authenticate("test", "pass")
        
        # Attendre que la session expire
        time.sleep(0.1)
        
        # Modifier manuellement l'expiration pour simuler une session expirée
        ac._sessions[session.session_id] = type(session)(
            session_id=session.session_id,
            user_id=session.user_id,
            created_at=session.created_at,
            expires_at=datetime.now() - timedelta(seconds=1),
            ip_address=session.ip_address
        )
        
        user = ac.get_user_from_session(session.session_id)
        
        assert user is None
        # La session expirée devrait être supprimée
        assert session.session_id not in ac._sessions


class TestHasPermission:
    """Tests de vérification des permissions."""
    
    def test_tim_has_view_codes_permission(self, access_control, sample_users):
        """Test que TIM a la permission VIEW_CODES."""
        session = access_control.authenticate("tim_user", "password123")
        
        assert access_control.has_permission(session.session_id, Permission.VIEW_CODES) is True
    
    def test_tim_has_correct_code_permission(self, access_control, sample_users):
        """Test que TIM a la permission CORRECT_CODE."""
        session = access_control.authenticate("tim_user", "password123")
        
        assert access_control.has_permission(session.session_id, Permission.CORRECT_CODE) is True
    
    def test_tim_no_manage_users_permission(self, access_control, sample_users):
        """Test que TIM n'a pas la permission MANAGE_USERS."""
        session = access_control.authenticate("tim_user", "password123")
        
        assert access_control.has_permission(session.session_id, Permission.MANAGE_USERS) is False
    
    def test_responsable_has_export_audit_permission(self, access_control, sample_users):
        """Test que Responsable DIM a la permission EXPORT_AUDIT."""
        session = access_control.authenticate("responsable_user", "password456")
        
        assert access_control.has_permission(session.session_id, Permission.EXPORT_AUDIT) is True
    
    def test_responsable_no_manage_users_permission(self, access_control, sample_users):
        """Test que Responsable DIM n'a pas la permission MANAGE_USERS."""
        session = access_control.authenticate("responsable_user", "password456")
        
        assert access_control.has_permission(session.session_id, Permission.MANAGE_USERS) is False
    
    def test_admin_has_all_permissions(self, access_control, sample_users):
        """Test que Admin a toutes les permissions."""
        session = access_control.authenticate("admin_user", "password789")
        
        # Tester quelques permissions
        assert access_control.has_permission(session.session_id, Permission.VIEW_CODES) is True
        assert access_control.has_permission(session.session_id, Permission.MANAGE_USERS) is True
        assert access_control.has_permission(session.session_id, Permission.EXPORT_AUDIT) is True
        assert access_control.has_permission(session.session_id, Permission.MANAGE_REFERENTIELS) is True
    
    def test_has_permission_invalid_session(self, access_control):
        """Test la vérification avec session invalide."""
        assert access_control.has_permission("invalid_session", Permission.VIEW_CODES) is False


class TestRequirePermission:
    """Tests de vérification stricte des permissions."""
    
    def test_require_permission_success(self, access_control, sample_users):
        """Test la vérification réussie d'une permission."""
        session = access_control.authenticate("tim_user", "password123")
        
        user = access_control.require_permission(session.session_id, Permission.VIEW_CODES)
        
        assert user is not None
        assert user.username == "tim_user"
    
    def test_require_permission_denied(self, access_control, sample_users):
        """Test la vérification échouée d'une permission."""
        session = access_control.authenticate("tim_user", "password123")
        
        with pytest.raises(PermissionError, match="Permission refusée"):
            access_control.require_permission(session.session_id, Permission.MANAGE_USERS)
    
    def test_require_permission_invalid_session(self, access_control):
        """Test la vérification avec session invalide."""
        with pytest.raises(PermissionError, match="Session invalide"):
            access_control.require_permission("invalid_session", Permission.VIEW_CODES)


class TestLogout:
    """Tests de déconnexion."""
    
    def test_logout_success(self, access_control, sample_users):
        """Test la déconnexion réussie."""
        session = access_control.authenticate("tim_user", "password123")
        
        result = access_control.logout(session.session_id)
        
        assert result is True
        # La session ne devrait plus exister
        user = access_control.get_user_from_session(session.session_id)
        assert user is None
    
    def test_logout_invalid_session(self, access_control):
        """Test la déconnexion avec session invalide."""
        result = access_control.logout("invalid_session")
        
        assert result is False


class TestDeactivateUser:
    """Tests de désactivation d'utilisateurs."""
    
    def test_deactivate_user_success(self, access_control, sample_users):
        """Test la désactivation réussie d'un utilisateur."""
        # Créer une session
        session = access_control.authenticate("tim_user", "password123")
        
        # Désactiver l'utilisateur
        result = access_control.deactivate_user(sample_users["tim"].user_id)
        
        assert result is True
        
        # La session devrait être supprimée
        user = access_control.get_user_from_session(session.session_id)
        assert user is None
        
        # L'utilisateur ne devrait plus pouvoir se connecter
        new_session = access_control.authenticate("tim_user", "password123")
        assert new_session is None
    
    def test_deactivate_user_invalid_id(self, access_control):
        """Test la désactivation avec ID invalide."""
        result = access_control.deactivate_user("invalid_user_id")
        
        assert result is False


class TestGetUserPermissions:
    """Tests de récupération des permissions."""
    
    def test_get_tim_permissions(self, access_control, sample_users):
        """Test la récupération des permissions TIM."""
        session = access_control.authenticate("tim_user", "password123")
        permissions = access_control.get_user_permissions(session.session_id)
        
        assert Permission.VIEW_CODES in permissions
        assert Permission.CORRECT_CODE in permissions
        assert Permission.VALIDATE_STAY in permissions
        assert Permission.MANAGE_USERS not in permissions
    
    def test_get_responsable_permissions(self, access_control, sample_users):
        """Test la récupération des permissions Responsable DIM."""
        session = access_control.authenticate("responsable_user", "password456")
        permissions = access_control.get_user_permissions(session.session_id)
        
        assert Permission.VIEW_CODES in permissions
        assert Permission.EXPORT_AUDIT in permissions
        assert Permission.MANAGE_RULES in permissions
        assert Permission.MANAGE_USERS not in permissions
    
    def test_get_admin_permissions(self, access_control, sample_users):
        """Test la récupération des permissions Admin."""
        session = access_control.authenticate("admin_user", "password789")
        permissions = access_control.get_user_permissions(session.session_id)
        
        # Admin devrait avoir toutes les permissions
        assert len(permissions) == len(Permission)
        assert Permission.MANAGE_USERS in permissions
    
    def test_get_permissions_invalid_session(self, access_control):
        """Test la récupération avec session invalide."""
        permissions = access_control.get_user_permissions("invalid_session")
        
        assert len(permissions) == 0


class TestPasswordHashing:
    """Tests de hachage des mots de passe."""
    
    def test_password_is_hashed(self, access_control):
        """Test que le mot de passe est hashé."""
        user = access_control.create_user("test", "password123", Role.TIM)
        
        # Le hash ne devrait pas être le mot de passe en clair
        assert user.password_hash != "password123"
        # Le hash devrait contenir un salt
        assert ":" in user.password_hash
    
    def test_same_password_different_hashes(self, access_control):
        """Test que le même mot de passe produit des hashes différents (salt aléatoire)."""
        user1 = access_control.create_user("user1", "password", Role.TIM)
        user2 = access_control.create_user("user2", "password", Role.TIM)
        
        # Les hashes devraient être différents (salt différent)
        assert user1.password_hash != user2.password_hash
    
    def test_password_verification(self, access_control):
        """Test la vérification du mot de passe."""
        user = access_control.create_user("test", "password123", Role.TIM)
        
        # Le bon mot de passe devrait fonctionner
        assert access_control._verify_password("password123", user.password_hash) is True
        
        # Un mauvais mot de passe ne devrait pas fonctionner
        assert access_control._verify_password("wrong_password", user.password_hash) is False
