"""Command Bus for decoupling CLI from application services.

This module provides a command bus pattern to decouple the CLI layer from
the application services layer. CLI commands dispatch command objects through
the bus, which routes them to appropriate handlers in the application layer.

This ensures:
- CLI has no direct dependencies on application services
- Clear separation of concerns between layers
- Easier testing and maintenance
- Flexibility to change service implementations
"""

from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import Any


class Command:
    """Base class for all commands dispatched through the command bus.

    Commands represent requests from the CLI layer to the application layer.
    Each command encapsulates the data needed to perform a specific operation.
    """

    pass


class CommandHandler[TCommand: Command, TResult](ABC):
    """Base class for command handlers.

    A handler processes a specific command type and returns a result.
    Handlers are registered with the command bus and invoked when
    matching commands are dispatched.
    """

    @abstractmethod
    def handle(self, command: TCommand) -> TResult:
        """Handle the given command and return a result.

        Args:
            command: The command to handle

        Returns:
            The result of handling the command
        """
        pass


class CommandBus:
    """Central command dispatcher for CLI-to-application communication.

    The command bus decouples the CLI layer from application services by
    accepting command objects and routing them to registered handlers.
    This ensures the CLI layer has no direct imports from the application
    services layer.
    """

    def __init__(self) -> None:
        """Initialize the command bus with an empty handler registry."""
        self._handlers: dict[type, Callable[[Any], Any]] = {}

    def register_handler(
        self, command_type: type, handler: Callable[[Any], Any]
    ) -> None:
        """Register a handler for a specific command type.

        Args:
            command_type: The command class this handler handles
            handler: A callable that accepts a command and returns a result
        """
        self._handlers[command_type] = handler

    def dispatch(self, command: Command) -> Any:
        """Dispatch a command to its registered handler.

        Args:
            command: The command to dispatch

        Returns:
            The result from the handler

        Raises:
            ValueError: If no handler is registered for the command type
        """
        command_type = type(command)
        if command_type not in self._handlers:
            raise ValueError(
                f"No handler registered for command type: {command_type.__name__}"
            )
        handler = self._handlers[command_type]
        return handler(command)

    def has_handler(self, command_type: type) -> bool:
        """Check if a handler is registered for a command type.

        Args:
            command_type: The command class to check

        Returns:
            True if a handler is registered, False otherwise
        """
        return command_type in self._handlers


# Global command bus instance
_command_bus: CommandBus | None = None


def get_command_bus() -> CommandBus:
    """Get the global command bus instance.

    Returns:
        The global CommandBus instance
    """
    global _command_bus
    if _command_bus is None:
        _command_bus = CommandBus()
    return _command_bus


def set_command_bus(bus: CommandBus) -> None:
    """Set the global command bus instance.

    This is primarily used for testing to inject a mock or test bus.

    Args:
        bus: The CommandBus instance to use globally
    """
    global _command_bus
    _command_bus = bus
