"""
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_, text

from core.database import RLS_TENANT_KEY
from .cache import PermissionCache
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
        self._cache = PermissionCache()

    def _ensure_user_tenant_row_for_company(
        self, user_id: str, company_id: int
    ) -> None:
        """STUB — implementa con el modelo de compañía de tu proyecto."""

    def _resolve_tenant_id_for_company(self, company_id: int) -> Optional[int]:
        """
        Resuelve tenant_id efectivo para una compañía usando primero el contexto RLS
        de la sesión y, como fallback, la tabla de compañías.
        """
        tenant_id = self.db.info.get(RLS_TENANT_KEY)
        if tenant_id is not None:
            try:
                return int(tenant_id)
            except (TypeError, ValueError):
                return None

        try:
            # La tabla de compañías del CRM es ``a76.company`` (esquema legado). No tiene modelo
            # ORM en este proyecto, así que se consulta con SQL crudo, igual que ``seed_crm.py``.
            #
            # Sin este respaldo la función devolvía None siempre que la sesión no traía contexto
            # RLS, y con ella se scopean las consultas de permisos de los cinco llamadores de
            # abajo: el efecto neto era que nadie resolvía permisos.
            row = self.db.execute(
                text("SELECT tenant_id FROM a76.company WHERE id = :company_id"),
                {"company_id": company_id},
            ).first()
            if row is not None and row[0] is not None:
                return int(row[0])
        except Exception as exc:
            logger.warning(
                "resolve_tenant_id_for_company_failed",
                extra={
                    "company_id": company_id,
                    "error": str(exc),
                },
            )
        return None

    def _get_user_permissions_uncached(
        self, user_id: str, company_id: int
    ) -> Set[str]:
        """
        Lógica de cálculo de permisos sin caché.
        """
        role_permissions = self._get_permissions_from_roles(user_id, company_id)
        direct_permissions = self._get_direct_permissions(user_id, company_id)

        all_permissions = role_permissions.copy()

        for perm_code, is_granted in direct_permissions.items():
            if is_granted:
                all_permissions.add(perm_code)
            else:
                all_permissions.discard(perm_code)

        return all_permissions

    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.
        Puede usar caché de Valkey cuando use_cache=True.
        """
        if not use_cache:
            return self._get_user_permissions_uncached(user_id, company_id)

        tenant_id = self._resolve_tenant_id_for_company(company_id)
        cache_key = self._cache.build_permissions_key(tenant_id, company_id, user_id)

        cached = self._cache.get_permissions(
            cache_key,
            user_id=user_id,
            company_id=company_id,
            tenant_id=tenant_id,
        )
        if cached is not None:
            return cached

        permissions = self._get_user_permissions_uncached(user_id, company_id)
        self._cache.set_permissions(
            cache_key,
            permissions,
            user_id=user_id,
            company_id=company_id,
            tenant_id=tenant_id,
        )
        return 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, use_cache=True)
        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, use_cache=True)
        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, use_cache=True)
        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)
                # Invalidar caché del usuario tras reactivar el rol
                try:
                    tenant_id = self._resolve_tenant_id_for_company(company_id)
                    self._cache.invalidate_user(tenant_id, company_id, user_id)
                except Exception:
                    logger.warning(
                        "permission_cache_invalidate_user_after_assign_role_failed",
                        extra={"user_id": user_id, "company_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)

        # Invalidar caché del usuario tras nueva asignación de rol
        try:
            tenant_id = self._resolve_tenant_id_for_company(company_id)
            self._cache.invalidate_user(tenant_id, company_id, user_id)
        except Exception:
            logger.warning(
                "permission_cache_invalidate_user_after_assign_role_failed",
                extra={"user_id": user_id, "company_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)
            # Invalidar caché del usuario tras mutación
            try:
                tenant_id = self._resolve_tenant_id_for_company(company_id)
                self._cache.invalidate_user(tenant_id, company_id, user_id)
            except Exception:
                # La invalidación de caché nunca debe romper la operación principal
                logger.warning(
                    "permission_cache_invalidate_user_after_grant_failed",
                    extra={"user_id": user_id, "company_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)

        # Invalidar caché del usuario tras mutación
        try:
            tenant_id = self._resolve_tenant_id_for_company(company_id)
            self._cache.invalidate_user(tenant_id, company_id, user_id)
        except Exception:
            logger.warning(
                "permission_cache_invalidate_user_after_grant_failed",
                extra={"user_id": user_id, "company_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. tenant_id del rol: se toma de la COMPAÑÍA, que es su fuente autoritativa.
            #    ``company_roles`` referencia a la vez a ``a76.company`` y a ``core.tenants``, así
            #    que el tenant del rol tiene que ser el de su compañía o la fila queda cruzada
            #    entre dos tenants.
            #
            #    Antes esto era ``self.db.info.get(RLS_TENANT_KEY) or 1``. Cuando la sesión no
            #    traía contexto RLS —justo el caso de ``/permissions/me`` en el primer acceso— el
            #    rol se creaba con ``tenant_id=1``; en una instalación real ese tenant no existe y
            #    el INSERT moría con ForeignKeyViolation. El bootstrap quedaba a medias, sin rol
            #    ni permisos, toda la API respondía 403 y ``/permissions/me`` seguía devolviendo
            #    200 con la lista vacía: el fallo se leía en pantalla como "no tienes permisos"
            #    en lugar de como el error de configuración que era.
            #    Se consulta la compañía DIRECTAMENTE y no vía ``_resolve_tenant_id_for_company``:
            #    ese helper prefiere el contexto RLS, que es el tenant del REQUEST y puede no ser
            #    el de la compañía. Para leer permisos esa preferencia está bien y ahorra una
            #    consulta en el camino caliente; para escribir una fila atada por FK a las dos
            #    tablas, no: si difirieran, el rol nacería cruzado.
            _row = self.db.execute(
                text("SELECT tenant_id FROM a76.company WHERE id = :company_id"),
                {"company_id": company_id},
            ).first()
            if _row is None or _row[0] is None:
                logger.error(
                    "Bootstrap: la compañía %s no existe en a76.company, no hay tenant al que "
                    "colgar el rol. Se aborta sin crear nada.",
                    company_id,
                )
                return False
            tenant_id = int(_row[0])

            # 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}. Sincronizando permisos nuevos si es necesario.")
                # Asegurar que el rol tenga TODOS los permisos activos (incluidos los agregados después)
                all_perms = self.db.query(Permission).filter(Permission.is_active == True).all()
                existing_perm_ids = {
                    pid
                    for (pid,) in self.db.query(RolePermission.permission_id).filter(
                        RolePermission.company_role_id == admin_role.id
                    )
                }
                added = 0
                for perm in all_perms:
                    if perm.id in existing_perm_ids:
                        continue
                    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)
                    added += 1
                if added:
                    logger.info(
                        "Bootstrap: sincronicé %s permisos nuevos en super_admin company_id=%s",
                        added,
                        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()
                # Invalida caché de permisos de la compañía y del usuario afectado
                try:
                    self._cache.invalidate_company(company_id)
                    self._cache.invalidate_user(tenant_id, company_id, user_id)
                except Exception:
                    logger.warning(
                        "permission_cache_invalidate_after_bootstrap_failed",
                        extra={"user_id": user_id, "company_id": company_id},
                    )
                return True
            else:
                logger.info(f"Bootstrap: El usuario {user_id} ya tiene el rol asignado.")
                self.db.commit()
                # Invalida caché de permisos de la compañía para reflejar cambios de catálogo
                try:
                    self._cache.invalidate_company(company_id)
                except Exception:
                    logger.warning(
                        "permission_cache_invalidate_after_bootstrap_failed",
                        extra={"user_id": user_id, "company_id": company_id},
                    )
                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
