"""Shared IP-based rate limiting backed by Redis, with graceful fail-open.

Mirrors the escalating-lockout pattern used by the onboarding API-key step,
generalised behind a ``prefix`` so multiple flows (login, etc.) can each keep
their own counters. Every function is best-effort: if Redis is unavailable the
limiter FAILS OPEN — it must never lock a legitimate user out because the
backing store is down.

Escalation per IP (failed attempts -> lockout):
    3 -> 60s, 6 -> 5m, 9 -> 1h, 12 -> 24h, 15+ -> permanent (contact support).
"""
import hashlib
import os


def _get_redis():
    import redis
    return redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0"))


def _ip_hash(ip: str) -> str:
    return hashlib.sha256((ip or "").encode()).hexdigest()[:32]


def check_rate_limit(prefix: str, ip: str) -> tuple:
    """Return ``(is_blocked, seconds_remaining)``.

    ``seconds_remaining == -1`` signals a permanent block. Fails open (returns
    ``(False, 0)``) on any Redis error.
    """
    try:
        r = _get_redis()
        ip_h = _ip_hash(ip)
        if r.exists(f"{prefix}_blocked:{ip_h}"):
            return True, -1
        if r.get(f"{prefix}_lock:{ip_h}"):
            return True, max(0, r.ttl(f"{prefix}_lock:{ip_h}"))
        return False, 0
    except Exception:
        return False, 0


def record_fail(prefix: str, ip: str) -> None:
    """Increment the failure counter for this IP and apply the matching lockout."""
    try:
        r = _get_redis()
        ip_h = _ip_hash(ip)
        fails_key = f"{prefix}_fail:{ip_h}"
        count = r.incr(fails_key)
        r.expire(fails_key, 7 * 86400)  # track for 7 days

        if count >= 15:
            r.set(f"{prefix}_blocked:{ip_h}", "1")  # permanent — contact support
            lockout = None
        elif count >= 12:
            lockout = 86400       # 24 hours
        elif count >= 9:
            lockout = 3600        # 1 hour
        elif count >= 6:
            lockout = 300         # 5 minutes
        elif count >= 3:
            lockout = 60          # 60 seconds
        else:
            lockout = None

        if lockout:
            r.setex(f"{prefix}_lock:{ip_h}", lockout, "1")
    except Exception:
        pass


def clear_rate_limit(prefix: str, ip: str) -> None:
    """Clear failure/lockout counters for this IP (call on successful auth)."""
    try:
        r = _get_redis()
        ip_h = _ip_hash(ip)
        r.delete(f"{prefix}_fail:{ip_h}", f"{prefix}_lock:{ip_h}")
    except Exception:
        pass
