"""
Shared Instagram API rate limiter.

Tracks authenticated API calls in a rolling 1-hour window and enforces
a configurable max rate. Both the main scraper and paid content modules
use this to avoid exceeding Instagram's rate threshold.
"""
import logging
import os
import threading
import time
from collections import deque

logger = logging.getLogger('media_downloader')

_PAUSE_FILE = '/opt/media-downloader/data/.ig_paused_until'


class InstagramBlockedError(Exception):
    """Raised when Instagram API calls are paused due to account restriction."""
    pass


class _InstagramRateLimiter:
    def __init__(self, max_calls_per_hour=180, window_seconds=3600):
        self.max_calls = max_calls_per_hour
        self.window = window_seconds
        self._timestamps = deque()
        self._lock = threading.Lock()
        self._operation_lock = threading.Lock()  # Cross-module mutex
        self._paused_until = 0  # Unix timestamp — block all calls until this time
        self._load_pause_state()

    def _load_pause_state(self):
        """Load pause state from disk (survives restarts)."""
        try:
            if os.path.exists(_PAUSE_FILE):
                with open(_PAUSE_FILE) as f:
                    ts = float(f.read().strip())
                if ts > time.time():
                    self._paused_until = ts
                    logger.warning(
                        f"[IG-RateLimit] Loaded pause state — blocked until "
                        f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts))}"
                    )
                else:
                    # Expired, clean up
                    os.remove(_PAUSE_FILE)
        except Exception:
            pass

    def pause_until(self, timestamp: float):
        """Block all Instagram API calls until the given unix timestamp."""
        self._paused_until = timestamp
        try:
            os.makedirs(os.path.dirname(_PAUSE_FILE), exist_ok=True)
            with open(_PAUSE_FILE, 'w') as f:
                f.write(str(timestamp))
        except Exception:
            pass
        logger.warning(
            f"[IG-RateLimit] All Instagram API calls PAUSED until "
            f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}"
        )

    def resume(self):
        """Resume Instagram API calls."""
        self._paused_until = 0
        try:
            if os.path.exists(_PAUSE_FILE):
                os.remove(_PAUSE_FILE)
        except Exception:
            pass
        logger.info("[IG-RateLimit] Instagram API calls RESUMED")

    @property
    def is_paused(self):
        if self._paused_until and time.time() < self._paused_until:
            return True
        return False

    def track_call(self):
        """Record an API call timestamp."""
        with self._lock:
            self._timestamps.append(time.time())

    def wait_if_needed(self):
        """Block if approaching rate limit. Call before each authenticated API request.

        Raises InstagramBlockedError if calls are paused due to account restriction.
        """
        # Check kill switch first (outside lock — fast path)
        # Re-check file if not paused in memory (another process may have set it)
        if not self._paused_until:
            self._load_pause_state()
        if self._paused_until:
            now = time.time()
            if now < self._paused_until:
                remaining = self._paused_until - now
                hours = remaining / 3600
                raise InstagramBlockedError(
                    f"Instagram API paused — account restricted. "
                    f"Resuming in {hours:.1f}h"
                )
            else:
                # Restriction expired, auto-resume
                self.resume()

        with self._lock:
            now = time.time()
            cutoff = now - self.window
            # Purge old entries
            while self._timestamps and self._timestamps[0] < cutoff:
                self._timestamps.popleft()

            count = len(self._timestamps)

            if count >= self.max_calls:
                # At limit — wait until the oldest call in window expires
                wait_time = self._timestamps[0] - cutoff + 1
                logger.warning(
                    f"[IG-RateLimit] At limit ({count}/{self.max_calls} calls/hr) — "
                    f"waiting {wait_time:.0f}s"
                )
                # Release lock while sleeping
                self._lock.release()
                try:
                    time.sleep(wait_time)
                finally:
                    self._lock.acquire()
            elif count >= self.max_calls * 0.8:
                # Approaching limit (80%+) — add progressive delay
                ratio = count / self.max_calls
                delay = 2 + (ratio - 0.8) * 40  # 2s at 80%, 10s at 100%
                logger.info(
                    f"[IG-RateLimit] Approaching limit ({count}/{self.max_calls} calls/hr) — "
                    f"adding {delay:.1f}s delay"
                )
                self._lock.release()
                try:
                    time.sleep(delay)
                finally:
                    self._lock.acquire()

            self._timestamps.append(time.time())

    @property
    def operation_lock(self):
        """Lock for serializing Instagram operations (main scraper vs paid content)."""
        return self._operation_lock

    @property
    def calls_in_window(self):
        with self._lock:
            now = time.time()
            cutoff = now - self.window
            while self._timestamps and self._timestamps[0] < cutoff:
                self._timestamps.popleft()
            return len(self._timestamps)


# Module-level singleton
rate_limiter = _InstagramRateLimiter(max_calls_per_hour=180)
