"""Per-tenant SMS rate limiter backed by Redis.

Limits:
  - max_per_minute : rolling 60-second window
  - max_per_hour   : rolling 3600-second window
  - max_per_day    : rolling 86400-second window

All counters use Redis INCR + EXPIRE (atomic via pipeline).
Falls back gracefully if Redis is unavailable (allows sending with a warning).
"""

from __future__ import annotations

import structlog
from dataclasses import dataclass

_log = structlog.get_logger("simorgh.notifications.sms.rate_limiter")


@dataclass
class RateLimitResult:
    allowed: bool
    limit_name: str = ""  # e.g. "per_minute"
    limit_value: int = 0
    current_count: int = 0


def _get_redis():
    """Return a Redis connection from Django's cache (lazy import)."""
    try:
        from django.core.cache import cache
        return cache
    except Exception:
        return None


class TenantSmsRateLimiter:
    """Check and increment per-tenant SMS counters stored in Redis."""

    def check_and_increment(
        self,
        *,
        tenant_id: int,
        max_per_minute: int,
        max_per_hour: int,
        max_per_day: int,
    ) -> RateLimitResult:
        """Return whether this SMS send is within all rate limits.

        If within limits, the counters are incremented atomically.
        """
        cache = _get_redis()
        if cache is None:
            _log.warning("sms.rate_limiter.no_cache", tenant_id=tenant_id)
            return RateLimitResult(allowed=True)

        windows = [
            ("per_minute", 60, max_per_minute),
            ("per_hour", 3600, max_per_hour),
            ("per_day", 86400, max_per_day),
        ]

        # First pass: read current counts without incrementing
        for name, ttl, limit in windows:
            if limit <= 0:
                continue
            key = f"sms:rl:{tenant_id}:{name}"
            try:
                current = cache.get(key, 0)
                if int(current) >= limit:
                    _log.warning(
                        "sms.rate_limit_exceeded",
                        tenant_id=tenant_id,
                        window=name,
                        limit=limit,
                        current=current,
                    )
                    return RateLimitResult(
                        allowed=False,
                        limit_name=name,
                        limit_value=limit,
                        current_count=int(current),
                    )
            except Exception as exc:
                _log.warning("sms.rate_limiter.read_error", error=str(exc), tenant_id=tenant_id)

        # Second pass: increment all counters (send is allowed)
        for name, ttl, limit in windows:
            if limit <= 0:
                continue
            key = f"sms:rl:{tenant_id}:{name}"
            try:
                # Django cache doesn't support INCR natively; use get+set
                current = cache.get(key, 0) or 0
                new_val = int(current) + 1
                # set with TTL — a brief race condition window here is acceptable
                cache.set(key, new_val, timeout=ttl)
            except Exception as exc:
                _log.warning(
                    "sms.rate_limiter.increment_error", error=str(exc), tenant_id=tenant_id
                )

        return RateLimitResult(allowed=True)


_limiter = TenantSmsRateLimiter()


def check_rate_limit(
    *,
    tenant_id: int,
    max_per_minute: int = 10,
    max_per_hour: int = 100,
    max_per_day: int = 1000,
) -> RateLimitResult:
    """Convenience function wrapping the shared limiter instance."""
    return _limiter.check_and_increment(
        tenant_id=tenant_id,
        max_per_minute=max_per_minute,
        max_per_hour=max_per_hour,
        max_per_day=max_per_day,
    )
