"""Redis connection management with reconnect / error detection."""
from __future__ import annotations

import logging
import random
import time
from typing import Any, Optional

logger = logging.getLogger(__name__)


class QueueError(Exception):
    """Base error for queue operations."""


class RedisConnection:
    """Manages the Redis client, including reconnects with exponential backoff."""

    def __init__(
        self,
        redis_client: Any = None,
        redis_url: Optional[str] = None,
        redis_password: Optional[str] = None,
        reconnect_delay: int = 1,
        max_reconnect_delay: int = 30,
    ) -> None:
        self.redis_url = redis_url
        self.redis_password = redis_password
        self.client = redis_client or self._build(redis_url, redis_password)
        self.reconnect_delay = reconnect_delay
        self.max_reconnect_delay = max_reconnect_delay

    # -- helpers ---------------------------------------------------------

    @staticmethod
    def _build(redis_url: Optional[str], password: Optional[str]):
        if not redis_url:
            raise QueueError("Redis URL required to build client")
        try:
            import redis  # type: ignore
        except ImportError as exc:  # pragma: no cover
            raise QueueError("redis library required") from exc
        return redis.Redis.from_url(redis_url, password=password, decode_responses=True)

    @staticmethod
    def is_connection_error(exc: Exception) -> bool:
        try:
            import redis  # type: ignore
            return isinstance(exc, (redis.exceptions.ConnectionError, redis.exceptions.TimeoutError, OSError))
        except Exception:
            return isinstance(exc, (ConnectionError, TimeoutError, OSError))

    def reconnect(self, stop_flag=None, on_reconnect=None) -> None:
        """Block until a new Redis connection is established, with jittered backoff.

        Args:
            stop_flag: A callable returning bool, or a bool. When truthy,
                       the reconnect loop exits without establishing a connection.
            on_reconnect: Optional callback invoked after a successful reconnect.
        """
        delay = self.reconnect_delay
        while True:
            if callable(stop_flag) and stop_flag():
                return
            if not callable(stop_flag) and stop_flag:
                return
            try:
                self.client = self._build(self.redis_url, self.redis_password)
                logger.info("Reconnected to Redis")
                if on_reconnect:
                    try:
                        on_reconnect()
                    except Exception:
                        logger.warning("Post-reconnect callback failed", exc_info=True)
                return
            except Exception as exc:
                jitter = random.uniform(0, delay * 0.3)
                logger.error("Reconnect to Redis failed: %s (retrying in %.1fs)", exc, delay + jitter)
                time.sleep(delay + jitter)
                delay = min(delay * 2, self.max_reconnect_delay)
