"""Plugin registry — discovery, loading, and lifecycle management."""

from __future__ import annotations

import importlib
import importlib.metadata
import logging
from typing import Any

from agent_scout.plugins.base import Plugin, PluginConfig, PluginStatus

logger = logging.getLogger(__name__)

ENTRY_POINT_GROUP = "agent_scout.plugins"


class PluginRegistry:
    """Central registry for all plugins.

    Handles plugin discovery (built-in, entry_points, manual),
    dependency resolution, lifecycle management, and acts as the
    ToolExecutor bridge for the ReAct loop.
    """

    def __init__(self) -> None:
        self._plugins: dict[str, Plugin] = {}

    @property
    def plugins(self) -> dict[str, Plugin]:
        """All registered plugins by name."""
        return dict(self._plugins)

    def register(self, plugin: Plugin) -> None:
        """Register a plugin instance."""
        if plugin.name in self._plugins:
            raise ValueError(f"Plugin '{plugin.name}' is already registered")
        self._plugins[plugin.name] = plugin

    def unregister(self, name: str) -> None:
        """Unregister a plugin by name."""
        self._plugins.pop(name, None)

    def get(self, name: str) -> Plugin | None:
        """Get a plugin by name."""
        return self._plugins.get(name)

    def get_enabled(self) -> list[Plugin]:
        """Return all enabled plugins."""
        return [p for p in self._plugins.values() if p.enabled]

    async def load_all(self) -> None:
        """Load and enable all registered plugins, respecting dependencies."""
        load_order = self._resolve_dependencies()
        for name in load_order:
            plugin = self._plugins[name]
            if plugin.status == PluginStatus.UNLOADED:
                try:
                    await plugin.load()
                    await plugin.enable()
                except Exception:
                    logger.exception("Failed to load plugin '%s'", name)
                    plugin._status = PluginStatus.ERROR

    async def unload_all(self) -> None:
        """Unload all plugins in reverse dependency order."""
        load_order = self._resolve_dependencies()
        for name in reversed(load_order):
            plugin = self._plugins.get(name)
            if plugin and plugin.status != PluginStatus.UNLOADED:
                try:
                    await plugin.unload()
                except Exception:
                    logger.exception("Failed to unload plugin '%s'", name)

    def discover_entry_points(self) -> list[type[Plugin]]:
        """Discover plugin classes from installed pip packages.

        Looks for packages that register entry_points under the
        'agent_scout.plugins' group.
        """
        discovered: list[type[Plugin]] = []
        try:
            eps = importlib.metadata.entry_points(group=ENTRY_POINT_GROUP)
        except Exception:
            return discovered

        for ep in eps:
            try:
                plugin_cls = ep.load()
                if isinstance(plugin_cls, type) and issubclass(plugin_cls, Plugin):
                    discovered.append(plugin_cls)
            except Exception:
                logger.exception("Failed to load entry_point '%s'", ep.name)

        return discovered

    def discover_and_register_entry_points(
        self, configs: dict[str, PluginConfig] | None = None
    ) -> int:
        """Discover entry_point plugins, instantiate, and register them.

        Returns the number of newly registered plugins.
        """
        configs = configs or {}
        count = 0
        for cls in self.discover_entry_points():
            try:
                instance = cls(config=configs.get(cls.__name__))
                if instance.name not in self._plugins:
                    self.register(instance)
                    count += 1
            except Exception:
                logger.exception("Failed to instantiate entry_point plugin '%s'", cls.__name__)
        return count

    # --- ToolExecutor bridge ---

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        """Execute a tool call by finding the right plugin.

        This method satisfies the ToolExecutor protocol used by the ReAct loop.
        """
        for plugin in self.get_enabled():
            tool_names = {t.name for t in plugin.get_tools()}
            if tool_name in tool_names:
                return await plugin.execute(tool_name, tool_input)
        raise ValueError(f"No enabled plugin provides tool '{tool_name}'")

    def get_available_tools(self) -> list[dict[str, Any]]:
        """Return metadata for all tools from all enabled plugins.

        This method satisfies the ToolExecutor protocol used by the ReAct loop.
        """
        tools: list[dict[str, Any]] = []
        for plugin in self.get_enabled():
            tools.extend(plugin.get_tool_metadata())
        return tools

    # --- Dependency resolution ---

    def _resolve_dependencies(self) -> list[str]:
        """Topological sort of plugins by their dependencies.

        Returns plugin names in safe load order (dependencies first).
        """
        visited: set[str] = set()
        order: list[str] = []
        in_progress: set[str] = set()

        def visit(name: str) -> None:
            if name in visited:
                return
            if name in in_progress:
                raise RuntimeError(f"Circular dependency detected involving '{name}'")

            in_progress.add(name)
            plugin = self._plugins.get(name)
            if plugin:
                for dep in plugin.dependencies:
                    if dep in self._plugins:
                        visit(dep)
            in_progress.discard(name)
            visited.add(name)
            order.append(name)

        for name in self._plugins:
            visit(name)

        return order
