"""Protocol contracts for mcp_server-like objects.

These contracts define the *minimum* surface area required by infrastructure
components (e.g. background workers and command handlers) without importing
concrete implementations.

Why:
- Avoids duck-typing via hasattr(...)
- Makes the expected interface explicit and type-checkable
- Supports the domain `McpServer` aggregate and any compatible implementations

Notes:
- Protocols are for typing only; they don't enforce runtime inheritance.
- Keep these contracts small and stable.
"""

from __future__ import annotations

from collections.abc import Iterable
from typing import Any, Protocol, runtime_checkable

from ..events import DomainEvent


@runtime_checkable
class SupportsEventCollection(Protocol):
    """Something that buffers domain events and can expose them for publishing."""

    def collect_events(self) -> Iterable[DomainEvent]:
        """Return all currently buffered domain events and clear the buffer."""
        ...


@runtime_checkable
class SupportsHealthCheck(Protocol):
    """Something that can perform an active health check."""

    def health_check(self) -> bool:
        """Return True if healthy, False otherwise."""
        ...


@runtime_checkable
class SupportsIdleShutdown(Protocol):
    """Something that can shut itself down when idle."""

    def maybe_shutdown_idle(self) -> bool:
        """Shutdown when idle past TTL. Returns True if shutdown happened."""
        ...


@runtime_checkable
class SupportsState(Protocol):
    """Something that exposes a state-like object.

    We intentionally keep this loose: state can be an enum with a `.value`
    or a string. Background worker may normalize this.
    """

    @property
    def state(self) -> Any:  # enum-like or str
        ...


@runtime_checkable
class SupportsHealthStats(Protocol):
    """Something that exposes health stats for metrics."""

    @property
    def health(self) -> Any:
        """Health tracker-like object (must expose `consecutive_failures`)."""
        ...


@runtime_checkable
class SupportsMcpServerLifecycle(Protocol):
    """Commands-side lifecycle surface required by command handlers."""

    def ensure_ready(self, *, by_call: bool = False) -> None:
        """Ensure mcp_server is started and ready to accept requests.

        ``by_call``: start it as a call would, not deliberately -- a dead server
        waits out its backoff and a capability-blocked one is refused (#1361).
        The aggregate has always taken it; the contract now says so, for the
        start command's non-deliberate mode (#1446).
        """
        ...

    def shutdown(self, reason: str = "shutdown") -> None:
        """Stop mcp_server and release resources, recording the stop under ``reason``."""
        ...

    def give_up(self, reason: str) -> bool:
        """Stop trying: leave a degraded mcp_server DEAD. Returns whether it did."""
        ...


@runtime_checkable
class SupportsToolInvocation(Protocol):
    """Commands-side tool invocation surface required by command handlers."""

    def invoke_tool(
        self,
        tool_name: str,
        arguments: dict[str, Any],
        timeout: float = 30.0,
        l7_approval_id: str | None = None,
        progress_token: str | None = None,
    ) -> dict[str, Any]:
        """Invoke a tool on the mcp_server.

        ``l7_approval_id``: a granted approval converting an L7
        requireApproval verdict (#921); None means nothing was granted.
        """
        ...

    def get_tool_names(self) -> list[str]:
        """Get list of available tool names."""
        ...


@runtime_checkable
class McpServerRuntime(
    SupportsEventCollection,
    SupportsHealthCheck,
    SupportsIdleShutdown,
    SupportsState,
    SupportsHealthStats,
    SupportsMcpServerLifecycle,
    SupportsToolInvocation,
    Protocol,
):
    """McpServer-like runtime contract required by background worker and command handlers.

    Any object satisfying this protocol can be managed by:
    - GC/health workers
    - CQRS command handlers

    Primary implementation:
    - domain aggregate: `mcp_hangar.domain.model.McpServer`
    """

    @property
    def mcp_server_id(self) -> Any:
        """Stable mcp_server identifier used by runtime management code."""
        ...


@runtime_checkable
class McpServerMapping(Protocol):
    """Dict-like view of mcp_servers consumed by BackgroundWorker.

    BackgroundWorker only needs `.items()` for snapshot iteration.
    """

    def items(self) -> Iterable[tuple[str, McpServerRuntime]]: ...


def normalize_state_to_str(state: Any) -> str:
    """Best-effort normalization of a state-like value to a lower-case string.

    This exists to centralize normalization logic instead of scattering
    `hasattr(state, "value")` checks around infrastructure code.
    """
    if state is None:
        return "unknown"
    value = getattr(state, "value", None)
    if value is not None:
        return str(value).lower()
    return str(state).lower()
