"""
Rate limiter for QA Copilot (Roadmap 4.6).

In-memory sliding window rate limiter using dict of deques.
Thread-safe. Background cleanup of stale entries.
"""
from __future__ import annotations

import os
import threading
import time
from collections import deque

_store: dict[str, deque] = {}
_lock = threading.Lock()
_enabled = os.environ.get("QA_RATE_LIMIT", "1") != "0"

# Configurable limits (requests per minute)
RATE_LIMIT_AUTH = int(os.environ.get("QA_RATE_LIMIT_AUTH", "10"))
RATE_LIMIT_GENERATE = int(os.environ.get("QA_RATE_LIMIT_GENERATE", "30"))
RATE_LIMIT_WEBHOOK = int(os.environ.get("QA_RATE_LIMIT_WEBHOOK", "20"))
RATE_LIMIT_CONTACT = 5  # hardcoded


def check_rate_limit(key: str, max_requests: int, window_seconds: int = 60) -> bool:
    """Check if a request is allowed under the rate limit.

    Args:
        key: Unique identifier (e.g., "auth:192.168.1.1" or "gen:user-123")
        max_requests: Maximum requests allowed in the window
        window_seconds: Time window in seconds (default 60)

    Returns:
        True if the request is allowed, False if rate limit exceeded.
    """
    if not _enabled:
        return True

    now = time.monotonic()
    cutoff = now - window_seconds

    with _lock:
        if key not in _store:
            _store[key] = deque()

        q = _store[key]
        # Remove expired entries
        while q and q[0] < cutoff:
            q.popleft()

        if len(q) >= max_requests:
            return False

        q.append(now)
        return True


def get_retry_after(key: str, window_seconds: int = 60) -> int:
    """Get seconds until the oldest request in the window expires."""
    now = time.monotonic()
    with _lock:
        q = _store.get(key)
        if not q:
            return 0
        return max(0, int(window_seconds - (now - q[0])) + 1)


def cleanup_stale(max_age: int = 300) -> int:
    """Remove keys with no requests in the last max_age seconds. Returns count removed."""
    now = time.monotonic()
    cutoff = now - max_age
    removed = 0
    with _lock:
        stale = [k for k, q in _store.items() if not q or q[-1] < cutoff]
        for k in stale:
            del _store[k]
            removed += 1
    return removed


def _cleanup_loop():
    """Background thread that cleans up stale entries every 60 seconds."""
    while True:
        time.sleep(60)
        try:
            cleanup_stale()
        except Exception:
            pass


# Start background cleanup thread
_cleanup_thread = threading.Thread(target=_cleanup_loop, daemon=True)
_cleanup_thread.start()
