"""Shared Redis-backed rate limiter (INDL-55).

Extracted from the previously private `_rate_limit`/`_get_rate_limit_redis`
helpers in `src/apps/public/router.py`, which were IP-keyed only. This
version accepts a fully-formed `bucket` string and does NOT derive any
identity (IP, user id, etc.) itself — callers are responsible for folding
whatever identity dimension they need into the bucket string before calling,
e.g. `f"ai-search:{ip}"` (public, IP-keyed) or `f"change-password:{user.id}"`
(auth, user-keyed per AC-10).

Behavior preserved byte-for-byte from the original `_rate_limit`:
- Module-level pooled `redis.asyncio` client via `settings.REDIS_URL`.
- Key scheme `f"rate:{bucket}"`.
- Atomic INCR + first-hit EXPIRE (only when count == 1) so a crash between
  the two calls can't leave a TTL-less key that rate-limits forever.
- Raises HTTPException(429, ...) with a `Retry-After` header once over limit.
- Fails open on any Redis exception (deliberate existing tradeoff — a public
  or self-service endpoint should not become fully unavailable just because
  Redis is briefly down).
"""
from fastapi import HTTPException

from src.core.config import settings

_rate_limit_redis = None


def _get_rate_limit_redis():
    global _rate_limit_redis
    if _rate_limit_redis is None:
        import redis.asyncio as aioredis

        _rate_limit_redis = aioredis.from_url(settings.REDIS_URL, decode_responses=True)
    return _rate_limit_redis


async def check_rate_limit(bucket: str, limit: int, window: int = 60) -> None:
    """Best-effort rate limit backed by Redis. Non-blocking if Redis is
    unavailable (fails open).

    `bucket` must already contain the full identity to key on (IP, user id,
    etc.) — this function does not append anything to it.
    """
    try:
        r = _get_rate_limit_redis()
        key = f"rate:{bucket}"
        # Atomic INCR + first-hit EXPIRE via pipeline so a crash between the two
        # can't leave a TTL-less key that rate-limits the bucket forever.
        count = await r.incr(key)
        if count == 1:
            await r.expire(key, window)
        if count > limit:
            raise HTTPException(
                status_code=429,
                detail="Too many requests. Please try again later.",
                headers={"Retry-After": str(window)},
            )
    except HTTPException:
        raise
    except Exception:
        pass  # Redis down → allow request.
