"""Redis-backed JWT jti denylist and per-user auth_epoch for instant access revocation."""

from __future__ import annotations

import time

from app.core.config import settings
from app.core.redis import get_redis_client

AUTH_EPOCH_PREFIX = "auth:epoch:"
JWT_DENY_PREFIX = "jwt:deny:"

_memory_epochs: dict[str, int] = {}
_memory_denied_jti: dict[str, float] = {}


def _purge_expired_memory_jtis() -> None:
    now = time.time()
    expired = [jti for jti, exp in _memory_denied_jti.items() if exp <= now]
    for jti in expired:
        _memory_denied_jti.pop(jti, None)


def ensure_jwt_revocation_backend() -> None:
    if settings.app_env.lower() != "production":
        return
    if get_redis_client() is None:
        raise RuntimeError("Redis is required for JWT revocation in production")


def get_auth_epoch(user_id: str) -> int:
    client = get_redis_client()
    if client is not None:
        try:
            value = client.get(f"{AUTH_EPOCH_PREFIX}{user_id}")
            return int(value) if value is not None else 0
        except Exception:
            if settings.app_env.lower() == "production":
                raise
    return _memory_epochs.get(user_id, 0)


def bump_auth_epoch(user_id: str) -> int:
    client = get_redis_client()
    if client is not None:
        try:
            return int(client.incr(f"{AUTH_EPOCH_PREFIX}{user_id}"))
        except Exception:
            if settings.app_env.lower() == "production":
                raise
    next_epoch = _memory_epochs.get(user_id, 0) + 1
    _memory_epochs[user_id] = next_epoch
    return next_epoch


def deny_jti(jti: str, exp: int) -> None:
    if not jti:
        return
    ttl = max(int(exp - time.time()), 1)
    client = get_redis_client()
    if client is not None:
        try:
            client.setex(f"{JWT_DENY_PREFIX}{jti}", ttl, "1")
            return
        except Exception:
            if settings.app_env.lower() == "production":
                raise
    _purge_expired_memory_jtis()
    _memory_denied_jti[jti] = time.time() + ttl


def is_jti_denied(jti: str) -> bool:
    if not jti:
        return False
    client = get_redis_client()
    if client is not None:
        try:
            return bool(client.exists(f"{JWT_DENY_PREFIX}{jti}"))
        except Exception:
            if settings.app_env.lower() == "production":
                return True
    _purge_expired_memory_jtis()
    return jti in _memory_denied_jti


def revoke_access_token(token: str) -> None:
    from app.core.security import decode_access_token

    try:
        payload = decode_access_token(token)
    except Exception:
        return
    jti = payload.get("jti")
    exp = payload.get("exp")
    if jti and exp:
        deny_jti(str(jti), int(exp))


def validate_access_claims(payload: dict) -> None:
    user_id = payload.get("sub")
    if not user_id:
        raise ValueError("INVALID_TOKEN")
    jti = payload.get("jti")
    if jti and is_jti_denied(str(jti)):
        raise ValueError("TOKEN_REVOKED")
    token_epoch = int(payload.get("auth_epoch", 0))
    if token_epoch != get_auth_epoch(str(user_id)):
        raise ValueError("TOKEN_REVOKED")
