import logging
import os
from typing import Optional, Set, Iterable

from core.config import settings

try:
    import redis  # type: ignore
except Exception:  # pragma: no cover - redis is optional in some envs
    redis = None  # type: ignore


logger = logging.getLogger(__name__)


class PermissionCache:
    """
    Caché de permisos basada en Valkey/Redis.

    - Clave por combinación (tenant_id, company_id, user_id)
    - Guarda el set de códigos de permiso como string CSV
    - TTL controlado por configuración (`PERMISSION_CACHE_TTL_SECONDS`)

    Todas las operaciones fallan en modo silencioso para no afectar el flujo
    principal de la aplicación si Redis/Valkey no está disponible.
    """

    KEY_PREFIX = "permissions:v1"

    def __init__(self, client: "redis.Redis | None" = None) -> None:  # type: ignore[name-defined]
        self.ttl_seconds = int(getattr(settings, "PERMISSION_CACHE_TTL_SECONDS", 300) or 300)
        enabled_flag = bool(getattr(settings, "PERMISSION_CACHE_ENABLED", True))

        # Si redis no está instalado, deshabilitar caché
        if redis is None:
            self._client = None
            self.enabled = False
            return

        if client is not None:
            self._client = client
            self.enabled = enabled_flag
            return

        url = (
            os.getenv("VALKEY_URL")
            or os.getenv("REDIS_URL")
            or getattr(settings, "VALKEY_URL", "redis://valkey:6379/0")
        )

        try:
            # decode_responses=True para trabajar con str en lugar de bytes
            self._client = redis.Redis.from_url(url, decode_responses=True)
            # Probar conexión rápida (no crítico si falla)
            if enabled_flag:
                try:
                    self._client.ping()
                    self.enabled = True
                except Exception:
                    logger.warning(
                        "permission_cache_ping_failed",
                        extra={"url": url},
                    )
                    self.enabled = False
            else:
                self.enabled = False
        except Exception as exc:
            logger.warning(
                "permission_cache_init_failed",
                extra={"url": url, "error": str(exc)},
            )
            self._client = None
            self.enabled = False

    # ------------------------------------------------------------------
    # Helpers de clave
    # ------------------------------------------------------------------
    def build_permissions_key(
        self,
        tenant_id: Optional[int],
        company_id: int,
        user_id: str,
    ) -> str:
        """
        Construye la clave única de caché para un usuario en una compañía.
        """
        tenant_part = str(tenant_id) if tenant_id is not None else "global"
        return f"{self.KEY_PREFIX}:tenant:{tenant_part}:company:{company_id}:user:{user_id}"

    # ------------------------------------------------------------------
    # Operaciones de lectura/escritura
    # ------------------------------------------------------------------
    def get_permissions(
        self,
        cache_key: str,
        **context: object,
    ) -> Optional[Set[str]]:
        """
        Obtiene el set de permisos desde caché.

        Devuelve:
            - set[str] si hay caché válido
            - None si no hay entrada o si la caché está deshabilitada
        """
        if not self.enabled or not self._client:
            return None

        try:
            raw = self._client.get(cache_key)
            if raw is None:
                return None
            if not raw:
                return set()
            return set(raw.split(","))
        except Exception as exc:
            logger.warning(
                "permission_cache_get_failed",
                extra={"cache_key": cache_key, "error": str(exc), **context},
            )
            return None

    def set_permissions(
        self,
        cache_key: str,
        permissions: Iterable[str],
        **context: object,
    ) -> None:
        """
        Escribe el set de permisos en caché con TTL.
        """
        if not self.enabled or not self._client:
            return

        try:
            value = ",".join(sorted(set(permissions)))
            self._client.setex(cache_key, self.ttl_seconds, value)
        except Exception as exc:
            logger.warning(
                "permission_cache_set_failed",
                extra={"cache_key": cache_key, "error": str(exc), **context},
            )

    # ------------------------------------------------------------------
    # Invalidaciones
    # ------------------------------------------------------------------
    def _delete_pattern(self, pattern: str) -> None:
        """
        Elimina todas las llaves que coincidan con un patrón.
        """
        if not self.enabled or not self._client:
            return

        try:
            # scan_iter evita bloquear Redis en grandes keyspaces
            keys = list(self._client.scan_iter(match=pattern))
            if keys:
                self._client.delete(*keys)
        except Exception as exc:
            logger.warning(
                "permission_cache_delete_pattern_failed",
                extra={"pattern": pattern, "error": str(exc)},
            )

    def invalidate_user(
        self,
        tenant_id: Optional[int],
        company_id: int,
        user_id: str,
    ) -> None:
        """
        Invalida el caché de permisos para un usuario específico.
        """
        if not self.enabled or not self._client:
            return

        cache_key = self.build_permissions_key(tenant_id, company_id, user_id)
        try:
            self._client.delete(cache_key)
        except Exception as exc:
            logger.warning(
                "permission_cache_invalidate_user_failed",
                extra={
                    "cache_key": cache_key,
                    "tenant_id": tenant_id,
                    "company_id": company_id,
                    "user_id": user_id,
                    "error": str(exc),
                },
            )

    def invalidate_company(self, company_id: int) -> None:
        """
        Invalida el caché de permisos para todos los usuarios de una compañía.
        """
        pattern = f"{self.KEY_PREFIX}:tenant:*:company:{company_id}:user:*"
        self._delete_pattern(pattern)

    def invalidate_all(self) -> None:
        """
        Elimina TODAS las entradas del caché de permisos.
        Úsese con precaución (ej. cleanup_cli).
        """
        pattern = f"{self.KEY_PREFIX}:*"
        self._delete_pattern(pattern)

