"""In-memory rate limiter for login attempts."""
from __future__ import annotations
import time
from collections import defaultdict

class RateLimiter:
    def __init__(
        self, *,
        max_attempts: int = 5,
        window_seconds: int = 60,
        global_ip_limit: int = 20,
        lockout_threshold: int = 10,
        lockout_seconds: int = 1800,
    ) -> None:
        self.max_attempts = max_attempts
        self.window = window_seconds
        self.global_ip_limit = global_ip_limit
        self.lockout_threshold = lockout_threshold
        self.lockout_seconds = lockout_seconds
        self._attempts: dict[str, list[float]] = defaultdict(list)
        self._failures: dict[str, list[float]] = defaultdict(list)
        self._lockouts: dict[str, float] = {}

    def _prune(self, key: str, store: dict) -> list[float]:
        cutoff = time.monotonic() - self.window
        pruned = [t for t in store[key] if t > cutoff]
        if pruned:
            store[key] = pruned
        else:
            del store[key]
        return pruned

    def check_and_record(self, ip: str, username: str) -> bool:
        """Check rate limit and record the attempt. Does NOT check lockout."""
        pair_key = f"{ip}:{username}"
        pair_attempts = self._prune(pair_key, self._attempts)
        if len(pair_attempts) >= self.max_attempts:
            return False
        ip_attempts = self._prune(ip, self._attempts)
        if len(ip_attempts) >= self.global_ip_limit:
            return False
        now = time.monotonic()
        self._attempts[pair_key].append(now)
        self._attempts[ip].append(now)
        return True

    def record_failure(self, ip: str, username: str) -> None:
        """Record a failed login. Lockout is per (IP, username) — not username alone."""
        key = f"{ip}:{username}"
        self._failures[key].append(time.monotonic())
        cutoff = time.monotonic() - self.window
        self._failures[key] = [t for t in self._failures[key] if t > cutoff]
        if len(self._failures[key]) >= self.lockout_threshold:
            self._lockouts[key] = time.monotonic() + self.lockout_seconds

    def is_locked_out(self, ip: str, username: str) -> bool:
        """Check if (IP, username) pair is locked out."""
        key = f"{ip}:{username}"
        if key not in self._lockouts:
            return False
        if time.monotonic() > self._lockouts[key]:
            del self._lockouts[key]
            return False
        return True

    def unlock(self, username: str) -> None:
        """Admin-initiated unlock. Clears all lockouts and failures for this username."""
        to_remove = [k for k in self._lockouts if k.endswith(f":{username}")]
        for k in to_remove:
            del self._lockouts[k]
        to_remove = [k for k in self._failures if k.endswith(f":{username}")]
        for k in to_remove:
            del self._failures[k]
