"""
Store de sesión en Valkey/Redis (patrón SIWEB).

Guarda los tokens de Keycloak (access + refresh) FUERA del browser, indexados por
un session_id opaco. La app usa la sesión local firmada (ver core.local_session)
para su propia auth; los tokens KC de aquí solo se usan para llamadas al Hub
(provisioning, my-apps, my-tenants), refrescándolos best-effort.

Fail-silent: si Valkey no está disponible, las operaciones degradan a None/no-op
y la sesión local firmada sigue sosteniendo la app.
"""

import json
import logging
import uuid
from typing import Optional

from core.config import settings

try:
    import redis  # type: ignore
except Exception:  # pragma: no cover - redis es opcional en algunos entornos
    redis = None  # type: ignore

logger = logging.getLogger(__name__)

_KEY_PREFIX = "crm:session:"
_client = None


def _get_client():
    """Cliente Redis/Valkey compartido (perezoso). None si no está disponible."""
    global _client
    if redis is None:
        return None
    if _client is None:
        try:
            _client = redis.Redis.from_url(settings.VALKEY_URL, decode_responses=True)
        except Exception as exc:
            logger.warning("session_store_init_failed: %s", exc)
            return None
    return _client


def _ttl_seconds() -> int:
    # La sesión en valkey vive como máximo lo que la vida absoluta de la sesión.
    return settings.SESSION_MAX_HOURS * 3600


def create_session(access_token: str, refresh_token: str, session_start: int) -> Optional[str]:
    """Crea una sesión con los tokens KC y devuelve el session_id (o None si Valkey no está)."""
    client = _get_client()
    if client is None:
        return None
    session_id = str(uuid.uuid4())
    data = json.dumps({
        "access_token": access_token,
        "refresh_token": refresh_token or "",
        "sst": int(session_start),
    })
    try:
        client.setex(f"{_KEY_PREFIX}{session_id}", _ttl_seconds(), data)
        return session_id
    except Exception as exc:
        logger.warning("session_store_create_failed: %s", exc)
        return None


def get_session(session_id: str) -> Optional[dict]:
    """Devuelve {access_token, refresh_token, sst} de la sesión, o None."""
    client = _get_client()
    if client is None or not session_id:
        return None
    try:
        raw = client.get(f"{_KEY_PREFIX}{session_id}")
        return json.loads(raw) if raw else None
    except Exception as exc:
        logger.warning("session_store_get_failed: %s", exc)
        return None


def update_session_tokens(session_id: str, access_token: str, refresh_token: str) -> None:
    """Actualiza los tokens KC de una sesión existente conservando su TTL y su sst."""
    client = _get_client()
    if client is None or not session_id:
        return
    try:
        key = f"{_KEY_PREFIX}{session_id}"
        ttl = client.ttl(key)
        if ttl and ttl > 0:
            existing = client.get(key)
            sst = json.loads(existing).get("sst") if existing else None
            data = json.dumps({
                "access_token": access_token,
                "refresh_token": refresh_token or "",
                "sst": sst,
            })
            client.setex(key, ttl, data)
    except Exception as exc:
        logger.warning("session_store_update_failed: %s", exc)


def delete_session(session_id: str) -> None:
    """Elimina la sesión (logout). Fail-silent."""
    client = _get_client()
    if client is None or not session_id:
        return
    try:
        client.delete(f"{_KEY_PREFIX}{session_id}")
    except Exception as exc:
        logger.warning("session_store_delete_failed: %s", exc)
