from __future__ import annotations

from typing import Any, Awaitable, Callable, Optional

from bot.infra.redis import cache_delete, cache_delete_pattern, redis_key
from bot.utils.ttl_cache import AsyncTTLCache
from config.settings import Settings

_WEBAPP_USER_PAYLOAD_CACHES: dict[tuple[int, str, int], AsyncTTLCache] = {}


def reset_webapp_settings_cache(app: Any) -> None:
    cache = app.get("webapp_settings_cache") if hasattr(app, "get") else None
    if isinstance(cache, dict):
        cache["ts"] = 0.0
        cache["data"] = {}


def reset_subscription_guides_cache(app: Any) -> None:
    cache = app.get("subscription_guides_config_cache") if hasattr(app, "get") else None
    if isinstance(cache, dict):
        cache["fingerprint"] = None
        cache["status"] = None


def _payload_namespaces(include_devices: bool = False) -> tuple[str, ...]:
    return ("me", "devices") if include_devices else ("me",)


def _webapp_user_payload_cache(
    settings: Settings,
    namespace: str,
    ttl_seconds: int,
) -> Optional[AsyncTTLCache]:
    ttl = max(0, int(ttl_seconds or 0))
    if ttl <= 0:
        return None
    cache_key = (id(settings), namespace, ttl)
    cache = _WEBAPP_USER_PAYLOAD_CACHES.get(cache_key)
    if cache is None:
        cache = AsyncTTLCache(
            ttl_seconds=ttl,
            settings=settings,
            namespace=f"webapp:{namespace}",
        )
        _WEBAPP_USER_PAYLOAD_CACHES[cache_key] = cache
    return cache


async def webapp_cached_user_payload(
    settings: Settings,
    namespace: str,
    user_id: int,
    ttl_seconds: int,
    loader: Callable[[], Awaitable[Any]],
) -> Any:
    cache = _webapp_user_payload_cache(settings, namespace, ttl_seconds)
    if cache is None:
        return await loader()
    return await cache.get_or_load(str(int(user_id)), loader)


def invalidate_local_webapp_user_payload(
    settings: Settings,
    namespace: str,
    user_id: int,
) -> None:
    key = str(int(user_id))
    for (settings_id, cache_namespace, _ttl), cache in tuple(_WEBAPP_USER_PAYLOAD_CACHES.items()):
        if settings_id == id(settings) and cache_namespace == namespace:
            cache.invalidate(key)


def invalidate_all_local_webapp_user_payloads(
    settings: Settings,
    namespace: Optional[str] = None,
    *,
    include_devices: Optional[bool] = None,
) -> None:
    if include_devices is not None:
        namespaces: Optional[set[str]] = set(_payload_namespaces(include_devices))
    elif namespace is not None:
        namespaces = {namespace}
    else:
        namespaces = None

    for (settings_id, cache_namespace, _ttl), cache in tuple(_WEBAPP_USER_PAYLOAD_CACHES.items()):
        if settings_id != id(settings):
            continue
        if namespaces is not None and cache_namespace not in namespaces:
            continue
        cache.invalidate()


async def invalidate_webapp_user_caches(
    settings: Settings,
    *user_ids: Optional[int],
    include_devices: bool = False,
) -> None:
    keys: list[str] = []
    seen: set[int] = set()
    for raw_user_id in user_ids:
        if raw_user_id is None:
            continue
        try:
            user_id = int(raw_user_id)
        except (TypeError, ValueError):
            continue
        if user_id in seen:
            continue
        seen.add(user_id)
        keys.append(redis_key(settings, "cache", "webapp", "me", user_id))
        invalidate_local_webapp_user_payload(settings, "me", user_id)
        if include_devices:
            keys.append(redis_key(settings, "cache", "webapp", "devices", user_id))
            invalidate_local_webapp_user_payload(settings, "devices", user_id)
    if keys:
        await cache_delete(settings, *keys)


async def invalidate_all_webapp_user_payloads(
    settings: Settings,
    *,
    include_devices: bool = False,
) -> None:
    for namespace in _payload_namespaces(include_devices):
        invalidate_all_local_webapp_user_payloads(settings, namespace=namespace)
        try:
            pattern = redis_key(settings, "cache", "webapp", namespace, "*")
            await cache_delete_pattern(settings, pattern)
        except Exception:
            continue


async def invalidate_all_webapp_user_caches(
    settings: Settings,
    *,
    include_devices: bool = False,
) -> None:
    await invalidate_all_webapp_user_payloads(settings, include_devices=include_devices)
