"""
Tests unitarios para el módulo de permisos multi-tenant.
Ejecutar con: pytest backend/api/v1/modules/core/permissions/test_permissions.py -v
"""

import pytest
from datetime import datetime, timedelta
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from core.database import Base
from api.v1.modules.core.permissions.models import (
    Permission,
    ClientRole,
    RolePermission,
    UserClientRole,
    UserClientPermission,
)
from api.v1.modules.core.permissions.service import PermissionService


# ============================================================================
# FIXTURES
# ============================================================================


@pytest.fixture
def db_session():
    """Crea una sesión de base de datos en memoria para testing."""
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    yield session

    session.close()


@pytest.fixture
def sample_permissions(db_session: Session):
    """Crea permisos de ejemplo."""
    permissions = [
        Permission(
            code="invoice.view",
            description="Ver facturas",
            module="invoice",
            action="view",
        ),
        Permission(
            code="invoice.create",
            description="Crear facturas",
            module="invoice",
            action="create",
        ),
        Permission(
            code="invoice.edit",
            description="Editar facturas",
            module="invoice",
            action="edit",
        ),
        Permission(
            code="invoice.delete",
            description="Eliminar facturas",
            module="invoice",
            action="delete",
        ),
        Permission(
            code="user.view", description="Ver usuarios", module="user", action="view"
        ),
    ]

    for perm in permissions:
        db_session.add(perm)

    db_session.commit()
    return permissions


@pytest.fixture
def sample_role(db_session: Session, sample_permissions):
    """Crea un rol de ejemplo con permisos."""
    role = ClientRole(
        client_id=1, name="Administrador", code="admin", description="Acceso completo"
    )
    db_session.add(role)
    db_session.commit()

    # Asignar permisos al rol
    for perm in sample_permissions[:3]:  # invoice.view, create, edit
        role_perm = RolePermission(client_role_id=role.id, permission_id=perm.id)
        db_session.add(role_perm)

    db_session.commit()
    return role


# ============================================================================
# TESTS DE PermissionService
# ============================================================================


class TestPermissionService:
    """Tests para el servicio de permisos."""

    def test_get_permissions_from_role(self, db_session, sample_role):
        """Test: obtener permisos desde roles asignados."""
        # Asignar rol a usuario
        user_role = UserClientRole(
            user_id="user-123", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        # Verificar permisos
        service = PermissionService(db_session)
        permissions = service.get_user_permissions("user-123", 1)

        assert "invoice.view" in permissions
        assert "invoice.create" in permissions
        assert "invoice.edit" in permissions
        assert "invoice.delete" not in permissions  # No está en el rol

    def test_has_permission_single(self, db_session, sample_role):
        """Test: verificar un permiso específico."""
        user_role = UserClientRole(
            user_id="user-123", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)

        assert service.has_permission("user-123", 1, "invoice.view") is True
        assert service.has_permission("user-123", 1, "invoice.delete") is False

    def test_has_all_permissions(self, db_session, sample_role):
        """Test: verificar múltiples permisos (todos requeridos)."""
        user_role = UserClientRole(
            user_id="user-123", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)

        # Tiene todos
        assert (
            service.has_all_permissions(
                "user-123", 1, ["invoice.view", "invoice.create"]
            )
            is True
        )

        # Falta uno
        assert (
            service.has_all_permissions(
                "user-123", 1, ["invoice.view", "invoice.delete"]
            )
            is False
        )

    def test_has_any_permission(self, db_session, sample_role):
        """Test: verificar múltiples permisos (al menos uno)."""
        user_role = UserClientRole(
            user_id="user-123", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)

        # Tiene al menos uno
        assert (
            service.has_any_permission(
                "user-123", 1, ["invoice.view", "invoice.delete"]
            )
            is True
        )

        # No tiene ninguno
        assert (
            service.has_any_permission("user-123", 1, ["invoice.delete", "user.view"])
            is False
        )

    def test_direct_permission_grant(self, db_session, sample_permissions):
        """Test: conceder permiso directo a usuario."""
        service = PermissionService(db_session)

        # Conceder permiso directo
        user_perm = service.grant_direct_permission(
            user_id="user-123", client_id=1, permission_code="invoice.delete"
        )

        assert user_perm.is_granted is True
        assert user_perm.is_active is True

        # Verificar que el usuario tiene el permiso
        permissions = service.get_user_permissions("user-123", 1)
        assert "invoice.delete" in permissions

    def test_direct_permission_revoke(
        self, db_session, sample_role, sample_permissions
    ):
        """Test: revocar permiso que viene de un rol."""
        # Asignar rol con permisos
        user_role = UserClientRole(
            user_id="user-123", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)

        # Verificar que tiene el permiso del rol
        assert service.has_permission("user-123", 1, "invoice.view") is True

        # Revocar explícitamente
        invoice_view = (
            db_session.query(Permission)
            .filter(Permission.code == "invoice.view")
            .first()
        )

        user_perm = UserClientPermission(
            user_id="user-123",
            client_id=1,
            permission_id=invoice_view.id,
            is_granted=False,  # Revocado
        )
        db_session.add(user_perm)
        db_session.commit()

        # Ahora NO debe tener el permiso
        assert service.has_permission("user-123", 1, "invoice.view") is False

    def test_permission_expiration(self, db_session, sample_permissions):
        """Test: permisos con fecha de expiración."""
        service = PermissionService(db_session)

        # Permiso expirado
        expired_perm = service.grant_direct_permission(
            user_id="user-123",
            client_id=1,
            permission_code="invoice.delete",
            expires_at=datetime.utcnow() - timedelta(days=1),  # Ayer
        )

        # No debe aparecer en los permisos
        permissions = service.get_user_permissions("user-123", 1)
        assert "invoice.delete" not in permissions

        # Permiso futuro
        future_perm = service.grant_direct_permission(
            user_id="user-456",
            client_id=1,
            permission_code="invoice.delete",
            expires_at=datetime.utcnow() + timedelta(days=7),  # En 7 días
        )

        # Debe aparecer
        permissions = service.get_user_permissions("user-456", 1)
        assert "invoice.delete" in permissions

    def test_assign_role_to_user(self, db_session, sample_role):
        """Test: asignar un rol a un usuario."""
        service = PermissionService(db_session)

        user_role = service.assign_role_to_user(
            user_id="user-789",
            client_id=1,
            role_id=sample_role.id,
            assigned_by="admin-001",
        )

        assert user_role.user_id == "user-789"
        assert user_role.client_id == 1
        assert user_role.client_role_id == sample_role.id
        assert user_role.assigned_by == "admin-001"
        assert user_role.is_active is True

    def test_assign_role_invalid_client(self, db_session, sample_role):
        """Test: intentar asignar rol de otro cliente falla."""
        service = PermissionService(db_session)

        with pytest.raises(ValueError, match="not found for client"):
            service.assign_role_to_user(
                user_id="user-789",
                client_id=999,  # Cliente incorrecto
                role_id=sample_role.id,
            )

    def test_multiple_roles_same_user(self, db_session, sample_permissions):
        """Test: usuario con múltiples roles combina permisos."""
        # Crear dos roles diferentes
        role1 = ClientRole(client_id=1, code="viewer", name="Viewer")
        role2 = ClientRole(client_id=1, code="editor", name="Editor")
        db_session.add_all([role1, role2])
        db_session.commit()

        # Role1: solo view
        rp1 = RolePermission(
            client_role_id=role1.id,
            permission_id=sample_permissions[0].id,  # invoice.view
        )

        # Role2: create y edit
        rp2 = RolePermission(
            client_role_id=role2.id,
            permission_id=sample_permissions[1].id,  # invoice.create
        )
        rp3 = RolePermission(
            client_role_id=role2.id,
            permission_id=sample_permissions[2].id,  # invoice.edit
        )
        db_session.add_all([rp1, rp2, rp3])
        db_session.commit()

        # Asignar ambos roles al usuario
        ur1 = UserClientRole(user_id="user-multi", client_id=1, client_role_id=role1.id)
        ur2 = UserClientRole(user_id="user-multi", client_id=1, client_role_id=role2.id)
        db_session.add_all([ur1, ur2])
        db_session.commit()

        # Verificar que tiene permisos de ambos roles
        service = PermissionService(db_session)
        permissions = service.get_user_permissions("user-multi", 1)

        assert "invoice.view" in permissions
        assert "invoice.create" in permissions
        assert "invoice.edit" in permissions
        assert "invoice.delete" not in permissions

    def test_inactive_role_no_permissions(self, db_session, sample_role):
        """Test: roles inactivos no otorgan permisos."""
        # Asignar rol activo
        user_role = UserClientRole(
            user_id="user-inactive", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)

        # Verificar que tiene permisos
        assert service.has_permission("user-inactive", 1, "invoice.view") is True

        # Desactivar el rol
        sample_role.is_active = False
        db_session.commit()

        # Ahora no debe tener permisos
        assert service.has_permission("user-inactive", 1, "invoice.view") is False

    def test_get_user_roles(self, db_session, sample_role):
        """Test: obtener roles de un usuario."""
        user_role = UserClientRole(
            user_id="user-roles", client_id=1, client_role_id=sample_role.id
        )
        db_session.add(user_role)
        db_session.commit()

        service = PermissionService(db_session)
        roles = service.get_user_roles("user-roles", 1)

        assert len(roles) == 1
        assert roles[0].code == "admin"
        assert roles[0].name == "Administrador"


# ============================================================================
# TESTS DE MODELOS
# ============================================================================


class TestModels:
    """Tests para los modelos de base de datos."""

    def test_permission_creation(self, db_session):
        """Test: crear un permiso."""
        perm = Permission(
            code="test.action",
            description="Permiso de prueba",
            module="test",
            action="action",
        )
        db_session.add(perm)
        db_session.commit()

        assert perm.id is not None
        assert perm.is_active is True
        assert perm.created_at is not None

    def test_client_role_unique_constraint(self, db_session):
        """Test: código de rol debe ser único por cliente."""
        role1 = ClientRole(client_id=1, code="admin", name="Admin 1")
        db_session.add(role1)
        db_session.commit()

        # Intentar crear otro con mismo código en mismo cliente
        role2 = ClientRole(client_id=1, code="admin", name="Admin 2")
        db_session.add(role2)

        with pytest.raises(Exception):  # Violación de constraint
            db_session.commit()

    def test_role_permission_relationship(
        self, db_session, sample_role, sample_permissions
    ):
        """Test: relación entre roles y permisos."""
        # El rol ya tiene permisos del fixture
        assert len(sample_role.role_permissions) > 0

        # Verificar que los permisos están relacionados
        permission_codes = [rp.permission.code for rp in sample_role.role_permissions]
        assert "invoice.view" in permission_codes


# ============================================================================
# EJECUTAR TESTS
# ============================================================================

if __name__ == "__main__":
    pytest.main([__file__, "-v"])
