"""
Sesión local del CRM (patrón SIWEB).

Emite y valida un JWT de sesión propio (HS256, firmado con SECRET_KEY) que
transporta la identidad YA verificada por Keycloak/Hub. Desacopla la sesión de la
app del token KC de 60s: la app valida esta sesión local (sin ir al Hub) durante
su ventana de inactividad, de modo que el refresh del token KC solo se intenta al
expirar la sesión local — no cada ~60s. Esto elimina el bucle de login.

Se RESPETA la revocación central: si el Hub rechaza el refresh, la sesión termina
(no hay re-emisión de fallback).

Marcadores del token:
  - source: "local" + crm_session: True  → distingue de tokens KC (RS256) y del
    token dev-local (dev_local: True).
  - sst (session start time, epoch seg)   → fija la vida ABSOLUTA máxima (cap).
  - exp                                    → sliding por inactividad (idle); se
    re-emite en cada refresh mientras no se supere el cap.

Seguridad: es un desacople CONSCIENTE de la revocación central de KC (OWASP A07).
Se acota con idle corto (= ssoSessionIdleTimeout) y cap absoluto
(= ssoSessionMaxLifespan); el logout elimina la sesión de valkey.
"""

from datetime import datetime, timezone
from typing import Any, Dict, Optional

from jose import JWTError, jwt

from core.config import settings

# Claims de identidad que se propagan del token KC a la sesión local.
_IDENTITY_CLAIMS = (
    "sub", "email", "preferred_username", "username", "name",
    "given_name", "family_name", "first_name", "last_name",
    "tenant_id", "tenant_slug", "roles", "permissions",
    "is_hub_admin", "avatar_url",
)


def _now_epoch() -> int:
    return int(datetime.now(timezone.utc).timestamp())


def mint_session_token(claims: Dict[str, Any], session_start: Optional[int] = None) -> str:
    """
    Emite un JWT de sesión local a partir de los claims (verificados) del usuario.
    `session_start` (epoch seg) fija el inicio de sesión para el cap absoluto; si
    no se provee, se usa el momento actual (sesión nueva).
    """
    now = _now_epoch()
    sst = int(session_start) if session_start else now

    payload: Dict[str, Any] = {
        k: claims[k] for k in _IDENTITY_CLAIMS if claims.get(k) is not None
    }
    payload.update({
        "source": "local",
        "crm_session": True,
        "sst": sst,
        "iat": now,
        "exp": now + settings.SESSION_IDLE_MINUTES * 60,
    })
    return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")


def verify_session_token(token: str) -> Optional[Dict[str, Any]]:
    """
    Valida un JWT de sesión local. Retorna los claims si es válido, no expiró por
    inactividad y no superó el cap absoluto de vida; None en cualquier otro caso.
    Nunca lanza (para poder encadenar con la validación contra el Hub).
    """
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
    except JWTError:
        return None

    # Solo aceptamos tokens de sesión local del CRM (no KC, no dev-local).
    if not payload.get("crm_session") or payload.get("source") != "local":
        return None

    # Cap absoluto de vida de sesión (independiente del sliding por idle).
    sst = payload.get("sst")
    if isinstance(sst, (int, float)):
        if _now_epoch() - int(sst) > settings.SESSION_MAX_HOURS * 3600:
            return None

    return payload


def session_start_of(payload: Dict[str, Any]) -> Optional[int]:
    """Extrae el epoch de inicio de sesión (sst) de un payload de sesión local."""
    sst = payload.get("sst")
    return int(sst) if isinstance(sst, (int, float)) else None
