"""Shared server composition state and runtime accessors.

This module centralizes bootstrap-owned mutable state that is shared across
server startup and runtime wiring.
"""

from __future__ import annotations

from collections.abc import Callable
from threading import Lock
from typing import Any, TYPE_CHECKING, cast
import warnings

from ...application.discovery import DiscoveryOrchestrator
from ...application.sagas import GroupRebalanceSaga
from ...bootstrap.runtime import create_runtime
from ...domain.model import McpServerGroup
from ...infrastructure.runtime_store import RuntimeMcpServerStore
from ...logging_config import get_logger

if TYPE_CHECKING:
    from ...bootstrap.runtime import Runtime

logger = get_logger(__name__)

# Runtime wiring
_runtime: Runtime | None = None
_runtime_lock = Lock()

_DEPRECATED_RUNTIME_EXPORTS: dict[str, tuple[str, str]] = {
    "PROVIDER_REPOSITORY": ("repository", "get_runtime().repository"),
    "EVENT_BUS": ("event_bus", "get_runtime().event_bus"),
    "COMMAND_BUS": ("command_bus", "get_runtime().command_bus"),
    "QUERY_BUS": ("query_bus", "get_runtime().query_bus"),
    "RATE_LIMIT_CONFIG": ("rate_limit_config", "get_runtime().rate_limit_config"),
    "RATE_LIMITER": ("rate_limiter", "get_runtime().rate_limiter"),
    "INPUT_VALIDATOR": ("input_validator", "get_runtime().input_validator"),
    "SECURITY_HANDLER": ("security_handler", "get_runtime().security_handler"),
    "PROVIDERS": ("repository", "get_runtime().repository"),
}

if TYPE_CHECKING:
    COMMAND_BUS: object
    EVENT_BUS: object
    INPUT_VALIDATOR: object
    PROVIDER_REPOSITORY: object
    PROVIDERS: object
    QUERY_BUS: object
    RATE_LIMIT_CONFIG: object
    RATE_LIMITER: object
    SECURITY_HANDLER: object

# McpServer Groups storage
GROUPS: dict[str, McpServerGroup] = {}

# Runtime (hot-loaded) mcp_servers storage
RUNTIME_PROVIDERS: RuntimeMcpServerStore = RuntimeMcpServerStore()

# Saga and discovery instances (initialized in main())
_group_rebalance_saga: GroupRebalanceSaga | None = None
_discovery_orchestrator: DiscoveryOrchestrator | None = None
_persistence_backend: Any = None

# What bootstrap starts that no component it hands out will stop: the loop
# behind the fleet writer and the one behind the fleet projection. The handlers
# holding them live as long as the buses do, so nobody else would (#1389).
_closers: list[Callable[[], None]] = []


def close_at_shutdown(close: Callable[[], None]) -> None:
    """Have `ApplicationContext.shutdown` call `close`."""
    _closers.append(close)


def close_what_bootstrap_started() -> None:
    """Call every closer `close_at_shutdown` was given, newest first, once each."""
    while _closers:
        close = _closers.pop()
        try:
            close()
        except Exception as e:  # noqa: BLE001 -- fault-barrier: one failed close must not leave the rest running
            logger.warning("bootstrap_resource_close_failed", error=str(e))


def get_runtime(
    rate_limit: dict[str, Any] | None = None,
    persistence_backend: Any = None,
) -> Runtime:
    """Get the lazily initialized runtime singleton.

    Args:
        rate_limit: Optional ``rate_limit`` config section forwarded to
            :func:`create_runtime` on first construction. Only applied when the
            singleton is created; subsequent calls return the existing runtime.
        persistence_backend: The selected storage backend, forwarded the same
            way. It has to arrive on the first call, because the runtime is
            frozen once built -- which is why bootstrap selects the backend
            before it asks for the runtime rather than after.
    """
    global _runtime
    if _runtime is None:
        with _runtime_lock:
            if _runtime is None:
                _runtime = create_runtime(rate_limit=rate_limit, persistence_backend=persistence_backend)
    return _runtime


def __getattr__(name: str) -> object:
    """Lazily resolve deprecated runtime-backed module attributes."""
    export = _DEPRECATED_RUNTIME_EXPORTS.get(name)
    if export is None:
        msg = f"module {__name__!r} has no attribute {name!r}"
        raise AttributeError(msg)

    runtime_attr, replacement = export
    warnings.warn(
        f"mcp_hangar.server.bootstrap.composition.{name} is deprecated; use {replacement} instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return cast(object, getattr(get_runtime(), runtime_attr))


def set_persistence_backend(backend: Any) -> None:
    """Record the one storage backend this process persists through.

    Held here rather than on `Runtime`, which is a frozen dataclass on purpose:
    the runtime is assembled once and not mutated afterwards, and the storage
    decision arrives during bootstrap like the discovery orchestrator does.
    """
    global _persistence_backend
    _persistence_backend = backend


def get_persistence_backend() -> Any:
    """The selected storage backend, or None when none was configured."""
    return _persistence_backend


def set_discovery_orchestrator(orchestrator: DiscoveryOrchestrator | None) -> None:
    """Set the discovery orchestrator instance."""
    global _discovery_orchestrator
    _discovery_orchestrator = orchestrator


def get_discovery_orchestrator() -> DiscoveryOrchestrator | None:
    """Get the discovery orchestrator instance."""
    return _discovery_orchestrator


def set_group_rebalance_saga(saga: GroupRebalanceSaga | None) -> None:
    """Set the group rebalance saga instance."""
    global _group_rebalance_saga
    _group_rebalance_saga = saga


def get_group_rebalance_saga() -> GroupRebalanceSaga | None:
    """Get the group rebalance saga instance."""
    return _group_rebalance_saga


def get_runtime_mcp_servers() -> RuntimeMcpServerStore:
    """Get the runtime mcp_servers store."""
    return RUNTIME_PROVIDERS


__all__ = [
    "COMMAND_BUS",
    "EVENT_BUS",
    "GROUPS",
    "INPUT_VALIDATOR",
    "PROVIDER_REPOSITORY",
    "PROVIDERS",
    "QUERY_BUS",
    "RATE_LIMIT_CONFIG",
    "RATE_LIMITER",
    "RUNTIME_PROVIDERS",
    "SECURITY_HANDLER",
    "get_discovery_orchestrator",
    "get_group_rebalance_saga",
    "get_runtime",
    "get_runtime_mcp_servers",
    "set_discovery_orchestrator",
    "set_group_rebalance_saga",
]
