"""Command and Query bus — central dispatch for CQRS.

Both CLI and API submit commands/queries through these buses,
ensuring a single code path for business logic.

Usage:
    bus = CommandBus(event_store=store)
    bus.register(AddProjectCommand, ProjectCommandHandler(registry, store))
    result = await bus.dispatch(AddProjectCommand(name="my-app", path="/home/dev/app"))

    qbus = QueryBus()
    qbus.register(ListProjectsQuery, ProjectQueryHandler(registry))
    projects = await qbus.dispatch(ListProjectsQuery(source_type="local"))
"""

from __future__ import annotations

import inspect
from dataclasses import dataclass, field
from typing import Any, Callable, Awaitable

import structlog

from proxym.domain.events import EventStore

logger = structlog.get_logger()


# ---------------------------------------------------------------------------
# Base command / query
# ---------------------------------------------------------------------------

@dataclass
class Command:
    """Base class for commands (write operations).

    Commands express intent: "I want to add a project", "I want to assign a task".
    They are dispatched through CommandBus to the appropriate handler.
    """

    actor: str = "system"  # "cli", "api", "mcp", user_id


@dataclass
class Query:
    """Base class for queries (read operations).

    Queries express information needs: "List all projects", "Get project details".
    They are dispatched through QueryBus to the appropriate handler.
    """

    pass


# ---------------------------------------------------------------------------
# Command Bus
# ---------------------------------------------------------------------------

class CommandBus:
    """Dispatches commands to registered handlers.

    Each command type maps to exactly one handler.
    Handlers execute business logic and emit domain events.
    """

    def __init__(self, event_store: EventStore | None = None) -> None:
        self._handlers: dict[type, Any] = {}
        self.event_store = event_store or EventStore()

    def register(self, command_type: type, handler: Any) -> None:
        """Register a handler for a command type.

        The handler must have a ``handle(command)`` or ``__call__(command)`` method.
        """
        self._handlers[command_type] = handler
        logger.debug("command_handler_registered", command=command_type.__name__)

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

        Returns the handler result (typically a domain object or dict).
        Raises ValueError if no handler is registered.
        """
        handler = self._handlers.get(type(command))
        if handler is None:
            raise ValueError(f"No handler registered for {type(command).__name__}")

        logger.info(
            "command_dispatched",
            command=type(command).__name__,
            actor=command.actor,
        )

        if hasattr(handler, "handle"):
            result = handler.handle(command)
        elif callable(handler):
            result = handler(command)
        else:
            raise TypeError(f"Handler for {type(command).__name__} is not callable")

        if inspect.isawaitable(result):
            return await result
        return result

    @property
    def registered_commands(self) -> list[str]:
        """List registered command type names."""
        return [t.__name__ for t in self._handlers]


# ---------------------------------------------------------------------------
# Query Bus
# ---------------------------------------------------------------------------

class QueryBus:
    """Dispatches queries to registered handlers.

    Each query type maps to exactly one handler.
    Handlers are read-only and never emit events.
    """

    def __init__(self) -> None:
        self._handlers: dict[type, Any] = {}

    def register(self, query_type: type, handler: Any) -> None:
        """Register a handler for a query type."""
        self._handlers[query_type] = handler
        logger.debug("query_handler_registered", query=query_type.__name__)

    async def dispatch(self, query: Query) -> Any:
        """Dispatch a query to its handler.

        Returns the handler result (list, dict, etc.).
        Raises ValueError if no handler is registered.
        """
        handler = self._handlers.get(type(query))
        if handler is None:
            raise ValueError(f"No handler registered for {type(query).__name__}")

        if hasattr(handler, "handle"):
            result = handler.handle(query)
        elif callable(handler):
            result = handler(query)
        else:
            raise TypeError(f"Handler for {type(query).__name__} is not callable")

        if inspect.isawaitable(result):
            return await result
        return result

    @property
    def registered_queries(self) -> list[str]:
        """List registered query type names."""
        return [t.__name__ for t in self._handlers]
