"""Message bus system for inter-component communication."""

from __future__ import annotations

from typing import Any, Callable
from weakref import WeakMethod, WeakSet

from pytola_sim.lscsim.utils.logger import get_logger

logger = get_logger(__name__)


class MessageBus:
    """Central message broker for publish-subscribe pattern."""

    def __init__(self) -> None:
        self._subscribers: dict[str, WeakSet] = {}
        self._message_filters: dict[str, list[Callable[[str, int, Any], bool]]] = {}
        self._message_history: list[dict[str, Any]] = []
        self._max_history: int = 1000
        self._enabled: bool = True

    def subscribe(
        self,
        message_type: str,
        handler: Callable[[str, int, Any], None],
    ) -> None:
        """Subscribe to a message type."""
        if message_type not in self._subscribers:
            self._subscribers[message_type] = WeakSet()

        # Use WeakMethod to prevent circular references
        weak_handler = WeakMethod(
            handler.__call__ if callable(handler) else handler,
        )
        self._subscribers[message_type].add(weak_handler)
        logger.debug(f"Subscribed to message type: {message_type}")

    def unsubscribe(
        self,
        message_type: str,
        handler: Callable[[str, int, Any], None],
    ) -> bool:
        """Unsubscribe from a message type."""
        if message_type in self._subscribers:
            try:
                # Remove the handler
                handlers_to_remove = []
                for weak_handler in self._subscribers[message_type]:
                    actual_handler = weak_handler()
                    if actual_handler == handler or (
                        hasattr(actual_handler, "__self__") and actual_handler.__self__ == handler.__self__
                    ):
                        handlers_to_remove.append(weak_handler)

                for handler_to_remove in handlers_to_remove:
                    self._subscribers[message_type].discard(handler_to_remove)

                # Clean up empty subscriber lists
                if not self._subscribers[message_type]:
                    del self._subscribers[message_type]

                logger.debug(f"Unsubscribed from message type: {message_type}")
            except Exception:
                logger.exception(f"Error unsubscribing from {message_type}")
        else:
            return True

        return False

    def publish(self, message_type: str, value: int = 0, data: object = None) -> None:
        """Publish a message to all subscribers."""
        if not self._enabled:
            logger.debug("Message bus is disabled, message not published")
            return

        logger.debug(f"Publishing message: {message_type}")

        # Record message in history
        self._record_message(message_type, value, data)

        # Apply filters
        if message_type in self._message_filters:
            for filter_func in self._message_filters[message_type]:
                try:
                    if not filter_func(message_type, value, data):
                        logger.debug(f"Message {message_type} filtered out")
                        return
                except Exception:  # noqa: PERF203
                    logger.exception(f"Error in message filter for {message_type}")

        # Deliver to subscribers
        if message_type in self._subscribers:
            handlers_to_remove = []

            for weak_handler in list(self._subscribers[message_type]):
                try:
                    handler = weak_handler()
                    if handler is not None:
                        handler(message_type, value, data)
                    else:
                        # Handler has been garbage collected
                        handlers_to_remove.append(weak_handler)
                except Exception:  # noqa: PERF203
                    logger.exception(f"Error delivering message {message_type}")

            # Clean up dead handlers
            for handler_to_remove in handlers_to_remove:
                self._subscribers[message_type].discard(handler_to_remove)

            # Remove empty subscriber sets
            if not self._subscribers[message_type]:
                del self._subscribers[message_type]

    def add_filter(
        self,
        message_type: str,
        filter_func: Callable[[str, int, Any], bool],
    ) -> None:
        """Add a filter function for a message type."""
        if message_type not in self._message_filters:
            self._message_filters[message_type] = []

        self._message_filters[message_type].append(filter_func)
        logger.debug(f"Added filter for message type: {message_type}")

    def remove_filter(
        self,
        message_type: str,
        filter_func: Callable[[str, int, Any], bool],
    ) -> bool:
        """Remove a filter function."""
        if message_type in self._message_filters:
            try:
                self._message_filters[message_type].remove(filter_func)
                if not self._message_filters[message_type]:
                    del self._message_filters[message_type]
                logger.debug(f"Removed filter for message type: {message_type}")
            except ValueError:
                pass
        else:
            return True
        return False

    def get_subscriber_count(self, message_type: str) -> int:
        """Get number of subscribers for a message type."""
        if message_type in self._subscribers:
            # Count only alive handlers
            alive_count = 0
            for weak_handler in self._subscribers[message_type]:
                if weak_handler() is not None:
                    alive_count += 1
            return alive_count
        return 0

    def get_all_message_types(self) -> list[str]:
        """Get all message types with subscribers."""
        return list(self._subscribers.keys())

    def has_subscribers(self, message_type: str) -> bool:
        """Check if message type has active subscribers."""
        return self.get_subscriber_count(message_type) > 0

    def enable(self) -> None:
        """Enable message publishing."""
        self._enabled = True
        logger.debug("Message bus enabled")

    def disable(self) -> None:
        """Disable message publishing."""
        self._enabled = False
        logger.debug("Message bus disabled")

    def is_enabled(self) -> bool:
        """Check if message bus is enabled."""
        return self._enabled

    def clear_subscribers(self, message_type: str | None = None) -> None:
        """Clear subscribers for a specific message type or all."""
        if message_type:
            if message_type in self._subscribers:
                self._subscribers[message_type].clear()
                del self._subscribers[message_type]
            logger.debug(f"Cleared subscribers for: {message_type}")
        else:
            self._subscribers.clear()
            logger.debug("Cleared all subscribers")

    def _record_message(self, message_type: str, value: int, data: Any) -> None:  # noqa: ANN401
        """Record message in history."""
        import datetime

        record = {
            "message_type": message_type,
            "value": value,
            "data_size": len(str(data)) if data else 0,
            "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        }

        self._message_history.append(record)

        # Maintain history size limit
        if len(self._message_history) > self._max_history:
            self._message_history.pop(0)

    def get_message_history(self, limit: int = 10) -> list[dict[str, Any]]:
        """Get recent message history."""
        return self._message_history[-limit:]

    def clear_history(self) -> None:
        """Clear message history."""
        self._message_history.clear()
        logger.debug("Message history cleared")


class MessageRouter:
    """Advanced message routing with priority and queuing."""

    def __init__(self, message_bus: MessageBus) -> None:
        self._message_bus = message_bus
        self._priority_queues: dict[int, list[dict[str, Any]]] = {}
        self._message_priorities: dict[str, int] = {}
        self._processing: bool = False

    def set_message_priority(self, message_type: str, priority: int) -> None:
        """Set priority for a message type (higher number = higher priority)."""
        self._message_priorities[message_type] = priority
        logger.debug(f"Set priority {priority} for message type: {message_type}")

    def queue_message(
        self,
        message_type: str,
        value: int = 0,
        data: Any = None,  # noqa: ANN401
    ) -> None:
        """Queue message for later processing."""
        priority = self._message_priorities.get(message_type, 0)

        if priority not in self._priority_queues:
            self._priority_queues[priority] = []

        message = {
            "type": message_type,
            "value": value,
            "data": data,
            "priority": priority,
        }

        self._priority_queues[priority].append(message)
        logger.debug(f"Queued message {message_type} with priority {priority}")

    def process_queued_messages(self) -> None:
        """Process all queued messages in priority order."""
        if self._processing:
            logger.warning("Message router is already processing")
            return

        self._processing = True
        try:
            # Process messages in descending priority order
            priorities = sorted(self._priority_queues.keys(), reverse=True)

            for priority in priorities:
                queue = self._priority_queues[priority]
                while queue:
                    message = queue.pop(0)
                    self._message_bus.publish(
                        message["type"],
                        message["value"],
                        message["data"],
                    )

            # Clean up empty queues
            self._priority_queues.clear()

        finally:
            self._processing = False
            logger.debug("Finished processing queued messages")

    def clear_queue(self, priority: int | None = None) -> None:
        """Clear message queue."""
        if priority is not None:
            if priority in self._priority_queues:
                self._priority_queues[priority].clear()
                del self._priority_queues[priority]
        else:
            self._priority_queues.clear()
        logger.debug(f"Cleared message queue (priority: {priority})")
