"""
Utilidades de seguridad y autenticación con Keycloak
"""

import logging
from typing import Any, Dict, Optional, Set

from fastapi import Depends, HTTPException, Request, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
import httpx
from cachetools import TTLCache 
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError

from .config import settings
from .database import get_core_db

logger = logging.getLogger(__name__)

# Cache para tokens verificados (1 minuto de TTL, máximo 1000 tokens)
token_cache = TTLCache(maxsize=1000, ttl=60)

# IDs de tenants ya sincronizados en este proceso (evita consultas repetidas)
_synced_tenant_ids: Set[int] = set()

# Alias Hub tenant_id -> tenant_id local cuando existe drift histórico de IDs
# (mismo slug, diferente id).
_tenant_id_aliases: Dict[int, int] = {}
# Inverso: id local core.tenants -> id tenant en Hub (JWT / client_tenants) para llamadas al Hub.
_tenant_id_hub_by_local: Dict[int, int] = {}

# Security scheme
security = HTTPBearer()

def get_active_system(request: Request) -> Optional[str]:
    """Sistema activo: header ``X-Active-System`` o cookie ``active_system``."""
    return request.headers.get("x-active-system") or request.cookies.get("active_system") or None


async def verify_token(token: str, tenant_id_override: str = None) -> Dict[str, Any]:
    """
    Verifica un token JWT llamando al Hub central.
    Si DEV_LOCAL_AUTH=True y el token es HS256 local, lo verifica sin Hub.
    """
    cache_key = (token, tenant_id_override)
    if cache_key in token_cache:
        return token_cache[cache_key]

    # Sesión local del CRM (patrón SIWEB): si el token es una sesión local firmada
    # (HS256, crm_session), validarla sin ir al Hub en cada request. Así el refresh
    # del token KC solo se intenta al expirar la sesión local (no cada ~60s), lo que
    # elimina el bucle de login. verify_session_token retorna None para tokens KC
    # (RS256), así que no interfiere con el flujo normal.
    if settings.SESSION_STORE_ENABLED:
        from core.local_session import verify_session_token

        local_claims = verify_session_token(token)
        if local_claims is not None:
            token_cache[cache_key] = local_claims
            return local_claims

    # Shortcut para tokens de desarrollo local
    if settings.DEV_LOCAL_AUTH:
        try:
            header = jwt.get_unverified_header(token)
            if header.get("alg") == "HS256":
                payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
                if payload.get("dev_local"):
                    token_cache[cache_key] = payload
                    return payload
        except JWTError as e:
            raise HTTPException(status_code=401, detail=f"Dev token inválido: {e}")

    try:
        headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
        if tenant_id_override:
            headers["X-Tenant-Override"] = tenant_id_override

        async with httpx.AsyncClient(timeout=5.0) as client:
            response = await client.get(
                f"{settings.HUB_URL}api/v1/auth/me",
                headers=headers
            )

        if response.status_code == 200:
            user_info = response.json()
            token_cache[cache_key] = user_info
            return user_info

        logger.error(f"Hub token verification failed with status {response.status_code}")
        raise HTTPException(status_code=401, detail="Could not validate credentials")

    except httpx.HTTPError as e:
        logger.error(f"Hub unreachable or error during token verification: {str(e)}")
        raise HTTPException(status_code=503, detail="Authentication service unavailable")
    except Exception as e:
        logger.error(f"Unexpected error during token verification: {str(e)}")
        raise HTTPException(status_code=401, detail="Authentication error")


def _ensure_user_tenant_for_company(
    db: Session, keycloak_user_id: str, tenant_id: int, company_id: int
) -> None:
    """Garantiza fila core.user_tenants (usuario ↔ compañía ↔ tenant)."""
    from api.v1.modules.core.user_tenant.models import UserTenant

    existing = (
        db.query(UserTenant)
        .filter(
            UserTenant.keycloak_user_id == keycloak_user_id,
            UserTenant.tenant_id == tenant_id,
            UserTenant.company_id == company_id,
        )
        .first()
    )
    if existing:
        if not existing.is_active:
            existing.is_active = True
            db.commit()
        return
    db.add(
        UserTenant(
            keycloak_user_id=keycloak_user_id,
            tenant_id=tenant_id,
            company_id=company_id,
            is_active=True,
        )
    )
    db.commit()


def _ensure_company_exists(
    db: Session,
    tenant_id: int,
    tenant_name: str,
    hub_user: Optional[Dict[str, Any]] = None,
) -> None:
    """
    STUB — implementa este método con el modelo de compañía de tu proyecto.

    Debe garantizar que exista al menos una empresa para el tenant y que el
    usuario del token (hub_user["sub"]) tenga un registro en user_tenants.
    """
    logger.debug(
        "_ensure_company_exists: no implementado en la plantilla (tenant_id=%s)", tenant_id
    )


def _repair_user_company_link_if_needed(
    db: Session,
    tenant_id_effective: int,
    hub_user: Optional[Dict[str, Any]],
) -> None:
    """STUB — implementa con el modelo de compañía de tu proyecto."""
    if not hub_user or not hub_user.get("sub"):
        return
    from api.v1.modules.core.permissions.service import PermissionService
    from api.v1.modules.core.user_tenant.models import UserTenant

    # Sin modelo de compañía en la plantilla, no hay empresa que verificar.
    # Implementa esta función cuando definas tu tabla de compañías.


def _ensure_tenant_synced(
    db: Session,
    tenant_id: int,
    tenant_slug: str,
    hub_user: Optional[Dict[str, Any]] = None,
) -> int:
    """
    Garantiza que el tenant del Hub exista en core.tenants local.
    Se ejecuta una sola vez por tenant_id por ciclo de vida del proceso.
    El Hub es la fuente de verdad — este método solo sincroniza en una dirección.
    """
    if tenant_id in _synced_tenant_ids:
        effective = int(_tenant_id_aliases.get(tenant_id, tenant_id))
        _repair_user_company_link_if_needed(db, effective, hub_user)
        return effective

    try:
        # Importación local para evitar imports circulares
        from api.v1.modules.core.tenants.models import Tenant, TenantType

        name = " ".join(word.capitalize() for word in tenant_slug.replace("-", " ").split())

        existing = db.query(Tenant).filter(Tenant.id == tenant_id).first()
        if existing:
            # Update name/slug/keycloak_realm if they differ (Hub is source of truth)
            if existing.slug != tenant_slug or existing.name != name or existing.keycloak_realm != tenant_slug:
                existing.slug = tenant_slug
                existing.name = name
                existing.keycloak_realm = tenant_slug
                db.commit()
                logger.info(f"Tenant id={tenant_id} actualizado: slug='{tenant_slug}'")
            _synced_tenant_ids.add(tenant_id)
            # Garantizar empresa aunque el tenant ya existiera
            _ensure_company_exists(db, tenant_id, name, hub_user)
            return tenant_id

        # Crear el tenant local con los datos disponibles del token.
        # El Hub siempre crea el realm de Keycloak con el mismo nombre que el slug.
        tenant = Tenant(
            id=tenant_id,
            name=name,
            slug=tenant_slug,
            type=TenantType.SHARED,
            keycloak_realm=tenant_slug,
            is_active=True,
        )
        db.add(tenant)
        db.commit()
        _synced_tenant_ids.add(tenant_id)
        logger.info(f"Tenant '{tenant_slug}' (id={tenant_id}) sincronizado desde Hub a core.tenants")
        # Crear la empresa correspondiente al tenant recién sincronizado
        _ensure_company_exists(db, tenant_id, name, hub_user)
        return tenant_id

    except IntegrityError:
        # Puede ser concurrencia o colisión de slug (id diferente, mismo slug)
        db.rollback()
        from api.v1.modules.core.tenants.models import Tenant
        # Si el slug ya existe con diferente id, el tenant real del Hub no está registrado aún.
        # Logueamos el conflicto para depuración; el sistema continuará con tenant_id vacío.
        stale = db.query(Tenant).filter(Tenant.slug == tenant_slug).first()
        if stale and stale.id != tenant_id:
            logger.error(
                f"Conflicto de tenant: JWT dice id={tenant_id} slug='{tenant_slug}', "
                f"pero core.tenants tiene id={stale.id} mismo slug. "
                f"Elimine el registro obsoleto con: "
                f"DELETE FROM core.tenants WHERE id={stale.id};"
            )
            # Auto-heal en runtime: mapear temporalmente al tenant local existente por slug
            # para evitar dejar al usuario sin compañías y evitar este conflicto en cada request.
            _tenant_id_aliases[tenant_id] = int(stale.id)
            _tenant_id_hub_by_local[int(stale.id)] = int(tenant_id)
            _synced_tenant_ids.add(tenant_id)
            _ensure_company_exists(db, int(stale.id), stale.name or tenant_slug, hub_user)
            return int(stale.id)
        else:
            _synced_tenant_ids.add(tenant_id)
            return tenant_id
    except Exception as e:
        db.rollback()
        logger.warning(f"No se pudo sincronizar tenant {tenant_id} ({tenant_slug}): {e}")
        return _tenant_id_aliases.get(tenant_id, tenant_id)


async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Security(security),
    db: Session = Depends(get_core_db),
    request: Request = None,
) -> Dict[str, Any]:
    """
    Dependency para obtener el usuario actual desde el token JWT.
    Auto-sincroniza el tenant en core.tenants si fue creado en el Hub
    pero aún no existe en la BD local.

    Uso en FastAPI:
        current_user: dict = Depends(get_current_user)
    """
    token = credentials.credentials

    # Leer tenant override del header X-Tenant-Override (pasado por el SvelteKit server
    # desde la cookie sso_tenant_id, flujo SSO relay multi-tenant)
    tenant_override = request.headers.get('X-Tenant-Override') if request else None

    logger.info(f"[get_current_user] X-Tenant-Override={tenant_override!r}")

    user_info = await verify_token(token, tenant_id_override=tenant_override)
    # Copia local para poder normalizar tenant_id sin mutar el objeto cacheado
    user_info = dict(user_info)

    # Modo local: el token ya trae todo. Saltar sincronización con el Hub.
    if settings.DEV_LOCAL_AUTH and user_info.get("dev_local"):
        return user_info

    # Sincronizar tenant desde Hub a BD local (solo la primera vez por tenant)
    tenant_id = user_info.get("tenant_id")
    tenant_slug = user_info.get("tenant_slug")
    if tenant_id and tenant_slug:
        effective_tenant_id = _ensure_tenant_synced(
            db, int(tenant_id), str(tenant_slug), hub_user=user_info
        )
        if effective_tenant_id != int(tenant_id):
            logger.warning(
                f"[get_current_user] tenant_id ajustado por alias: hub={tenant_id} local={effective_tenant_id} slug={tenant_slug}"
            )
            user_info["tenant_id"] = effective_tenant_id

    # Rehidratación de sesión: sincronización no bloqueante de avatar/perfil
    # con cache corto para evitar llamadas excesivas al Hub.
    try:
        from core.workspace_profile_sync import sync_workspace_profile_for_user

        await sync_workspace_profile_for_user(
            db,
            access_token=token,
            keycloak_user_id=user_info.get("sub"),
            tenant_id=user_info.get("tenant_id"),
            workspace_profile=user_info,
        )
    except Exception as exc:
        logger.warning("workspace_profile_sync_failed_on_get_current_user: %s", exc)

    return user_info


async def get_current_active_user(
    current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
    """
    Dependency para obtener usuario activo (puede incluir validaciones adicionales)
    """
    # Aquí se pueden agregar validaciones adicionales
    # Por ejemplo, verificar si el usuario está activo en la BD
    return current_user


def has_role(required_role: str):
    """
    Decorator/Dependency para verificar roles de usuario

    Uso:
        @router.get("/admin")
        async def admin_endpoint(user = Depends(has_role("admin"))):
            ...
    """

    async def role_checker(
        current_user: Dict[str, Any] = Depends(get_current_user),
    ) -> Dict[str, Any]:
        user_roles = collect_user_role_names(current_user)

        if required_role not in user_roles:
            logger.warning(
                "Role denied. Required: %s. User has: %s",
                required_role,
                sorted(user_roles),
            )
            raise HTTPException(
                status_code=403,
                detail=f"User does not have required role: {required_role}",
            )

        return current_user

    return role_checker


def get_tenant_from_token(user_info: Dict[str, Any]) -> Optional[int]:
    """
    Extrae el tenant_id del token JWT

    El tenant_id puede estar en diferentes lugares según configuración de Keycloak:
    - En claims personalizados
    - En el realm
    - En atributos del usuario
    """
    # Intentar obtener de claims personalizados
    tenant_id = user_info.get("tenant_id")
    if not tenant_id:
        # Intentar obtener de atributos
        tenant_id = user_info.get("attributes", {}).get("tenant_id")

    if tenant_id:
        return int(tenant_id)

    return None


def resolve_hub_tenant_id_for_api(
    local_tenant_id: Optional[int], x_tenant_override: Optional[str]
) -> int:
    """
    ID de tenant en Hub (client_tenants) para llamadas a la API del Hub.

    Prioriza X-Tenant-Override (cookie SSO). Si hubo drift id Hub↔local,
    usa el mapeo inverso registrado en _ensure_tenant_synced.
    """
    if x_tenant_override and str(x_tenant_override).strip().isdigit():
        return int(str(x_tenant_override).strip())
    if local_tenant_id is None:
        return 0
    lid = int(local_tenant_id)
    return int(_tenant_id_hub_by_local.get(lid, lid))


def resolve_effective_tenant_id_from_user(current_user: Dict[str, Any]) -> Optional[int]:
    """
    tenant_id efectivo del usuario: claims del token vía get_tenant_from_token,
    luego fallback a ``tenant_id`` plano del Hub (puede venir como lista).

    Contrato Hub: no es obligatorio que todo usuario tenga ``tenant_id`` en /auth/me;
    el acceso por compañía puede basarse solo en RBAC local (ver ``user_has_app_company_membership``).
    """
    tid = get_tenant_from_token(current_user)
    if tid is not None:
        return int(tid)
    raw = current_user.get("tenant_id")
    if raw is None:
        return None
    if isinstance(raw, list) and raw:
        raw = raw[0]
    try:
        return int(raw)
    except (TypeError, ValueError):
        return None


def is_hub_admin(current_user: Dict[str, Any]) -> bool:
    """True si el Hub atestigua que el usuario es hub_admin (super-admin global)."""
    return bool(current_user.get("is_hub_admin"))


def _has_local_super_admin_role(
    db: "Session", user_id: Optional[str], company_id: Optional[int]
) -> bool:
    """
    True si el usuario tiene el rol local ``super_admin`` activo en la compañía.

    Sustituye al antiguo bypass por rol ``admin`` del realm Keycloak para
    autorización: la fuente de verdad es la BD local (``core.user_company_roles``
    + ``core.company_roles``), no claims del JWT. La promoción automática de
    admins de Keycloak a ``super_admin`` local sigue ocurriendo en el endpoint
    ``/permissions/me`` (bootstrap), por lo que un admin del realm que entre
    al sistema sigue obteniendo el bypass sin coordinación manual.
    """
    if not user_id or not company_id:
        return False
    try:
        from api.v1.modules.core.permissions.models import (
            CompanyRole,
            UserCompanyRole,
        )

        return (
            db.query(UserCompanyRole)
            .join(CompanyRole, CompanyRole.id == UserCompanyRole.company_role_id)
            .filter(
                UserCompanyRole.user_id == user_id,
                UserCompanyRole.company_id == company_id,
                UserCompanyRole.is_active == True,
                CompanyRole.code == "super_admin",
                CompanyRole.is_active == True,
            )
            .first()
            is not None
        )
    except Exception as exc:
        # Un fallo de BD aquí no debe escalar a acceso silenciosamente:
        # se loguea y se trata como "no es super_admin" (deniega bypass).
        logger.warning(
            "has_local_super_admin_role_failed",
            extra={
                "op": "has_local_super_admin_role",
                "user_id": user_id,
                "company_id": company_id,
                "error": str(exc),
            },
        )
        return False


def resolve_tenant_id_required(
    current_user: Dict[str, Any],
    db: Optional["Session"] = None,
    company_id: Optional[int] = None,
) -> Optional[int]:
    """
    Retorna el tenant_id efectivo o lanza 400.
    Hub admin sin tenant_id en token: resuelve desde la empresa si company_id está disponible,
    o retorna None como sentinel de acceso global (sin filtro de tenant).
    """
    tid = get_tenant_from_token(current_user)
    if tid is not None:
        return int(tid)
    raw = current_user.get("tenant_id")
    if isinstance(raw, list) and raw:
        raw = raw[0]
    if raw is not None:
        try:
            return int(raw)
        except (TypeError, ValueError):
            raise HTTPException(status_code=400, detail="Invalid tenant ID in token")

    if is_hub_admin(current_user):
        # Sin modelo de compañía en la plantilla → acceso global sin filtro de tenant.
        # Implementa la consulta a tu tabla de compañías si necesitas resolución exacta.
        return None

    raise HTTPException(status_code=400, detail="Tenant ID not found in user data")


def user_has_app_company_membership(
    db: Session, user_id: str, company_id: int
) -> bool:
    """
    True si el usuario tiene fila activa en RBAC de la app o en core.user_tenants
    para esa compañía (independiente del tenant en el JWT).
    """
    if not user_id:
        return False
    try:
        from api.v1.modules.core.permissions.models import (
            UserCompanyPermission,
            UserCompanyRole,
        )
        from api.v1.modules.core.user_tenant.models import UserTenant

        if (
            db.query(UserCompanyRole)
            .filter(
                UserCompanyRole.user_id == user_id,
                UserCompanyRole.company_id == company_id,
                UserCompanyRole.is_active == True,  # noqa: E712
            )
            .first()
        ):
            return True
        if (
            db.query(UserCompanyPermission)
            .filter(
                UserCompanyPermission.user_id == user_id,
                UserCompanyPermission.company_id == company_id,
                UserCompanyPermission.is_active == True,  # noqa: E712
            )
            .first()
        ):
            return True
        if (
            db.query(UserTenant)
            .filter(
                UserTenant.keycloak_user_id == user_id,
                UserTenant.company_id == company_id,
                UserTenant.is_active == True,  # noqa: E712
            )
            .first()
        ):
            return True
    except Exception as e:
        logger.error("Error checking app company membership: %s", e)
        return False
    return False


def collect_company_ids_from_app_membership(
    db: Session, user_id: str
) -> Set[int]:
    """IDs de compañía donde el usuario tiene rol, permiso directo o user_tenants."""
    ids: Set[int] = set()
    if not user_id:
        return ids
    try:
        from api.v1.modules.core.permissions.models import (
            UserCompanyPermission,
            UserCompanyRole,
        )
        from api.v1.modules.core.user_tenant.models import UserTenant

        for (cid,) in (
            db.query(UserCompanyRole.company_id)
            .filter(
                UserCompanyRole.user_id == user_id,
                UserCompanyRole.is_active == True,  # noqa: E712
            )
            .distinct()
            .all()
        ):
            ids.add(int(cid))
        for (cid,) in (
            db.query(UserCompanyPermission.company_id)
            .filter(
                UserCompanyPermission.user_id == user_id,
                UserCompanyPermission.is_active == True,  # noqa: E712
            )
            .distinct()
            .all()
        ):
            ids.add(int(cid))
        for (cid,) in (
            db.query(UserTenant.company_id)
            .filter(
                UserTenant.keycloak_user_id == user_id,
                UserTenant.is_active == True,  # noqa: E712
            )
            .distinct()
            .all()
        ):
            ids.add(int(cid))
    except Exception as e:
        logger.error("Error collecting company ids from membership: %s", e)
    return ids


def collect_user_role_names(current_user: Dict[str, Any]) -> Set[str]:
    """
    Roles del usuario: primero la lista ``roles`` del Hub (GET /api/v1/auth/me
    vía verify_token). Si no hay lista no vacía, se unen realm_access y
    resource_access del JWT Keycloak clásico.
    """
    names: Set[str] = set()
    hub_roles = current_user.get("roles")
    if isinstance(hub_roles, list):
        names.update(str(r) for r in hub_roles if r is not None)

    if names:
        return names

    realm = current_user.get("realm_access")
    if isinstance(realm, dict):
        names.update(str(r) for r in (realm.get("roles") or []) if r is not None)
    for client in (current_user.get("resource_access") or {}).values():
        if isinstance(client, dict):
            names.update(str(r) for r in (client.get("roles") or []) if r is not None)
    return names


def validate_company_access(
    db: Session, company_id: int, current_user: Dict[str, Any]
) -> bool:
    """
    Valida acceso a la compañía: (1) tenant del token/Hub alineado con la empresa, o
    (2) membership en la app (RBAC / user_tenants) para ese ``company_id``.

    El contrato con el Hub puede no incluir ``tenant_id`` para todos los usuarios;
    en ese caso el acceso se basa en asignaciones en PostgreSQL.
    """
    user_id = current_user.get("sub") or current_user.get("id")
    if user_id and user_has_app_company_membership(db, str(user_id), company_id):
        return True

    tenant_id = resolve_effective_tenant_id_from_user(current_user)
    if not tenant_id:
        return False

    # Sin modelo de compañía en la plantilla → delega solo en user_has_app_company_membership.
    # Implementa la consulta a tu tabla de compañías para validación estricta.
    return True


def validate_access_to_resource(
    db: Session,
    company_id: int,
    current_user: Dict[str, Any],
    required_permissions: Optional[list[str]] = None,
    require_all: bool = True,
) -> Optional[int]:
    """
    Valida que el usuario tenga acceso a un recurso específico basado en company_id
    y regresa el tenant_id. Opcionalmente verifica permisos.

    Args:
        db: Sesión de base de datos
        company_id: company_id asociado al recurso
        current_user: Información del usuario actual desde el token
        required_permissions: Lista opcional de permisos requeridos. Si es None, no verifica permisos.
        require_all: Si True, requiere TODOS los permisos. Si False, requiere AL MENOS UNO.

    Returns:
        tenant_id si el usuario tiene acceso

    Raises:
        HTTPException: Si no hay tenant_id, no tiene acceso o no tiene los permisos requeridos
    """

    tenant_id = resolve_effective_tenant_id_from_user(current_user)

    # Si el usuario no trae tenant en el token (p. ej. hub_admin del workspace),
    # resolverlo desde la compañía activa (a76.company.tenant_id). Permite operar
    # por compañía seleccionada cuando el token no está ligado a un tenant.
    if tenant_id is None and company_id:
        try:
            from sqlalchemy import text as _text
            row = db.execute(
                _text("SELECT tenant_id FROM a76.company WHERE id = :c"), {"c": company_id}
            ).first()
            if row and row[0] is not None:
                tenant_id = int(row[0])
        except Exception as exc:
            logger.warning("no se pudo resolver tenant desde company_id=%s: %s", company_id, exc)

    # Bypass de checks de permisos: hub_admin (atestado por el Hub en /auth/me)
    # o rol local "super_admin" en la compañía (fuente de verdad: BD de a76).
    # Se reemplazó el antiguo "admin" in realm_access.roles para que la
    # autorización deje de depender de claims del JWT.
    user_id = current_user.get("sub") or current_user.get("id")
    is_global_admin = is_hub_admin(current_user) or _has_local_super_admin_role(
        db, user_id, company_id
    )

    # 🚪 EXCEPCIÓN ESPECIAL: Si es el endpoint /me, permitimos el paso para el Bootstrap
    # Detectamos si no se requieren permisos (típico de /me)
    is_me_endpoint = required_permissions is None

    if not is_global_admin and not is_me_endpoint:
        if not validate_company_access(db, company_id, current_user):
            raise HTTPException(status_code=403, detail="Access denied to this company")
    
    # Sin modelo de compañía en la plantilla no se puede resolver tenant_id desde company.
    # Implementa esta lógica cuando definas tu tabla de compañías.

    # Si aún no hay tenant_id y no es admin, error 400
    if not tenant_id and not is_global_admin and not is_me_endpoint:
        raise HTTPException(status_code=400, detail="Tenant ID not found in token")

    # Verificar permisos locales
    if required_permissions:
        if is_global_admin:
            # hub_admin / super_admin local: siempre debe tener tenant_id resuelto
            # cuando se exigen permisos; retornar 1 silenciosamente sería acceso
            # al tenant equivocado.
            if tenant_id is None:
                raise HTTPException(
                    status_code=400,
                    detail="No se pudo resolver el tenant_id para la empresa especificada",
                )
            return int(tenant_id)

        from api.v1.modules.core.permissions.service import PermissionService
        user_id = current_user.get("sub") or current_user.get("id")
        permission_service = PermissionService(db)
        
        has_access = False
        if require_all:
            has_access = permission_service.has_all_permissions(user_id, company_id, required_permissions)
        else:
            has_access = permission_service.has_any_permission(user_id, company_id, required_permissions)

        # 🛡️ MEJORA DEV: Auto-bootstrap si falla el acceso en desarrollo
        if not has_access and settings.ENVIRONMENT == "development":
            try:
                # Si el usuario no tiene roles asignados, intentamos el bootstrap
                # bootstrap_super_admin solo asigna el rol si no tiene ninguno (o es admin)
                permission_service.bootstrap_super_admin(user_id, company_id)
                # Re-validar
                if require_all:
                    has_access = permission_service.has_all_permissions(user_id, company_id, required_permissions)
                else:
                    has_access = permission_service.has_any_permission(user_id, company_id, required_permissions)
                
                if has_access:
                    logger.info(
                        "Auto-bootstrap exitoso para user_id=%s company_id=%s", user_id, company_id
                    )
            except Exception as e:
                logger.warning(
                    "Error en auto-bootstrap de seguridad user_id=%s company_id=%s: %s",
                    user_id, company_id, e,
                )

        if not has_access:
            raise HTTPException(status_code=403, detail="Permission denied")

    # Nunca sustituir tenant_id=None/0 silenciosamente — un valor inválido aquí
    # significaría acceso al tenant equivocado. Si llegamos aquí sin tenant_id
    # válido para un usuario no-admin, es un estado inconsistente que debe fallar.
    if not isinstance(tenant_id, int) or tenant_id <= 0:
        if not is_global_admin:
            raise HTTPException(
                status_code=400,
                detail="No se pudo determinar el tenant_id para la empresa especificada",
            )
    return tenant_id  # puede ser None solo para hub_admin sin required_permissions (acceso global)
