import logging
import time
from typing import Any, Dict, Optional, Callable
from queue import Queue
from pathlib import Path

# Import standard ARTOS interface
from .base_module import BaseModule
from .connection_manager import ConnectionManager
from .data_recorder import DataRecorder

logger = logging.getLogger("BusMonitor.Core")


class BusMonitorCore(BaseModule):
    """
    Main controller for the 1553 Bus Monitor system.
    
    This class implements the ARTOS BaseModule interface, making it usable
    both as a standalone monitor (GUI/CLI) and as an ARTOS Collector module.
    
    Key Features:
    - Dual-mode operation (standalone / ARTOS plugin)
    - Message reception and transmission
    - Data recording and replay for test automation
    - Event notification system for real-time monitoring
    - Timestamp normalization for multi-source synchronization
    """

    def __init__(self):
        super().__init__("BusMonitor1553")
        self.manager = ConnectionManager()
        self.recorder = DataRecorder()
        self.event_queue = Queue()
        self.message_callbacks: Dict[str, Callable] = {}
        self._config: Dict[str, Any] = {}
        self._messagedb = None  # Cached after init_library()
        self._recording_filepath: Optional[Path] = None
        
    def register_callback(self, label: str, callback: Callable) -> None:
        """
        Register a callback function for a specific message label.
        
        When a message with the given label is received, the callback
        will be invoked with the message object as argument.
        
        This is useful for:
        - ARTOS Collector integration (notification of critical events)
        - GUI updates in standalone mode
        - Test validation hooks
        
        Args:
            label: Message label to monitor (e.g., "msg_a2", "msg_b1")
            callback: Function to call when message arrives
        """
        self.message_callbacks[label] = callback
        logger.debug(f"Registered callback for message '{label}'")

    def _notify_message_received(self, label: str, message: Any) -> None:
        """
        Internal method to dispatch message arrival notifications.
        
        Args:
            label: Message label
            message: Message object
        """
        # Add to event queue
        self.event_queue.put(label)
        
        # Record event if recording is active
        if self.recorder.recording:
            self.recorder.record_event(
                event_type='msg_received',
                data=self._serialize_message(message),
                label=label,
                hw_timestamp=getattr(message, 'hardware_timestamp', None)
            )
        
        # Invoke registered callback if any
        if label in self.message_callbacks:
            try:
                self.message_callbacks[label](message)
            except Exception as e:
                logger.error(f"Callback error for '{label}': {e}")

    def initialize(self, config: Dict[str, Any]) -> bool:
        """
        Sets up the library and prepares the message database.
        
        Args:
            config: Dictionary containing network parameters:
                - 'ip': IP address (optional, defaults from env PYBM_RX_IP)
                - 'send_port': TX port (optional, defaults from env PYBM_TX_PORT)
                - 'recv_port': RX port (optional, defaults from env PYBM_RX_PORT)
        
        Returns:
            True if initialization successful, False otherwise
        """
        logger.info("Initializing BusMonitor Core...")
        
        # Store config for potential reconfiguration
        self._config = config
        
        # Apply environment variable overrides if config is empty
        if not config:
            import os
            config = {
                'ip': os.getenv('PYBM_RX_IP', '127.0.0.1'),
                'send_port': int(os.getenv('PYBM_TX_PORT', '5001')),
                'recv_port': int(os.getenv('PYBM_RX_PORT', '5002'))
            }
            self._config = config
        
        # TODO: Apply network configuration to connection.utils
        # For now, ConnectionManager uses environment variables internally
        
        # Initialize the low-level library (imports and creates message wrappers)
        success = self.manager.init_library()
        if not success:
            logger.error(f"Failed to initialize 1553 library: {self.manager.import_error}")
            return False
        
        # Cache MessageDB reference after successful initialization
        try:
            # CRITICAL: Get MessageDB from the SAME module that ConnectionManager used
            # to ensure we see the same messages_buffer1
            if hasattr(self.manager, '_messages_mod') and self.manager._messages_mod:
                self._messagedb = getattr(self.manager._messages_mod, 'MessageDB', None)
                if self._messagedb:
                    logger.info(f"MessageDB cached from ConnectionManager's module")
                else:
                    logger.warning("MessageDB not found in ConnectionManager's module")
            else:
                logger.warning("ConnectionManager has no _messages_mod")
                self._messagedb = None
        except Exception as e:
            logger.warning(f"Could not cache MessageDB reference: {e}")
            self._messagedb = None
            
        logger.info("BusMonitor Core initialized successfully")
        self.is_running = False  # Not started yet
        return True

    def start_session(self):
        """Starts the periodic transmission and reception threads."""
        if not self.is_running:
            success = self.manager.start()
            if success:
                self.is_running = True
                logger.info("1553 session started")
            else:
                logger.error("Failed to start 1553 session")

    def stop_session(self):
        """Stops all threads and releases hardware resources."""
        if self.is_running:
            self.manager.stop()
            self.is_running = False
            logger.info("1553 session stopped")

    def get_status(self) -> Dict[str, Any]:
        """
        Returns the current state of the monitor.
        
        ARTOS Collector uses this to monitor module health during test execution.
        
        Returns:
            Dictionary containing:
            - is_running: Module active status
            - connected: Network/hardware link status
            - messages_count: Total messages in database
            - recording: Whether recording is active
            - errors: List of errors (if any)
        """
        message_count = 0
        try:
            if self._messagedb:
                messages = self._messagedb.getAllMessages()
                message_count = len(messages) if messages else 0
        except Exception:
            pass
            
        status = {
            "is_running": self.is_running,
            "connected": self.manager.is_running(),
            "messages_count": message_count,
            "recording": self.recorder.recording,
            "recorded_events": len(self.recorder.events),
            "errors": []
        }
        
        if hasattr(self.manager, 'import_error') and self.manager.import_error:
            status["errors"].append(self.manager.import_error)
            
        return status

    def get_message(self, label: str) -> Optional[Any]:
        """
        Provides access to a specific message wrapper by its label.
        
        Args:
            label: Message label (e.g., "msg_a2", "msg_b1", "A2", "B1")
            
        Returns:
            Message object if found, None otherwise
        """
        if self._messagedb is None:
            logger.warning("MessageDB not available - call initialize() first")
            return None
        
        try:
            return self._messagedb.getMessage(label)
        except Exception as e:
            logger.debug(f"Message '{label}' not found: {e}")
            return None

    def get_all_messages(self) -> Dict[str, Any]:
        """
        Get all registered message wrappers.
        
        Returns:
            Dictionary mapping labels to message objects
        """
        if self._messagedb is None:
            logger.warning("MessageDB not available - call initialize() first")
            return {}
        
        try:
            return self._messagedb.getAllMessages()
        except Exception as e:
            logger.error(f"Failed to get messages: {e}")
            return {}

    def wait_for_message(self, label: str, timeout: float = 1.0) -> bool:
        """
        Blocking call that waits until a specific message is received.
        
        Useful for ARTOS Orchestrator test routines that need to verify
        message arrival within a specific time window.
        
        Args:
            label: Message label to wait for
            timeout: Maximum wait time in seconds
            
        Returns:
            True if message was received within timeout, False otherwise
        """
        start_time = time.time()
        while (time.time() - start_time) < timeout:
            if not self.event_queue.empty():
                arrived_label = self.event_queue.get()
                if arrived_label == label:
                    return True
            time.sleep(0.001)  # Small delay to avoid busy-wait
        return False

    # Recording & Replay Methods (ARTOS Integration)
    
    def start_recording(self, filepath: Optional[Path] = None) -> None:
        """
        Begin recording 1553 bus events.
        
        Args:
            filepath: Optional path where to save the recording when stopped
        """
        self.recorder.start_recording()
        self._recording_filepath = filepath
        logger.info("Started recording bus events")

    def stop_recording(self, save: bool = True) -> Optional[Path]:
        """
        Stop recording and optionally save to file.
        
        Args:
            save: Whether to save the recording to file
            
        Returns:
            Path to saved file if save=True, None otherwise
        """
        self.recorder.stop_recording()
        
        if save and self._recording_filepath:
            self.recorder.save_to_file(self._recording_filepath)
            logger.info(f"Recording saved to {self._recording_filepath}")
            return self._recording_filepath
        
        return None

    def load_recording(self, filepath: Path) -> None:
        """
        Load a previously saved recording.
        
        Args:
            filepath: Path to the recording file
        """
        self.recorder.load_from_file(filepath)
        logger.info(f"Loaded recording from {filepath}")

    def _serialize_message(self, message: Any) -> Dict[str, Any]:
        """
        Convert a message object to a JSON-serializable dictionary.
        
        Args:
            message: Message object to serialize
            
        Returns:
            Dictionary representation of the message
        """
        # Basic serialization - will be enhanced when MessageDB is integrated
        if hasattr(message, '__dict__'):
            return {k: v for k, v in message.__dict__.items() 
                   if not k.startswith('_')}
        return {"raw": str(message)}