"""
Message bus system for inter-thread communication using Queues
"""

import time
import json
import logging
import threading
from enum import Enum
from typing import Dict, Any, Optional, Callable, List
from queue import Queue, Empty
from dataclasses import dataclass, asdict
from datetime import datetime

logger = logging.getLogger(__name__)


class MessageType(Enum):
    """Message types for different operations"""
    # Video control messages
    VIDEO_PLAY = "VIDEO_PLAY"
    VIDEO_PAUSE = "VIDEO_PAUSE"
    VIDEO_STOP = "VIDEO_STOP"
    VIDEO_SEEK = "VIDEO_SEEK"
    VIDEO_VOLUME = "VIDEO_VOLUME"
    VIDEO_FULLSCREEN = "VIDEO_FULLSCREEN"
    VIDEO_PROGRESS = "VIDEO_PROGRESS"
    VIDEO_STATUS = "VIDEO_STATUS"
    VIDEO_ERROR = "VIDEO_ERROR"
    
    # Template and overlay messages
    TEMPLATE_CHANGE = "TEMPLATE_CHANGE"
    OVERLAY_UPDATE = "OVERLAY_UPDATE"
    OVERLAY_DATA = "OVERLAY_DATA"
    
    # API client messages
    API_REQUEST = "API_REQUEST"
    API_RESPONSE = "API_RESPONSE"
    API_ERROR = "API_ERROR"
    API_CONFIG_CHANGE = "API_CONFIG_CHANGE"
    
    # Configuration messages
    CONFIG_UPDATE = "CONFIG_UPDATE"
    CONFIG_REQUEST = "CONFIG_REQUEST"
    CONFIG_RESPONSE = "CONFIG_RESPONSE"
    
    # System messages
    SYSTEM_STATUS = "SYSTEM_STATUS"
    SYSTEM_ERROR = "SYSTEM_ERROR"
    SYSTEM_SHUTDOWN = "SYSTEM_SHUTDOWN"
    SYSTEM_READY = "SYSTEM_READY"
    STATUS_REQUEST = "STATUS_REQUEST"
    
    # Web dashboard messages
    WEB_USER_LOGIN = "WEB_USER_LOGIN"
    WEB_USER_LOGOUT = "WEB_USER_LOGOUT"
    WEB_ACTION = "WEB_ACTION"
    WEB_STATUS = "WEB_STATUS"
    
    # Log messages
    LOG_ENTRY = "LOG_ENTRY"
    
    # Game messages
    START_GAME = "START_GAME"
    GAME_STARTED = "GAME_STARTED"
    SCHEDULE_GAMES = "SCHEDULE_GAMES"
    START_GAME_DELAYED = "START_GAME_DELAYED"
    START_INTRO = "START_INTRO"
    MATCH_START = "MATCH_START"
    PLAY_VIDEO_MATCH = "PLAY_VIDEO_MATCH"
    PLAY_VIDEO_MATCH_DONE = "PLAY_VIDEO_MATCH_DONE"
    PLAY_VIDEO_RESULT = "PLAY_VIDEO_RESULT"
    PLAY_VIDEO_RESULT_DONE = "PLAY_VIDEO_RESULT_DONE"
    MATCH_DONE = "MATCH_DONE"
    NEXT_MATCH = "NEXT_MATCH"
    GAME_STATUS = "GAME_STATUS"
    GAME_UPDATE = "GAME_UPDATE"

    # Custom messages (for future extensions)
    CUSTOM = "CUSTOM"


@dataclass
class Message:
    """Message structure for inter-thread communication"""
    type: MessageType
    data: Dict[str, Any]
    sender: str
    recipient: Optional[str] = None
    timestamp: float = None
    correlation_id: Optional[str] = None
    priority: int = 0  # 0 = normal, 1 = high, 2 = critical
    
    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = time.time()
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert message to dictionary"""
        result = asdict(self)
        result['type'] = self.type.value
        return result
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Message':
        """Create message from dictionary"""
        if isinstance(data['type'], str):
            data['type'] = MessageType(data['type'])
        return cls(**data)
    
    def to_json(self) -> str:
        """Convert message to JSON string"""
        return json.dumps(self.to_dict(), default=str)
    
    @classmethod
    def from_json(cls, json_str: str) -> 'Message':
        """Create message from JSON string"""
        data = json.loads(json_str)
        return cls.from_dict(data)
    
    def is_response_to(self, original_message: 'Message') -> bool:
        """Check if this message is a response to another message"""
        return (self.correlation_id is not None and 
                self.correlation_id == original_message.correlation_id)
    
    def create_response(self, response_type: MessageType, response_data: Dict[str, Any], 
                       sender: str) -> 'Message':
        """Create a response message to this message"""
        return Message(
            type=response_type,
            data=response_data,
            sender=sender,
            recipient=self.sender,
            correlation_id=self.correlation_id,
            priority=self.priority
        )
    
    def __str__(self):
        return f"Message({self.type.value}, {self.sender}->{self.recipient or 'ALL'})"


class MessageBus:
    """Central message bus for inter-thread communication"""

    def __init__(self, max_queue_size: int = 1000, dev_message: bool = False, debug_messages: bool = False):
        self.max_queue_size = max_queue_size
        self.dev_message = dev_message
        self.debug_messages = debug_messages
        self._queues: Dict[str, Queue] = {}
        self._handlers: Dict[str, Dict[MessageType, List[Callable]]] = {}
        self._global_handlers: Dict[MessageType, List[Callable]] = {}
        self._running = False
        self._lock = threading.RLock()
        self._message_history: List[Message] = []
        self._max_history = 1000

        if dev_message:
            logger.info("MessageBus initialized with dev_message mode enabled")
        elif debug_messages:
            logger.info("MessageBus initialized with debug_messages mode enabled")
        else:
            logger.info("MessageBus initialized")
    
    def register_component(self, component_name: str) -> Queue:
        """Register a component and get its message queue"""
        with self._lock:
            if component_name not in self._queues:
                self._queues[component_name] = Queue(maxsize=self.max_queue_size)
                self._handlers[component_name] = {}
                logger.info(f"Component '{component_name}' registered with MessageBus")
            
            return self._queues[component_name]
    
    def unregister_component(self, component_name: str):
        """Unregister a component"""
        with self._lock:
            if component_name in self._queues:
                # Clear the queue
                try:
                    while True:
                        self._queues[component_name].get_nowait()
                except Empty:
                    pass
                
                del self._queues[component_name]
                del self._handlers[component_name]
                logger.info(f"Component '{component_name}' unregistered from MessageBus")
    
    def subscribe(self, component_name: str, message_type: MessageType, 
                  handler: Callable[[Message], None]):
        """Subscribe to specific message type for a component"""
        with self._lock:
            if component_name not in self._handlers:
                self._handlers[component_name] = {}
            
            if message_type not in self._handlers[component_name]:
                self._handlers[component_name][message_type] = []
            
            self._handlers[component_name][message_type].append(handler)
            logger.debug(f"Component '{component_name}' subscribed to {message_type.value}")
    
    def subscribe_global(self, message_type: MessageType, handler: Callable[[Message], None]):
        """Subscribe to message type globally (all components)"""
        with self._lock:
            if message_type not in self._global_handlers:
                self._global_handlers[message_type] = []
            
            self._global_handlers[message_type].append(handler)
            logger.debug(f"Global handler subscribed to {message_type.value}")
    
    def publish(self, message: Message, broadcast: bool = False) -> bool:
        """Publish message to the bus"""
        try:
            with self._lock:
                # Add to message history
                self._add_to_history(message)
                
                # Log the message (only in dev_message mode)
                if self.dev_message:
                    logger.info(f"📨 MESSAGE_BUS: {message}")

                # Display message on screen (debug_messages mode with debug enabled)
                if self.debug_messages and self.dev_message:
                    timestamp_str = datetime.fromtimestamp(message.timestamp).strftime('%H:%M:%S.%f')[:-3]
                    print(f"[{timestamp_str}] 📨 {message.sender} -> {message.recipient or 'ALL'}: {message.type.value}")
                    if message.data:
                        # Show key data fields (truncate long values)
                        data_str = ", ".join([f"{k}: {str(v)[:50]}{'...' if len(str(v)) > 50 else ''}" for k, v in message.data.items()])
                        print(f"    Data: {{{data_str}}}")
                    print()  # Empty line for readability
                
                if broadcast or message.recipient is None:
                    # Broadcast to all components
                    success_count = 0
                    for component_name, queue in self._queues.items():
                        if component_name != message.sender:  # Don't send to sender
                            if self._deliver_to_queue(queue, message):
                                success_count += 1
                else:
                    # Send to specific recipient
                    if message.recipient in self._queues:
                        if self._deliver_to_queue(self._queues[message.recipient], message):
                            logger.debug(f"Message delivered to {message.recipient}")
                            return True
                        else:
                            logger.warning(f"Failed to deliver message to {message.recipient} (queue full)")
                            return False
                    else:
                        logger.warning(f"Recipient '{message.recipient}' not found")
                        return False
                
                # Call global handlers
                self._call_handlers(self._global_handlers, message)
                
                # Call component-specific handlers
                if message.recipient and message.recipient in self._handlers:
                    self._call_handlers(self._handlers[message.recipient], message)
                
                return True
                
        except Exception as e:
            logger.error(f"Failed to publish message: {e}")
            return False
    
    def _deliver_to_queue(self, queue: Queue, message: Message) -> bool:
        """Deliver message to a specific queue"""
        try:
            # Special handling for high-frequency messages like VIDEO_PROGRESS
            if message.type == MessageType.VIDEO_PROGRESS:
                # Try to remove any existing VIDEO_PROGRESS message from the queue
                try:
                    temp_queue = Queue()
                    removed = False
                    
                    # Drain the queue, keeping all messages except VIDEO_PROGRESS
                    while True:
                        item = queue.get_nowait()
                        if item.type == MessageType.VIDEO_PROGRESS:
                            removed = True
                        else:
                            temp_queue.put(item)
                except Empty:
                    pass
                
                # Put back all non-VIDEO_PROGRESS messages
                while not temp_queue.empty():
                    queue.put(temp_queue.get())
                
                # Now add the new VIDEO_PROGRESS message
                queue.put(message, block=False)
                return True
            
            # Priority handling - critical messages skip queue size limits
            elif message.priority >= 2:
                queue.put(message, block=False)
            else:
                # Check queue size for normal messages
                if queue.qsize() < self.max_queue_size:
                    queue.put(message, block=False)
                else:
                    logger.warning(f"Queue full, dropping message: {message}")
                    return False
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to deliver message to queue: {e}")
            return False
    
    def _call_handlers(self, handlers_dict: Dict[MessageType, List[Callable]], message: Message):
        """Call message handlers"""
        try:
            if message.type in handlers_dict:
                for handler in handlers_dict[message.type]:
                    try:
                        handler(message)
                    except Exception as e:
                        logger.error(f"Message handler failed: {e}")
        except Exception as e:
            logger.error(f"Failed to call handlers: {e}")
    
    def _add_to_history(self, message: Message):
        """Add message to history for debugging"""
        try:
            self._message_history.append(message)
            
            # Trim history if too long
            if len(self._message_history) > self._max_history:
                self._message_history = self._message_history[-self._max_history//2:]
                
        except Exception as e:
            logger.error(f"Failed to add message to history: {e}")
    
    def get_message(self, component_name: str, timeout: Optional[float] = None) -> Optional[Message]:
        """Get message for a component"""
        if component_name not in self._queues:
            logger.error(f"Component '{component_name}' not registered")
            return None
        
        try:
            if timeout is None:
                return self._queues[component_name].get_nowait()
            else:
                return self._queues[component_name].get(timeout=timeout)
        except Empty:
            return None
        except Exception as e:
            logger.error(f"Failed to get message: {e}")
            return None
    
    def has_messages(self, component_name: str) -> bool:
        """Check if component has pending messages"""
        if component_name not in self._queues:
            return False
        
        return not self._queues[component_name].empty()
    
    def get_queue_size(self, component_name: str) -> int:
        """Get current queue size for a component"""
        if component_name not in self._queues:
            return 0
        
        return self._queues[component_name].qsize()
    
    def clear_queue(self, component_name: str) -> int:
        """Clear all messages from a component's queue"""
        if component_name not in self._queues:
            return 0
        
        cleared_count = 0
        try:
            while True:
                self._queues[component_name].get_nowait()
                cleared_count += 1
        except Empty:
            pass
        
        logger.info(f"Cleared {cleared_count} messages from {component_name} queue")
        return cleared_count
    
    def get_message_history(self, limit: int = 100) -> List[Message]:
        """Get recent message history"""
        with self._lock:
            if limit <= 0:
                return self._message_history.copy()
            else:
                return self._message_history[-limit:].copy()
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get message bus statistics"""
        with self._lock:
            stats = {
                'total_components': len(self._queues),
                'total_messages_in_history': len(self._message_history),
                'components': {},
                'message_types': {}
            }
            
            # Component statistics
            for component_name, queue in self._queues.items():
                stats['components'][component_name] = {
                    'queue_size': queue.qsize(),
                    'max_queue_size': self.max_queue_size,
                    'handlers_count': len(self._handlers.get(component_name, {}))
                }
            
            # Message type statistics
            for message in self._message_history:
                msg_type = message.type.value
                if msg_type not in stats['message_types']:
                    stats['message_types'][msg_type] = 0
                stats['message_types'][msg_type] += 1
            
            return stats
    
    def shutdown(self):
        """Shutdown message bus"""
        with self._lock:
            self._running = False
            
            # Clear all queues
            for component_name in list(self._queues.keys()):
                self.clear_queue(component_name)
                self.unregister_component(component_name)
            
            # Clear handlers
            self._handlers.clear()
            self._global_handlers.clear()
            
            # Clear history
            self._message_history.clear()
            
            logger.info("MessageBus shutdown complete")


class MessageBuilder:
    """Helper class for building messages"""
    
    @staticmethod
    def video_play(sender: str, file_path: str, template: str = "news_template",
                   overlay_data: Optional[Dict[str, Any]] = None) -> Message:
        """Create VIDEO_PLAY message"""
        return Message(
            type=MessageType.VIDEO_PLAY,
            sender=sender,
            data={
                "file_path": file_path,
                "template": template,
                "overlay_data": overlay_data or {}
            }
        )
    
    @staticmethod
    def video_progress(sender: str, position: float, duration: float,
                      percentage: float) -> Message:
        """Create VIDEO_PROGRESS message"""
        return Message(
            type=MessageType.VIDEO_PROGRESS,
            sender=sender,
            data={
                "position": position,
                "duration": duration,
                "percentage": percentage
            }
        )
    
    @staticmethod
    def template_change(sender: str, template_name: str,
                       template_data: Dict[str, Any]) -> Message:
        """Create TEMPLATE_CHANGE message"""
        return Message(
            type=MessageType.TEMPLATE_CHANGE,
            sender=sender,
            data={
                "template_name": template_name,
                "template_data": template_data
            }
        )
    
    @staticmethod
    def overlay_update(sender: str, overlay_data: Dict[str, Any]) -> Message:
        """Create OVERLAY_UPDATE message"""
        return Message(
            type=MessageType.OVERLAY_UPDATE,
            sender=sender,
            data={
                "overlay_data": overlay_data
            }
        )
    
    @staticmethod
    def api_request(sender: str, url: str = None, endpoint: str = None, method: str = "GET",
                   headers: Optional[Dict[str, str]] = None,
                   data: Optional[Dict[str, Any]] = None) -> Message:
        """Create API_REQUEST message"""
        message_data = {
            "method": method,
            "headers": headers or {},
            "data": data or {}
        }
        
        if endpoint:
            message_data["endpoint"] = endpoint
        if url:
            message_data["url"] = url
            
        return Message(
            type=MessageType.API_REQUEST,
            sender=sender,
            data=message_data
        )
    
    @staticmethod
    def api_response(sender: str, status_code: int, response_data: Dict[str, Any],
                    correlation_id: Optional[str] = None) -> Message:
        """Create API_RESPONSE message"""
        return Message(
            type=MessageType.API_RESPONSE,
            sender=sender,
            correlation_id=correlation_id,
            data={
                "status_code": status_code,
                "response_data": response_data
            }
        )
    
    @staticmethod
    def config_update(sender: str, config_section: str,
                     config_data: Dict[str, Any]) -> Message:
        """Create CONFIG_UPDATE message"""
        return Message(
            type=MessageType.CONFIG_UPDATE,
            sender=sender,
            data={
                "config_section": config_section,
                "config_data": config_data
            }
        )
    
    @staticmethod
    def system_status(sender: str, status: str, details: Dict[str, Any]) -> Message:
        """Create SYSTEM_STATUS message"""
        return Message(
            type=MessageType.SYSTEM_STATUS,
            sender=sender,
            data={
                "status": status,
                "details": details
            }
        )
    
    @staticmethod
    def log_entry(sender: str, level: str, message: str,
                 details: Optional[Dict[str, Any]] = None) -> Message:
        """Create LOG_ENTRY message"""
        return Message(
            type=MessageType.LOG_ENTRY,
            sender=sender,
            data={
                "level": level,
                "message": message,
                "details": details or {}
            }
        )

    @staticmethod
    def start_game(sender: str, fixture_id: Optional[str] = None) -> Message:
        """Create START_GAME message"""
        return Message(
            type=MessageType.START_GAME,
            sender=sender,
            data={
                "fixture_id": fixture_id
            }
        )

    @staticmethod
    def game_started(sender: str, fixture_id: str) -> Message:
        """Create GAME_STARTED message"""
        return Message(
            type=MessageType.GAME_STARTED,
            sender=sender,
            data={
                "fixture_id": fixture_id
            }
        )

    @staticmethod
    def schedule_games(sender: str, fixture_id: Optional[str] = None) -> Message:
        """Create SCHEDULE_GAMES message"""
        return Message(
            type=MessageType.SCHEDULE_GAMES,
            sender=sender,
            data={
                "fixture_id": fixture_id
            }
        )

    @staticmethod
    def start_game_delayed(sender: str, fixture_id: Optional[str] = None, delay_minutes: Optional[int] = None) -> Message:
        """Create START_GAME_DELAYED message"""
        return Message(
            type=MessageType.START_GAME_DELAYED,
            sender=sender,
            data={
                "fixture_id": fixture_id,
                "delay_minutes": delay_minutes
            }
        )

    @staticmethod
    def start_intro(sender: str, fixture_id: Optional[str] = None, match_id: Optional[int] = None) -> Message:
        """Create START_INTRO message"""
        return Message(
            type=MessageType.START_INTRO,
            sender=sender,
            data={
                "fixture_id": fixture_id,
                "match_id": match_id
            }
        )

    @staticmethod
    def match_start(sender: str, fixture_id: str, match_id: int) -> Message:
        """Create MATCH_START message"""
        return Message(
            type=MessageType.MATCH_START,
            sender=sender,
            data={
                "fixture_id": fixture_id,
                "match_id": match_id
            }
        )

    @staticmethod
    def play_video_match(sender: str, match_id: int, video_filename: str, fixture_id: Optional[str] = None, result: str = None) -> Message:
        """Create PLAY_VIDEO_MATCH message"""
        data = {
            "match_id": match_id,
            "video_filename": video_filename,
            "fixture_id": fixture_id
        }
        if result is not None:
            data["result"] = result
        return Message(
            type=MessageType.PLAY_VIDEO_MATCH,
            sender=sender,
            data=data
        )

    @staticmethod
    def play_video_match_done(sender: str, match_id: int, video_filename: str, fixture_id: Optional[str] = None) -> Message:
        """Create PLAY_VIDEO_MATCH_DONE message"""
        return Message(
            type=MessageType.PLAY_VIDEO_MATCH_DONE,
            sender=sender,
            data={
                "match_id": match_id,
                "video_filename": video_filename,
                "fixture_id": fixture_id
            }
        )

    @staticmethod
    def play_video_result(sender: str, fixture_id: str, match_id: int, result: str, under_over_result: Optional[str] = None, winning_outcomes: Optional[List[str]] = None) -> Message:
        """Create PLAY_VIDEO_RESULT message"""
        data = {
            "fixture_id": fixture_id,
            "match_id": match_id,
            "result": result
        }
        if under_over_result is not None:
            data["under_over_result"] = under_over_result
        if winning_outcomes is not None:
            data["winning_outcomes"] = winning_outcomes
        return Message(
            type=MessageType.PLAY_VIDEO_RESULT,
            sender=sender,
            data=data
        )

    @staticmethod
    def play_video_result_done(sender: str, fixture_id: str, match_id: int, result: str) -> Message:
        """Create PLAY_VIDEO_RESULT_DONE message"""
        return Message(
            type=MessageType.PLAY_VIDEO_RESULT_DONE,
            sender=sender,
            data={
                "fixture_id": fixture_id,
                "match_id": match_id,
                "result": result
            }
        )

    @staticmethod
    def match_done(sender: str, fixture_id: str, match_id: int, result: Optional[str] = None) -> Message:
        """Create MATCH_DONE message"""
        data = {
            "fixture_id": fixture_id,
            "match_id": match_id
        }
        if result is not None:
            data["result"] = result
        return Message(
            type=MessageType.MATCH_DONE,
            sender=sender,
            data=data
        )

    @staticmethod
    def next_match(sender: str, fixture_id: str, match_id: int) -> Message:
        """Create NEXT_MATCH message"""
        return Message(
            type=MessageType.NEXT_MATCH,
            sender=sender,
            data={
                "fixture_id": fixture_id,
                "match_id": match_id
            }
        )