"""
Servicio de gestión de permisos multi-tenant.
Proporciona funciones para verificar y obtener permisos de usuarios por companye.
"""

import logging
from datetime import datetime
from typing import Set, Optional, List
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_
from .models import (
    Permission,
    CompanyRole,
    RolePermission,
    UserCompanyRole,
    UserCompanyPermission,
)

logger = logging.getLogger(__name__)


class PermissionService:
    """
    Servicio para gestionar permisos de usuarios en contextos multi-tenant.
    Combina permisos de roles y permisos directos del usuario.
    """

    def __init__(self, db: Session):
        self.db = db

    def _ensure_user_tenant_row_for_company(
        self, user_id: str, company_id: int
    ) -> None:
        """Mantiene core.user_tenants alineado con asignaciones RBAC de la app."""
        try:
            from api.v1.modules.a76.general_catalogs.company.models import Company
            from core.security import _ensure_user_tenant_for_company

            company = (
                self.db.query(Company).filter(Company.id == company_id).first()
            )
            if company:
                _ensure_user_tenant_for_company(
                    self.db, user_id, company.tenant_id, company_id
                )
        except Exception as e:
            logger.warning("user_tenants sync skipped: %s", e)

    def get_user_permissions(
        self, user_id: str, company_id: int, use_cache: bool = False
    ) -> Set[str]:
        """
        Obtiene todos los permisos de un usuario para un companye específico.
        Combina permisos de roles asignados y permisos directos.

        Args:
            user_id: ID del usuario
            company_id: ID del companye/tenant
            use_cache: Si True, intenta usar caché (útil en producción)

        Returns:
            Set de códigos de permisos (ej: {"invoice.view", "invoice.edit"})
        """
        # Obtener permisos de roles asignados al usuario
        role_permissions = self._get_permissions_from_roles(user_id, company_id)

        # Obtener permisos directos del usuario
        direct_permissions = self._get_direct_permissions(user_id, company_id)

        # Combinar ambos conjuntos
        # Los permisos directos revocados (is_granted=False) anulan los de roles
        all_permissions = role_permissions.copy()

        for perm_code, is_granted in direct_permissions.items():
            if is_granted:
                all_permissions.add(perm_code)
            else:
                # Permiso explícitamente revocado
                all_permissions.discard(perm_code)

        return all_permissions

    def _get_permissions_from_roles(self, user_id: str, company_id: int) -> Set[str]:
        """
        Obtiene permisos derivados de los roles del usuario en el companye.

        Realiza un JOIN eficiente para obtener todos los permisos de los roles activos.
        """
        query = (
            self.db.query(Permission.code)
            .join(RolePermission, RolePermission.permission_id == Permission.id)
            .join(CompanyRole, CompanyRole.id == RolePermission.company_role_id)
            .join(UserCompanyRole, UserCompanyRole.company_role_id == CompanyRole.id)
            .filter(
                and_(
                    UserCompanyRole.user_id == user_id,
                    UserCompanyRole.company_id == company_id,
                    UserCompanyRole.is_active == True,
                    CompanyRole.is_active == True,
                    Permission.is_active == True,
                )
            )
        )

        results = query.all()
        return {perm_code for (perm_code,) in results}

    def _get_direct_permissions(self, user_id: str, company_id: int) -> dict:
        """
        Obtiene permisos directos asignados al usuario.

        Returns:
            Dict con código de permiso como key y is_granted como value
            {
                "invoice.delete": True,  # Permiso concedido
                "user.delete": False     # Permiso revocado explícitamente
            }
        """
        now = datetime.utcnow()

        query = (
            self.db.query(Permission.code, UserCompanyPermission.is_granted)
            .join(
                UserCompanyPermission,
                UserCompanyPermission.permission_id == Permission.id,
            )
            .filter(
                and_(
                    UserCompanyPermission.user_id == user_id,
                    UserCompanyPermission.company_id == company_id,
                    UserCompanyPermission.is_active == True,
                    Permission.is_active == True,
                    or_(
                        UserCompanyPermission.expires_at.is_(None),
                        UserCompanyPermission.expires_at > now,
                    ),
                )
            )
        )

        results = query.all()
        return {perm_code: is_granted for perm_code, is_granted in results}

    def has_permission(
        self, user_id: str, company_id: int, permission_code: str
    ) -> bool:
        """
        Verifica si un usuario tiene un permiso específico en un companye.

        Args:
            user_id: ID del usuario
            company_id: ID del companye/tenant
            permission_code: Código del permiso (ej: "invoice.edit")

        Returns:
            True si el usuario tiene el permiso, False en caso contrario
        """
        permissions = self.get_user_permissions(user_id, company_id)
        return permission_code in permissions

    def has_any_permission(
        self, user_id: str, company_id: int, permission_codes: List[str]
    ) -> bool:
        """
        Verifica si el usuario tiene al menos uno de los permisos especificados.
        """
        permissions = self.get_user_permissions(user_id, company_id)
        return any(perm in permissions for perm in permission_codes)

    def has_all_permissions(
        self, user_id: str, company_id: int, permission_codes: List[str]
    ) -> bool:
        """
        Verifica si el usuario tiene todos los permisos especificados.
        """
        permissions = self.get_user_permissions(user_id, company_id)
        return all(perm in permissions for perm in permission_codes)

    def get_user_roles(self, user_id: str, company_id: int) -> List[CompanyRole]:
        """
        Obtiene los roles activos de un usuario en un companye.
        """
        query = (
            self.db.query(CompanyRole)
            .join(UserCompanyRole, UserCompanyRole.company_role_id == CompanyRole.id)
            .filter(
                and_(
                    UserCompanyRole.user_id == user_id,
                    UserCompanyRole.company_id == company_id,
                    UserCompanyRole.is_active == True,
                    CompanyRole.is_active == True,
                )
            )
        )

        return query.all()

    def assign_role_to_user(
        self,
        user_id: str,
        company_id: int,
        role_id: int,
        assigned_by: Optional[str] = None,
    ) -> UserCompanyRole:
        """
        Asigna un rol a un usuario en un companye específico.
        """
        # Verificar que el rol pertenece al companye
        role = (
            self.db.query(CompanyRole)
            .filter(
                and_(
                    CompanyRole.id == role_id,
                    CompanyRole.company_id == company_id,
                    CompanyRole.is_active == True,
                )
            )
            .first()
        )

        if not role:
            raise ValueError(f"Role {role_id} not found for company {company_id}")

        # Verificar si ya existe la asignación
        existing = (
            self.db.query(UserCompanyRole)
            .filter(
                and_(
                    UserCompanyRole.user_id == user_id,
                    UserCompanyRole.company_id == company_id,
                    UserCompanyRole.company_role_id == role_id,
                )
            )
            .first()
        )

        if existing:
            if not existing.is_active:
                existing.is_active = True
                existing.assigned_at = datetime.utcnow()
                existing.assigned_by = assigned_by
                self.db.commit()
                self._ensure_user_tenant_row_for_company(user_id, company_id)
                return existing
            self._ensure_user_tenant_row_for_company(user_id, company_id)
            return existing

        # Crear nueva asignación
        user_role = UserCompanyRole(
            user_id=user_id,
            company_id=company_id,
            company_role_id=role_id,
            assigned_by=assigned_by,
        )

        self.db.add(user_role)
        self.db.commit()
        self.db.refresh(user_role)

        self._ensure_user_tenant_row_for_company(user_id, company_id)

        return user_role

    def grant_direct_permission(
        self,
        user_id: str,
        company_id: int,
        permission_code: str,
        assigned_by: Optional[str] = None,
        expires_at: Optional[datetime] = None,
    ) -> UserCompanyPermission:
        """
        Concede un permiso directo a un usuario en un companye.
        """
        # Obtener el permiso por código
        permission = (
            self.db.query(Permission)
            .filter(
                and_(Permission.code == permission_code, Permission.is_active == True)
            )
            .first()
        )

        if not permission:
            raise ValueError(f"Permission {permission_code} not found")

        # Verificar si ya existe
        existing = (
            self.db.query(UserCompanyPermission)
            .filter(
                and_(
                    UserCompanyPermission.user_id == user_id,
                    UserCompanyPermission.company_id == company_id,
                    UserCompanyPermission.permission_id == permission.id,
                )
            )
            .first()
        )

        if existing:
            existing.is_granted = True
            existing.is_active = True
            existing.assigned_at = datetime.utcnow()
            existing.assigned_by = assigned_by
            existing.expires_at = expires_at
            self.db.commit()
            self._ensure_user_tenant_row_for_company(user_id, company_id)
            return existing

        # Crear nuevo permiso directo
        user_permission = UserCompanyPermission(
            user_id=user_id,
            company_id=company_id,
            permission_id=permission.id,
            is_granted=True,
            assigned_by=assigned_by,
            expires_at=expires_at,
        )

        self.db.add(user_permission)
        self.db.commit()
        self.db.refresh(user_permission)

        self._ensure_user_tenant_row_for_company(user_id, company_id)

        return user_permission

    def sync_permissions(self) -> dict:
        """
        Sincroniza los permisos registrados en el registry con la base de datos.
        Inserta nuevos permisos y actualiza los existentes.
        """
        from .registry import registry
        # IMPORTANTE: Importar seed_v2 para que se ejecute register_core_permissions()
        from . import seed_v2
        
        registered_permissions = registry.get_all()
        synced_count = 0
        updated_count = 0
        
        for p_def in registered_permissions:
            # Buscar permiso existente
            db_permission = self.db.query(Permission).filter(Permission.code == p_def.code).first()
            
            if db_permission:
                # Actualizar si hay cambios
                changed = False
                if db_permission.description != p_def.description:
                    db_permission.description = p_def.description
                    changed = True
                if db_permission.module != p_def.module:
                    db_permission.module = p_def.module
                    changed = True
                if db_permission.action != p_def.action:
                    db_permission.action = p_def.action
                    changed = True
                if db_permission.is_active != p_def.is_active:
                    db_permission.is_active = p_def.is_active
                    changed = True
                
                if changed:
                    updated_count += 1
            else:
                # Crear nuevo
                new_permission = Permission(
                    code=p_def.code,
                    description=p_def.description,
                    module=p_def.module,
                    action=p_def.action,
                    is_active=p_def.is_active
                )
                self.db.add(new_permission)
                synced_count += 1
        
        self.db.commit()
        
        return {
            "synced": synced_count,
            "updated": updated_count,
            "total_registered": len(registered_permissions)
        }

    def bootstrap_super_admin(self, user_id: str, company_id: int) -> bool:
        """
        Crea un rol de Super Administrador con todos los permisos y se lo asigna al usuario.
        Diseñado para el primer inicio de una compañía o para asegurar acceso a administradores.
        """
        from .models import CompanyRole, RolePermission, UserCompanyRole, Permission
        import logging
        logger = logging.getLogger(__name__)

        try:
            # 0. Sincronizar permisos por si la tabla esta vacía
            # Esto puebla la tabla 'permissions' desde el registry de código
            logger.info("Bootstrap: Sincronizando catálogo de permisos desde el registry...")
            from . import seed_v2
            sync_res = self.sync_permissions()
            logger.info(f"Bootstrap: Sincronización completa. {sync_res.get('synced', 0)} nuevos, {sync_res.get('total_registered', 0)} totales.")

            # 1. Obtener el tenant_id
            tenant_id = 1 
            try:
                from api.v1.modules.a76.general_catalogs.company.models import Company
                company = self.db.query(Company).filter(Company.id == company_id).first()
                if company:
                    tenant_id = company.tenant_id
            except:
                pass

            # 2. Buscar si ya existe el rol "super_admin"
            admin_role = self.db.query(CompanyRole).filter(
                CompanyRole.company_id == company_id,
                CompanyRole.code == "super_admin"
            ).first()

            if not admin_role:
                logger.info(f"Bootstrap: Creando Super Administrador para usuario {user_id} (Company: {company_id})")
                # Crear el rol Super Administrador si no existe
                admin_role = CompanyRole(
                    company_id=company_id,
                    tenant_id=tenant_id,
                    name="Super Administrador",
                    code="super_admin",
                    description="Rol con acceso total al sistema (generado automáticamente)",
                    is_active=True
                )
                self.db.add(admin_role)
                self.db.flush() 

                # 4. Asignar TODOS los permisos activos al rol
                all_perms = self.db.query(Permission).filter(Permission.is_active == True).all()
                if not all_perms:
                     logger.warning("Bootstrap: ¡ALERTA! No se encontraron permisos en la DB ni tras la sincronización.")
                
                for perm in all_perms:
                    role_perm = RolePermission(
                        company_role_id=admin_role.id,
                        permission_id=perm.id,
                        tenant_id=tenant_id,
                        company_id=company_id
                    )
                    self.db.add(role_perm)
            else:
                logger.info(f"Bootstrap: El rol super_admin ya existe para la compañía {company_id}.")

            # 5. Asegurar que el usuario tenga el rol asignado
            user_has_role = self.db.query(UserCompanyRole).filter(
                UserCompanyRole.user_id == user_id,
                UserCompanyRole.company_id == company_id,
                UserCompanyRole.company_role_id == admin_role.id
            ).first()

            if not user_has_role:
                logger.info(f"Bootstrap: Asignando rol Super Administrador al usuario {user_id}")
                user_role = UserCompanyRole(
                    user_id=user_id,
                    company_id=company_id,
                    tenant_id=tenant_id,
                    company_role_id=admin_role.id,
                    is_active=True,
                    assigned_by="SYSTEM_BOOTSTRAP"
                )
                self.db.add(user_role)
                self.db.commit()
                return True
            else:
                logger.info(f"Bootstrap: El usuario {user_id} ya tiene el rol asignado.")
                self.db.commit()
                return False

        except Exception as e:
            self.db.rollback()
            logger.error(f"Bootstrap: ERROR CRÍTICO - {str(e)}")
            import traceback
            logger.error(traceback.format_exc())
            return False
