import asyncio
import redis.asyncio as redis
import json
import logging
from redis.asyncio.retry import Retry
from redis.backoff import ExponentialBackoff
from redis.exceptions import ConnectionError as RedisConnectionError, TimeoutError as RedisTimeoutError
from app.core.config import settings

logger = logging.getLogger(__name__)

# A managed pool caches connections, and a hosted Redis hangs up on idle ones --
# Azure Cache for Redis defaults to a 10 minute idle timeout. The pool then
# hands out a socket the server has already closed, and the first command on it
# raises "Connection reset by peer" from deep inside the parser, which surfaced
# as a 500 on an endpoint that had simply been quiet for a while.
#
# health_check_interval alone does not cover it: the health check PING travels
# over the same dead socket and fails the same way. The retry is what makes the
# reconnect transparent -- redis-py drops the broken connection and re-runs the
# command on a fresh one.
_RETRY_ON = [RedisConnectionError, RedisTimeoutError, ConnectionResetError]

redis_pool = redis.ConnectionPool(
    host=settings.REDIS_HOST,
    port=settings.REDIS_PORT,
    username=settings.REDIS_USER,
    password=settings.REDIS_PASSWORD,
    connection_class=redis.SSLConnection if settings.REDIS_SSL else redis.Connection,
    db=0,
    decode_responses=True,
    max_connections=50,  # Limits the total number of clients this worker can create
    socket_timeout=60,    # Increased to allow more idle time for PubSub
    socket_connect_timeout=10,
    socket_keepalive=True,  # Keeps an idle connection from being reaped silently
    health_check_interval=25,  # Periodically checks if the connection is still alive
    retry=Retry(ExponentialBackoff(cap=1.0, base=0.05), retries=3),
    retry_on_error=_RETRY_ON,
)

def get_redis_client():
    """Returns an async Redis client from the managed pool"""
    return redis.Redis(connection_pool=redis_pool)


# Keeps a reference so a task can't be garbage-collected mid-publish.
_publish_tasks = set()


async def publish_event(channel: str, data: dict):
    """Publish a JSON event to a Redis channel.

    Fires in the background and returns immediately: a stale connection's
    retry policy can take up to socket_timeout(60s) * retries(3) = 180s to
    recover (see redis_pool's comment above), and no caller -- a chat reply,
    a ticket update -- should ever wait on a websocket notification.
    """
    async def _do_publish():
        try:
            # Using a context manager ensures the connection is returned to the pool immediately
            client = get_redis_client()
            async with client:
                message = json.dumps(data)
                await client.publish(channel, message)
                logger.info(f"Published event to {channel}")
        except Exception as e:
            logger.error(f"Error publishing to Redis channel {channel}: {e}")

    task = asyncio.create_task(_do_publish())
    _publish_tasks.add(task)
    task.add_done_callback(_publish_tasks.discard)
