"""Rate limiting in-memory basado en ventana deslizante.

Protege endpoints sensibles frente a abuso si el token del dashboard
llega a manos equivocadas o se producen errores de cliente en bucle.
No requiere Redis ni ninguna dependencia externa.

Ventanas configurables por grupo:
  - "strict" : 30 req / 60 s  (comandos, agentes, importar memoria)
  - "medium" : 60 req / 60 s  (eventos inbound desde PHP)
  - "loose"  : 120 req / 60 s (lectura, SSE)

Clave de throttling: dirección IP remota. Para IPs detrás de proxy
se lee X-Forwarded-For (primer valor, sanitizado).
"""
from __future__ import annotations

import logging
import time
from collections import deque
from threading import Lock
from typing import Any

from fastapi import HTTPException, Request

logger = logging.getLogger(__name__)

# (max_requests, window_seconds)
_LIMITS: dict[str, tuple[int, int]] = {
    "strict": (30, 60),
    "medium": (60, 60),
    "loose":  (120, 60),
}

# Estado compartido: {(grupo, ip): deque[timestamp_float]}
_buckets: dict[tuple[str, str], deque[float]] = {}
_lock = Lock()


def _get_client_ip(request: Request) -> str:
    """Obtiene IP real del cliente de forma segura."""
    forwarded = request.headers.get("X-Forwarded-For", "")
    if forwarded:
        # tomamos solo el primer valor y descartamos caracteres no estándar
        ip = forwarded.split(",")[0].strip()
        # validación básica: solo chars permitidos en IPv4/IPv6
        if all(c in "0123456789abcdefABCDEF.:[]" for c in ip) and len(ip) < 50:
            return ip
    return request.client.host if request.client else "unknown"


def _check(group: str, ip: str) -> None:
    """Lanza HTTP 429 si este IP superó el límite del grupo."""
    max_req, window = _LIMITS.get(group, _LIMITS["loose"])
    now = time.monotonic()
    cutoff = now - window

    key = (group, ip)
    with _lock:
        bucket = _buckets.setdefault(key, deque())
        # eliminar timestamps fuera de ventana
        while bucket and bucket[0] < cutoff:
            bucket.popleft()
        if len(bucket) >= max_req:
            logger.warning(
                "rate_limit.exceeded",
                extra={"group": group, "ip": ip, "count": len(bucket), "limit": max_req},
            )
            raise HTTPException(
                status_code=429,
                detail=f"Demasiadas solicitudes. Espera {window}s y vuelve a intentarlo.",
                headers={"Retry-After": str(window)},
            )
        bucket.append(now)


# ------------------------------------------------------------------ #
# Dependencias FastAPI                                                  #
# ------------------------------------------------------------------ #

def rate_limit_strict(request: Request) -> None:
    """30 req / 60 s — para POST /api/commands, /api/agents/chat, /api/memories/import."""
    _check("strict", _get_client_ip(request))


def rate_limit_medium(request: Request) -> None:
    """60 req / 60 s — para POST /api/events/inbound."""
    _check("medium", _get_client_ip(request))


def rate_limit_loose(request: Request) -> None:
    """120 req / 60 s — para lecturas frecuentes si se desea limitar bots."""
    _check("loose", _get_client_ip(request))


def get_stats() -> dict[str, Any]:
    """Devuelve un resumen del estado actual de los buckets (útil para /health)."""
    now = time.monotonic()
    total_ips: set[str] = set()
    throttled: int = 0
    with _lock:
        for (group, ip), bucket in _buckets.items():
            max_req, window = _LIMITS.get(group, _LIMITS["loose"])
            active = sum(1 for t in bucket if t > now - window)
            if active > 0:
                total_ips.add(ip)
            if active >= max_req:
                throttled += 1
    return {"active_ips": len(total_ips), "throttled_buckets": throttled}
