"""
Redis-backed JWT/Identity cache for DTA services (shared Redis).
All services using this module share the same Redis instance for JWT cache.
"""
import json
import logging
import os
from typing import Any, Dict, Optional

from .constants import DTARoles
from .exceptions import AuthError
from .expiry import is_identity_token_expired

logger = logging.getLogger(__name__)

_redis_client: Optional[Any] = None


def _is_local_identity_env_override_enabled() -> bool:
    """
    Whether Identity authorization should use local env overrides instead of Redis/RAC.

    This is intentionally conservative:
    - Requires DTA_ENVIRONMENT to be "development" or "local"
    - Requires DTA_ROLE_USER to be set
    """
    environment = (os.getenv("DTA_ENVIRONMENT") or "").strip().lower()
    if environment not in {"development", "local"}:
        return False
    return bool((os.getenv("DTA_ROLE_USER") or "").strip())


def _get_local_identity_dta_roles() -> list[dict]:
    role = (os.getenv("DTA_ROLE_USER") or "").strip()
    if not role:
        raise AuthError("DTA_ROLE_USER not configured for local Identity auth", status_code=500)

    valid_roles = {r.value for r in DTARoles}
    if role not in valid_roles:
        raise AuthError(
            f"Invalid DTA_ROLE_USER '{role}'. Expected one of: {', '.join(sorted(valid_roles))}",
            status_code=500,
        )

    tenant_id = (os.getenv("DTA_TOTVS_TENANT_ID") or "").strip()
    tenant_name = (os.getenv("DTA_TENANT_NAME") or "").strip()
    tenant = tenant_id or tenant_name
    if not tenant:
        raise AuthError(
            "DTA_TOTVS_TENANT_ID or DTA_TENANT_NAME must be configured for local Identity auth",
            status_code=500,
        )

    role_entry: dict = {"tenant": tenant, "role": role}
    if tenant_id:
        role_entry["tenant_id"] = tenant_id
    if tenant_name:
        role_entry["tenant_name"] = tenant_name
    return [role_entry]


def _get_redis_client():
    """Redis client for JWT cache (env AUTH_REDIS_*)."""
    global _redis_client
    if _redis_client is None:
        try:
            from redis import ConnectionPool, Redis
        except ImportError as err:
            raise ImportError(
                "Redis is required for JWT cache. Install with: pip install totvs-dta-utils[auth]"
            ) from err
        pool = ConnectionPool(
            host=os.environ.get("AUTH_REDIS_HOST", "redis"),
            port=int(os.environ.get("AUTH_REDIS_PORT", "6379")),
            db=int(os.environ.get("AUTH_REDIS_DB", "0")),
            password=os.environ.get("AUTH_REDIS_PASSWORD") or None,
            decode_responses=True,
            max_connections=int(os.environ.get("AUTH_REDIS_MAX_CONNECTIONS", "10")),
            socket_timeout=float(os.environ.get("AUTH_REDIS_TIMEOUT", "10")),
            socket_connect_timeout=float(os.environ.get("AUTH_REDIS_TIMEOUT", "10")),
        )
        _redis_client = Redis(connection_pool=pool)
    return _redis_client


def get_jwt_cached(key: str, *, _redis: Optional[Any] = None) -> Optional[Dict[str, Any]]:
    """Get value from JWT/Identity cache. Key = token string."""
    client = _redis if _redis is not None else _get_redis_client()
    try:
        value = client.get(key)
        if value:
            return json.loads(value)
        return None
    except Exception as e:
        logger.debug("JWT cache get failed for key %s: %s", key[:20] + "..." if len(key) > 20 else key, e)
        return None


def set_jwt_cached(
    key: str,
    value: Dict[str, Any],
    ex: Optional[int] = None,
    *,
    _redis: Optional[Any] = None,
) -> bool:
    """Set value in JWT/Identity cache. ex = TTL in seconds."""
    client = _redis if _redis is not None else _get_redis_client()
    try:
        payload = json.dumps(value, default=str)
        if ex is not None:
            client.setex(key, ex, payload)
        else:
            client.set(key, payload)
        return True
    except Exception as e:
        logger.debug("JWT cache set failed for key %s: %s", key[:20] + "..." if len(key) > 20 else key, e)
        return False


def delete_jwt_cached(key: str, *, _redis: Optional[Any] = None) -> bool:
    """Delete key from JWT/Identity cache."""
    client = _redis if _redis is not None else _get_redis_client()
    try:
        client.delete(key)
        return True
    except Exception as e:
        logger.debug("JWT cache delete failed for key %s: %s", key[:20] + "..." if len(key) > 20 else key, e)
        return False


def get_identity_user_from_cache(
    token: str,
    *,
    _redis: Optional[Any] = None,
) -> Dict[str, Any]:
    """Normalized user dict from cache (name, identifier, dta_roles, exp, org). Raises AuthError if missing or expired."""
    if _is_local_identity_env_override_enabled():
        from .jwt import DEFAULT_JWKS_BASE_URL, validate_identity_jwt

        payload = validate_identity_jwt(token,
                                        jwks_base_url=DEFAULT_JWKS_BASE_URL)
        email = (payload.get("email") or "").strip()
        if not email:
            raise AuthError("Email not found in Identity token",
                            status_code=401)

        return {
            "name": payload.get("fullName") or "",
            "identifier": email,
            "dta_roles": _get_local_identity_dta_roles(),
            "exp": payload.get("exp"),
            "org": "totvs",
        }

    """Normalized user dict from cache (name, identifier, dta_roles, exp, org, super). Raises AuthError if missing or expired."""
    cached = get_jwt_cached(token, _redis=_redis)
    if not cached:
        delete_jwt_cached(token, _redis=_redis)
        raise AuthError("Token not found or expired", status_code=401)

    if is_identity_token_expired(cached):
        delete_jwt_cached(token, _redis=_redis)
        raise AuthError("Token expired", status_code=401)

    return {
        "name": cached.get("name") or "",
        "identifier": cached.get("email") or cached.get("identifier") or "",
        "dta_roles": cached.get("dta_roles") or [],
        "exp": cached.get("exp"),
        "org": cached.get("org") or "totvs",
        "super": bool(cached.get("super", False)),
    }
