"""Command dispatcher for routing commands to appropriate components."""

from __future__ import annotations

from typing import Any, Callable
from weakref import WeakMethod

from pytola_sim.lscsim.utils.logger import get_logger

logger = get_logger(__name__)


class CommandDispatcher:
    """Handles command routing and execution."""

    def __init__(self) -> None:
        self._command_routes: dict[str, list[Callable[..., bool]]] = {}
        self._component_commands: dict[str, str] = {}  # command_id -> component_id
        self._command_history: list[dict[str, Any]] = []
        self._max_history: int = 100

    def register_command(self, command_id: str, handler: Callable[..., bool]) -> None:
        """Register a command handler."""
        if command_id not in self._command_routes:
            self._command_routes[command_id] = []

        # Use WeakMethod to prevent circular references
        weak_handler = WeakMethod(handler)
        self._command_routes[command_id].append(weak_handler)
        logger.debug(f"Registered command handler for: {command_id}")

    def register_component_command(self, command_id: str, component_id: str) -> None:
        """Register which component handles a command."""
        self._component_commands[command_id] = component_id
        logger.debug(f"Command {command_id} registered for component {component_id}")

    def execute_command(
        self,
        command_id: str,
        in_param: object = None,
        out_param: object = None,
    ) -> bool:
        """Execute a command by routing to registered handlers."""
        logger.debug(f"Executing command: {command_id}")

        # Record command in history
        self._record_command(command_id, in_param)

        if command_id not in self._command_routes:
            logger.warning(f"No handlers registered for command: {command_id}")
            return False

        success = False
        handlers_to_remove = []

        # Execute all registered handlers
        for i, weak_handler in enumerate(self._command_routes[command_id]):
            result = self._execute_command_handler(
                weak_handler,
                command_id,
                in_param,
                out_param,
                handlers_to_remove,
                i,
            )
            success = success or result

        # Clean up dead handlers
        for i in reversed(handlers_to_remove):
            self._command_routes[command_id].pop(i)

        if not self._command_routes[command_id]:
            del self._command_routes[command_id]

        logger.info(
            f"Command {command_id} execution {'successful' if success else 'failed'}",
        )
        return success

    def _execute_command_handler(
        self,
        weak_handler: object,
        command_id: str,
        in_param: object,
        out_param: object,
        handlers_to_remove: list[int],
        index: int,
    ) -> bool:
        """Execute a single command handler with exception handling."""
        try:
            handler = weak_handler()  # type: ignore[attr-defined]
            if handler is not None:
                result = handler(command_id, in_param, out_param)  # type: ignore[attr-defined]
                return result
            # Handler has been garbage collected
            handlers_to_remove.append(index)
        except Exception:
            logger.exception(f"Error executing command {command_id}")
        return False

    def has_command(self, command_id: str) -> bool:
        """Check if command is registered."""
        return command_id in self._command_routes

    def get_command_handlers(self, command_id: str) -> list[Callable[..., bool]]:
        """Get active handlers for a command."""
        if command_id not in self._command_routes:
            return []

        handlers = []
        for weak_handler in self._command_routes[command_id]:
            handler = weak_handler()
            if handler is not None:
                handlers.append(handler)

        return handlers

    def get_registered_commands(self) -> list[str]:
        """Get all registered command IDs."""
        return list(self._command_routes.keys())

    def get_component_for_command(self, command_id: str) -> str | None:
        """Get which component handles a command."""
        return self._component_commands.get(command_id)

    def _record_command(self, command_id: str, in_param: Any) -> None:  # noqa: ANN401
        """Record command execution in history."""
        import datetime

        record = {
            "command_id": command_id,
            "parameters": str(in_param)[:100],  # Limit parameter string length
            "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        }

        self._command_history.append(record)

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

    def get_command_history(self, limit: int = 10) -> list[dict[str, Any]]:
        """Get recent command execution history."""
        return self._command_history[-limit:]

    def clear_history(self) -> None:
        """Clear command history."""
        self._command_history.clear()
        logger.debug("Command history cleared")


class CommandRouter:
    """Higher-level command routing with component awareness."""

    def __init__(self, main_controller: Any) -> None:  # noqa: ANN401
        self._dispatcher = CommandDispatcher()
        self._main_controller = main_controller
        self._command_aliases: dict[str, str] = {}

    def route_command(
        self,
        command_id: str,
        in_param: Any = None,  # noqa: ANN401
        out_param: Any = None,  # noqa: ANN401
    ) -> bool:
        """Route command through the main controller system."""
        # Resolve command aliases
        actual_command = self._command_aliases.get(command_id, command_id)

        # Let main controller handle component-specific routing
        return self._main_controller.execute_command(
            actual_command,
            in_param,
            out_param,
        )

    def add_command_alias(self, alias: str, actual_command: str) -> None:
        """Add command alias mapping."""
        self._command_aliases[alias] = actual_command
        logger.debug(f"Command alias added: {alias} -> {actual_command}")

    def get_dispatcher(self) -> CommandDispatcher:
        """Get the underlying command dispatcher."""
        return self._dispatcher
