"""Redis Pub/Sub for real-time event broadcasting across workers."""

import asyncio
import json
from typing import Callable, Dict, Any, Optional, List
from src.core.redis import get_redis_client
from src.core.logger import get_logger

logger = get_logger(__name__)


# Channel names
CHANNEL_MARKET_EVENTS = "crypto_trader:market_events"
CHANNEL_TRADE_EVENTS = "crypto_trader:trade_events"
CHANNEL_SYSTEM_EVENTS = "crypto_trader:system_events"
CHANNEL_AUTOPILOT_EVENTS = "crypto_trader:autopilot_events"


class RedisPubSub:
    """Redis Pub/Sub handler for real-time event broadcasting."""
    
    def __init__(self):
        """Initialize Redis Pub/Sub."""
        self.redis = get_redis_client()
        self._subscribers: Dict[str, List[Callable]] = {}
        self._pubsub = None
        self._running = False
        self._listen_task: Optional[asyncio.Task] = None
    
    async def publish(self, channel: str, event_type: str, data: Dict[str, Any]) -> int:
        """Publish an event to a channel.
        
        Args:
            channel: Channel name
            event_type: Type of event (e.g., 'price_update', 'trade_executed')
            data: Event data
            
        Returns:
            Number of subscribers that received the message
        """
        message = {
            "type": event_type,
            "data": data,
            "timestamp": asyncio.get_event_loop().time()
        }
        
        try:
            client = self.redis.get_client()
            count = await client.publish(channel, json.dumps(message))
            logger.debug(f"Published {event_type} to {channel} ({count} subscribers)")
            return count
        except Exception as e:
            logger.error(f"Failed to publish to {channel}: {e}")
            return 0
    
    async def subscribe(self, channel: str, callback: Callable[[Dict[str, Any]], None]) -> None:
        """Subscribe to a channel.
        
        Args:
            channel: Channel name
            callback: Async function to call when message received
        """
        if channel not in self._subscribers:
            self._subscribers[channel] = []
        self._subscribers[channel].append(callback)
        
        logger.info(f"Subscribed to channel: {channel}")
        
        # Start listener if not running
        if not self._running:
            await self._start_listener()
    
    async def unsubscribe(self, channel: str, callback: Callable = None) -> None:
        """Unsubscribe from a channel.
        
        Args:
            channel: Channel name
            callback: Specific callback to remove, or None to remove all
        """
        if channel in self._subscribers:
            if callback:
                self._subscribers[channel] = [c for c in self._subscribers[channel] if c != callback]
            else:
                del self._subscribers[channel]
        
        logger.info(f"Unsubscribed from channel: {channel}")
    
    async def _start_listener(self) -> None:
        """Start the Pub/Sub listener."""
        if self._running:
            return
        
        self._running = True
        self._listen_task = asyncio.create_task(self._listen())
        logger.info("Started Redis Pub/Sub listener")
    
    async def _listen(self) -> None:
        """Listen for messages on subscribed channels."""
        try:
            client = self.redis.get_client()
            self._pubsub = client.pubsub()
            
            # Subscribe to all registered channels
            channels = list(self._subscribers.keys())
            if channels:
                await self._pubsub.subscribe(*channels)
            
            while self._running:
                try:
                    message = await self._pubsub.get_message(ignore_subscribe_messages=True, timeout=1.0)
                    
                    if message and message['type'] == 'message':
                        channel = message['channel']
                        if isinstance(channel, bytes):
                            channel = channel.decode('utf-8')
                        
                        data = message['data']
                        if isinstance(data, bytes):
                            data = data.decode('utf-8')
                        
                        try:
                            parsed = json.loads(data)
                        except json.JSONDecodeError:
                            parsed = {"raw": data}
                        
                        # Call all subscribers for this channel
                        callbacks = self._subscribers.get(channel, [])
                        for callback in callbacks:
                            try:
                                if asyncio.iscoroutinefunction(callback):
                                    await callback(parsed)
                                else:
                                    callback(parsed)
                            except Exception as e:
                                logger.error(f"Subscriber callback error: {e}")
                    
                    await asyncio.sleep(0.01)  # Prevent busy loop
                    
                except asyncio.CancelledError:
                    break
                except Exception as e:
                    logger.error(f"Pub/Sub listener error: {e}")
                    await asyncio.sleep(1)
                    
        finally:
            if self._pubsub:
                await self._pubsub.close()
            self._running = False
    
    async def stop(self) -> None:
        """Stop the Pub/Sub listener."""
        self._running = False
        if self._listen_task:
            self._listen_task.cancel()
            try:
                await self._listen_task
            except asyncio.CancelledError:
                pass
        logger.info("Stopped Redis Pub/Sub listener")
    
    # Convenience methods for common event types
    
    async def publish_price_update(self, symbol: str, price: float, bid: float = None, ask: float = None) -> int:
        """Publish a price update event.
        
        Args:
            symbol: Trading symbol
            price: Current price
            bid: Bid price
            ask: Ask price
            
        Returns:
            Number of subscribers notified
        """
        return await self.publish(CHANNEL_MARKET_EVENTS, "price_update", {
            "symbol": symbol,
            "price": price,
            "bid": bid,
            "ask": ask
        })
    
    async def publish_trade_executed(
        self,
        symbol: str,
        side: str,
        quantity: float,
        price: float,
        order_id: str = None
    ) -> int:
        """Publish a trade execution event.
        
        Args:
            symbol: Trading symbol
            side: 'buy' or 'sell'
            quantity: Trade quantity
            price: Execution price
            order_id: Order ID
            
        Returns:
            Number of subscribers notified
        """
        return await self.publish(CHANNEL_TRADE_EVENTS, "trade_executed", {
            "symbol": symbol,
            "side": side,
            "quantity": quantity,
            "price": price,
            "order_id": order_id
        })
    
    async def publish_autopilot_status(
        self,
        symbol: str,
        status: str,
        action: str = None,
        details: Dict[str, Any] = None
    ) -> int:
        """Publish an autopilot status event.
        
        Args:
            symbol: Trading symbol
            status: 'started', 'stopped', 'error', 'signal'
            action: Optional action taken
            details: Additional details
            
        Returns:
            Number of subscribers notified
        """
        return await self.publish(CHANNEL_AUTOPILOT_EVENTS, "autopilot_status", {
            "symbol": symbol,
            "status": status,
            "action": action,
            "details": details or {}
        })
    
    async def publish_system_event(self, event_type: str, message: str, severity: str = "info") -> int:
        """Publish a system event.
        
        Args:
            event_type: Event type (e.g., 'startup', 'shutdown', 'error')
            message: Event message
            severity: 'info', 'warning', 'error'
            
        Returns:
            Number of subscribers notified
        """
        return await self.publish(CHANNEL_SYSTEM_EVENTS, event_type, {
            "message": message,
            "severity": severity
        })


# Global Pub/Sub instance
_redis_pubsub: Optional[RedisPubSub] = None


def get_redis_pubsub() -> RedisPubSub:
    """Get global Redis Pub/Sub instance."""
    global _redis_pubsub
    if _redis_pubsub is None:
        _redis_pubsub = RedisPubSub()
    return _redis_pubsub
