"""Plugin manager responsible for registering and serving library plugins."""

from __future__ import annotations

import logging
import threading
from typing import Dict, Iterable, List, Optional

from .contracts import (
    InstallAction,
    KeywordOverrideHandler,
    LibraryCapabilities,
    LibraryHints,
    LibraryPlugin,
    LibraryStateProvider,
    LibraryMetadata,
    PromptBundle,
    TypeConversionProvider,
)
from .discovery import iter_entry_point_plugins, iter_manifest_plugins

logger = logging.getLogger(__name__)


class LibraryPluginManager:
    """Central registry of rf-mcp library plugins."""

    def __init__(self) -> None:
        self._plugins: Dict[str, LibraryPlugin] = {}
        self._metadata: Dict[str, LibraryMetadata] = {}
        self._capabilities: Dict[str, LibraryCapabilities] = {}
        self._install_actions: Dict[str, List[InstallAction]] = {}
        self._hints: Dict[str, LibraryHints] = {}
        self._prompts: Dict[str, PromptBundle] = {}
        self._lock = threading.RLock()
        self._sources: Dict[str, str] = {}
        self._keyword_map: Dict[str, str] = {}
        self._keyword_overrides: Dict[tuple[str, str], KeywordOverrideHandler] = {}

    # ------------------------------------------------------------------
    # Registration
    # ------------------------------------------------------------------
    def register_plugin(self, plugin: LibraryPlugin, source: str = "builtin") -> None:
        """Register a plugin instance."""
        metadata = plugin.get_metadata()

        if metadata.schema_version < 1:
            logger.warning(
                "Skipping plugin %s due to unsupported schema version %s",
                metadata.name,
                metadata.schema_version,
            )
            return

        with self._lock:
            self._plugins[metadata.name] = plugin
            self._metadata[metadata.name] = metadata

            capabilities = plugin.get_capabilities()
            if capabilities:
                self._capabilities[metadata.name] = capabilities
            else:
                self._capabilities.pop(metadata.name, None)

            install_actions = plugin.get_install_actions()
            if install_actions:
                self._install_actions[metadata.name] = list(install_actions)
            else:
                self._install_actions.pop(metadata.name, None)

            hints = plugin.get_hints()
            if hints:
                self._hints[metadata.name] = hints
            else:
                self._hints.pop(metadata.name, None)

            prompts = plugin.get_prompt_bundle()
            if prompts:
                self._prompts[metadata.name] = prompts
            else:
                self._prompts.pop(metadata.name, None)

            self._sources[metadata.name] = source

            keyword_map = plugin.get_keyword_library_map()
            if keyword_map:
                for keyword, lib_name in keyword_map.items():
                    if not keyword:
                        continue
                    self._keyword_map[keyword.lower()] = lib_name

            overrides = plugin.get_keyword_overrides()
            if overrides:
                for keyword, handler in overrides.items():
                    if not keyword or handler is None:
                        continue
                    self._keyword_overrides[(metadata.name, keyword.lower())] = handler

            logger.debug("Registered library plugin '%s' from %s", metadata.name, source)

    def register_plugins(
        self, plugins: Iterable[LibraryPlugin], source: str = "builtin"
    ) -> None:
        for plugin in plugins:
            self.register_plugin(plugin, source=source)

    # ------------------------------------------------------------------
    # Discovery helpers
    # ------------------------------------------------------------------
    def discover_entry_point_plugins(self) -> None:
        self.register_plugins(iter_entry_point_plugins(), source="entry_point")

    def discover_manifest_plugins(self, paths: Optional[List[str]] = None) -> None:
        self.register_plugins(iter_manifest_plugins(paths), source="manifest")

    # ------------------------------------------------------------------
    # Lookup
    # ------------------------------------------------------------------
    def get_plugin(self, name: str) -> Optional[LibraryPlugin]:
        return self._plugins.get(name)

    def get_metadata(self, name: str) -> Optional[LibraryMetadata]:
        return self._metadata.get(name)

    def get_capabilities(self, name: str) -> Optional[LibraryCapabilities]:
        return self._capabilities.get(name)

    def get_install_actions(self, name: str) -> List[InstallAction]:
        return self._install_actions.get(name, [])

    def get_hints(self, name: str) -> Optional[LibraryHints]:
        return self._hints.get(name)

    def get_prompt_bundle(self, name: str) -> Optional[PromptBundle]:
        return self._prompts.get(name)

    def get_plugin_source(self, name: str) -> Optional[str]:
        return self._sources.get(name)

    def get_state_provider(self, name: str) -> Optional[LibraryStateProvider]:
        plugin = self.get_plugin(name)
        if plugin:
            try:
                return plugin.get_state_provider()
            except Exception as exc:  # pragma: no cover - defensive
                logger.warning(
                    "Failed to obtain state provider for %s: %s", name, exc
                )
        return None

    def get_type_conversion_provider(self, name: str) -> Optional[TypeConversionProvider]:
        plugin = self.get_plugin(name)
        if plugin:
            try:
                return plugin.get_type_converters()
            except Exception as exc:  # pragma: no cover - defensive
                logger.warning(
                    "Failed to obtain type conversion provider for %s: %s", name, exc
                )
        return None

    def list_plugin_names(self) -> List[str]:
        return sorted(self._metadata.keys(), key=str.casefold)

    def iter_metadata(self):
        for name, meta in self._metadata.items():
            yield name, meta

    def iter_capabilities(self):
        for name, cap in self._capabilities.items():
            yield name, cap

    def get_library_for_keyword(self, keyword: str) -> Optional[str]:
        return self._keyword_map.get(keyword.lower())

    def get_keyword_override(
        self, library_name: Optional[str], keyword: str
    ) -> Optional[KeywordOverrideHandler]:
        if not library_name:
            return None
        return self._keyword_overrides.get((library_name, keyword.lower()))

    def run_before_keyword_execution(
        self,
        library_name: Optional[str],
        session: Any,
        keyword_name: str,
        library_manager: Any,
        keyword_discovery: Any,
    ) -> None:
        if not library_name:
            return
        plugin = self._plugins.get(library_name)
        if not plugin:
            return
        try:
            plugin.before_keyword_execution(
                session,
                keyword_name,
                library_manager,
                keyword_discovery,
            )
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "Plugin %s before_keyword_execution failed: %s",
                library_name,
                exc,
            )

    def generate_failure_hints(
        self,
        library_name: Optional[str],
        session: Any,
        keyword_name: str,
        arguments: List[Any],
        error_text: str,
    ) -> List[Dict[str, Any]]:
        if not library_name:
            return []
        plugin = self._plugins.get(library_name)
        if not plugin:
            return []
        try:
            hints = plugin.generate_failure_hints(
                session,
                keyword_name,
                arguments,
                error_text,
            )
            return hints or []
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "Plugin %s generate_failure_hints failed: %s",
                library_name,
                exc,
            )
            return []

    def validate_keyword_for_session(
        self,
        session_library: Optional[str],
        session: Any,
        keyword_name: str,
        keyword_source_library: Optional[str],
    ) -> Optional[Dict[str, Any]]:
        """Validate keyword compatibility with session library.

        Delegates to the session library's plugin to check if the keyword
        from another library can be used.

        Returns None if valid, or error dict if incompatible.
        """
        if not session_library:
            return None

        plugin = self._plugins.get(session_library)
        if not plugin:
            return None

        try:
            return plugin.validate_keyword_for_session(
                session, keyword_name, keyword_source_library
            )
        except Exception as exc:
            logger.debug(
                "Plugin %s validate_keyword_for_session failed: %s",
                session_library,
                exc,
            )
            return None

    def get_incompatible_libraries(self, library_name: str) -> List[str]:
        """Get list of libraries incompatible with the given library."""
        plugin = self._plugins.get(library_name)
        if not plugin:
            return []

        try:
            return plugin.get_incompatible_libraries()
        except Exception as exc:
            logger.debug(
                "Plugin %s get_incompatible_libraries failed: %s",
                library_name,
                exc,
            )
            return []

    def get_keyword_alternatives(self, library_name: str) -> Dict[str, Dict[str, Any]]:
        """Get keyword alternatives mapping for the given library."""
        plugin = self._plugins.get(library_name)
        if not plugin:
            return {}

        try:
            return plugin.get_keyword_alternatives()
        except Exception as exc:
            logger.debug(
                "Plugin %s get_keyword_alternatives failed: %s",
                library_name,
                exc,
            )
            return {}


_GLOBAL_MANAGER: Optional[LibraryPluginManager] = None
_GLOBAL_MANAGER_LOCK = threading.Lock()


def get_library_plugin_manager() -> LibraryPluginManager:
    """Return the process-wide plugin manager."""
    global _GLOBAL_MANAGER
    if _GLOBAL_MANAGER is None:
        with _GLOBAL_MANAGER_LOCK:
            if _GLOBAL_MANAGER is None:
                _GLOBAL_MANAGER = LibraryPluginManager()
    return _GLOBAL_MANAGER


def reset_library_plugin_manager_for_tests() -> None:
    """Reset the global manager (intended for tests)."""
    global _GLOBAL_MANAGER
    with _GLOBAL_MANAGER_LOCK:
        _GLOBAL_MANAGER = LibraryPluginManager()
