"""Lightweight in-memory rate limiting for reward-related endpoints.

The project ships no rate-limit library and no shared cache, so this is a small,
dependency-free fixed-window limiter modelled on the reward-token replay guard
(:mod:`app.advertisements.reward_token`). It exists to blunt automated abuse of the
reward mint/claim/beacon endpoints — the limits are deliberately generous so a
normal user is never affected.

Per-process only (like the replay guard); a shared cache (Redis/Memcached) can
replace the store later without changing callers. Keyed per ``(bucket, identity)``
so limits are independent per endpoint and per authenticated user.
"""

from __future__ import annotations

import threading
import time


class _RateLimiter:
    """Thread-safe fixed-window counter. Stale windows are pruned on each hit."""

    def __init__(self) -> None:
        self._hits: dict[str, tuple[int, int]] = {}  # key -> (window_index, count)
        self._lock = threading.Lock()

    def hit(self, key: str, limit: int, window_seconds: int) -> bool:
        """Register one request for ``key``; return True if allowed, False if over."""
        if limit <= 0 or window_seconds <= 0:
            return True
        window = int(time.time() // window_seconds)
        with self._lock:
            self._prune(window)
            stored_window, count = self._hits.get(key, (window, 0))
            if stored_window != window:  # new window → reset the counter
                count = 0
            if count >= limit:
                self._hits[key] = (window, count)  # keep blocking for the rest of the window
                return False
            self._hits[key] = (window, count + 1)
            return True

    def _prune(self, current_window: int) -> None:
        for key in [k for k, (w, _) in self._hits.items() if w < current_window]:
            del self._hits[key]

    def reset(self) -> None:
        with self._lock:
            self._hits.clear()


_LIMITER = _RateLimiter()


def check_rate_limit(bucket: str, identity, *, limit: int, window_seconds: int = 60) -> bool:
    """Return True if this request is within the limit, False if it exceeds it.

    ``bucket`` names the protected endpoint (e.g. ``"reward_claim"``); ``identity``
    is typically the authenticated user id so limits are per-user.
    """
    return _LIMITER.hit(f"{bucket}:{identity}", limit, window_seconds)


def reset_rate_limits() -> None:
    """Clear all rate-limit counters (used by tests)."""
    _LIMITER.reset()
