"""MCP refresh hook — wires MCP tool-change notifications to SkillRegistry.

When an MCP server sends a ``notifications/tools/list_changed`` event, this
hook triggers ``SkillRegistry.refresh_all()`` after a configurable debounce
window so that rapid successive notifications collapse into a single refresh.

Usage::

    from cleveragents.mcp.refresh_hook import MCPRefreshHook

    hook = MCPRefreshHook(
        adapter=adapter,
        skill_registry=skill_registry,
        debounce_seconds=0.5,  # default
    )

    # Later, when done:
    hook.cancel()
"""

from __future__ import annotations

import logging
import threading
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from cleveragents.mcp.adapter import MCPToolAdapter
    from cleveragents.skills.registry import SkillRegistry

logger = logging.getLogger(__name__)

_TOOLS_LIST_CHANGED = "notifications/tools/list_changed"
_DEFAULT_DEBOUNCE_SECONDS = 0.5


class MCPRefreshHook:
    """Connects an ``MCPToolAdapter`` to a ``SkillRegistry`` via notifications.

    Registers itself as a notification listener on *adapter* and, upon
    receiving ``notifications/tools/list_changed``, schedules a debounced
    call to ``skill_registry.refresh_all()``.

    Rapid successive notifications within the debounce window are coalesced:
    the timer resets on each notification so only one refresh fires once the
    storm subsides.

    Parameters
    ----------
    adapter:
        The ``MCPToolAdapter`` whose notifications should trigger refresh.
    skill_registry:
        The ``SkillRegistry`` to refresh when tool changes arrive.
    debounce_seconds:
        How long to wait after the *last* notification before triggering
        ``refresh_all()``.  Defaults to ``0.5`` seconds.
    """

    def __init__(
        self,
        adapter: MCPToolAdapter,
        skill_registry: SkillRegistry,
        debounce_seconds: float = _DEFAULT_DEBOUNCE_SECONDS,
    ) -> None:
        if debounce_seconds < 0:
            msg = "debounce_seconds must be >= 0"
            raise ValueError(msg)

        self._adapter = adapter
        self._skill_registry = skill_registry
        self._debounce_seconds = debounce_seconds
        self._timer: threading.Timer | None = None
        self._lock = threading.Lock()
        self._refresh_count = 0

        adapter.add_notification_listener(self._on_notification)

    # -- Internal -------------------------------------------------------------

    def _on_notification(self, method: str, _params: dict[str, Any]) -> None:
        """Handle an incoming MCP notification.

        Only ``notifications/tools/list_changed`` triggers a refresh.
        All other methods are silently ignored.
        """
        if method != _TOOLS_LIST_CHANGED:
            return

        with self._lock:
            if self._timer is not None:
                self._timer.cancel()
            timer = threading.Timer(self._debounce_seconds, self._do_refresh)
            timer.daemon = True
            self._timer = timer

        timer.start()
        logger.debug(
            "MCPRefreshHook: scheduled refresh in %.3fs (server=%s)",
            self._debounce_seconds,
            self._adapter.server_name,
        )

    def _do_refresh(self) -> None:
        """Execute the debounced refresh and log the summary."""
        with self._lock:
            self._timer = None
            self._refresh_count += 1

        result = self._skill_registry.refresh_all()
        logger.info(
            "MCPRefreshHook: refresh_all complete after tools/list_changed "
            "from '%s' — %s",
            self._adapter.server_name,
            result.to_summary(),
        )

    # -- Public API -----------------------------------------------------------

    @property
    def refresh_count(self) -> int:
        """Number of times ``refresh_all`` has been triggered by this hook."""
        with self._lock:
            return self._refresh_count

    def cancel(self) -> None:
        """Cancel any pending debounced refresh.

        Safe to call multiple times and from any thread.
        """
        with self._lock:
            if self._timer is not None:
                self._timer.cancel()
                self._timer = None
                logger.debug(
                    "MCPRefreshHook: cancelled pending refresh for server '%s'",
                    self._adapter.server_name,
                )
