# -*- coding: utf-8 -*-
"""Location: ./mcpgateway/services/plugin_service.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Plugin service for managing and querying ContextForge plugins.
This module provides a service layer for accessing plugin information,
statistics, and configuration from the PluginManager.
"""

# Standard
from collections import defaultdict
import logging
from typing import Any, Dict, List, Optional

# Third-Party
from cpex.framework import PluginManager
from cpex.framework.models import PluginMode

logger = logging.getLogger(__name__)

# Cache import (lazy to avoid circular dependencies)
_ADMIN_STATS_CACHE = None


def _get_admin_stats_cache():
    """Get admin stats cache singleton lazily.

    Returns:
        AdminStatsCache instance.
    """
    global _ADMIN_STATS_CACHE  # pylint: disable=global-statement
    if _ADMIN_STATS_CACHE is None:
        # First-Party
        from mcpgateway.cache.admin_stats_cache import admin_stats_cache  # pylint: disable=import-outside-toplevel

        _ADMIN_STATS_CACHE = admin_stats_cache
    return _ADMIN_STATS_CACHE


class PluginService:
    """Service for managing plugin information and statistics."""

    def __init__(self, plugin_manager: Optional[PluginManager] = None):
        """Initialize the plugin service.

        Args:
            plugin_manager: Optional PluginManager instance. If not provided,
                           attempts to get from app state will be made at runtime.
        """
        self._plugin_manager = plugin_manager

    def get_plugin_manager(self) -> Optional[PluginManager]:
        """Get the plugin manager instance.

        Returns:
            PluginManager instance or None if plugins are disabled.
        """
        return self._plugin_manager

    def set_plugin_manager(self, manager: Optional[PluginManager]) -> None:
        """Set or clear the plugin manager instance.

        ``None`` is accepted so the admin runtime-toggle handler can detach the
        cached manager when the subsystem is disabled at runtime (otherwise the
        admin surfaces keep reading a now-stale manager until restart).

        Args:
            manager: The PluginManager instance to attach, or ``None`` to clear.
        """
        self._plugin_manager = manager

    def get_all_plugins(self) -> List[Dict[str, Any]]:
        """Get all registered plugins with their configuration, including disabled plugins.

        Returns:
            List of plugin dictionaries containing configuration and status.
        """
        if not self._plugin_manager:
            return []

        plugins = []
        registry = self._plugin_manager._registry  # pylint: disable=protected-access
        config = self._plugin_manager._config  # pylint: disable=protected-access

        # First, add all registered (enabled) plugins from the registry
        registered_names = set()
        for plugin_ref in registry.get_all_plugins():
            # Get the plugin config from the plugin reference
            plugin_config = plugin_ref.plugin.config if hasattr(plugin_ref, "plugin") else plugin_ref._plugin.config if hasattr(plugin_ref, "_plugin") else None  # pylint: disable=protected-access

            plugin_dict = {
                "name": plugin_ref.name,
                "description": plugin_config.description if plugin_config and plugin_config.description else "",
                "author": plugin_config.author if plugin_config and plugin_config.author else "Unknown",
                "version": plugin_config.version if plugin_config and plugin_config.version else "0.0.0",
                "mode": plugin_ref.mode if isinstance(plugin_ref.mode, str) else plugin_ref.mode.value if plugin_ref.mode else "disabled",
                "priority": plugin_ref.priority,
                "hooks": [hook if isinstance(hook, str) else hook.value for hook in plugin_ref.hooks] if plugin_ref.hooks else [],
                "tags": plugin_ref.tags or [],
                "kind": plugin_config.kind if plugin_config and plugin_config.kind else "",
                "namespace": plugin_config.namespace if plugin_config and plugin_config.namespace else "",
                "status": "enabled" if plugin_ref.mode != PluginMode.DISABLED else "disabled",
            }

            # Add implementation type if available (e.g., Rust vs Python for PII filter)
            plugin_instance = plugin_ref.plugin if hasattr(plugin_ref, "plugin") else plugin_ref._plugin if hasattr(plugin_ref, "_plugin") else None  # pylint: disable=protected-access
            if plugin_instance and hasattr(plugin_instance, "implementation"):
                plugin_dict["implementation"] = plugin_instance.implementation

            # Add config summary (first few keys only for list view)
            if plugin_config and hasattr(plugin_config, "config") and plugin_config.config:
                config_keys = list(plugin_config.config.keys())[:5]
                plugin_dict["config_summary"] = {k: plugin_config.config[k] for k in config_keys}
            else:
                plugin_dict["config_summary"] = {}

            plugins.append(plugin_dict)
            registered_names.add(plugin_ref.name)

        # Then, add disabled plugins from the configuration (not in registry)
        if config and config.plugins:
            for plugin_config in config.plugins:
                if plugin_config.mode == PluginMode.DISABLED and plugin_config.name not in registered_names:
                    plugin_dict = {
                        "name": plugin_config.name,
                        "description": plugin_config.description or "",
                        "author": plugin_config.author or "Unknown",
                        "version": plugin_config.version or "0.0.0",
                        "mode": plugin_config.mode if isinstance(plugin_config.mode, str) else plugin_config.mode.value,
                        "priority": plugin_config.priority or 100,
                        "hooks": [hook if isinstance(hook, str) else hook.value for hook in plugin_config.hooks] if plugin_config.hooks else [],
                        "tags": plugin_config.tags or [],
                        "kind": plugin_config.kind or "",
                        "namespace": plugin_config.namespace or "",
                        "status": "disabled",
                        "config_summary": {},
                    }

                    # Add config summary (first few keys only for list view)
                    if hasattr(plugin_config, "config") and plugin_config.config:
                        config_keys = list(plugin_config.config.keys())[:5]
                        plugin_dict["config_summary"] = {k: plugin_config.config[k] for k in config_keys}

                    plugins.append(plugin_dict)

        return sorted(plugins, key=lambda x: x["priority"])

    def get_plugin_by_name(self, name: str) -> Optional[Dict[str, Any]]:
        """Get detailed information about a specific plugin.

        Args:
            name: The name of the plugin to retrieve.

        Returns:
            Plugin dictionary with full configuration or None if not found.
        """
        if not self._plugin_manager:
            return None

        registry = self._plugin_manager._registry  # pylint: disable=protected-access
        plugin_ref = registry.get_plugin(name)

        if plugin_ref:
            # Get the plugin config from the plugin reference
            plugin_config = plugin_ref.plugin.config if hasattr(plugin_ref, "plugin") else plugin_ref._plugin.config if hasattr(plugin_ref, "_plugin") else None  # pylint: disable=protected-access

            plugin_dict = {
                "name": plugin_ref.name,
                "description": plugin_config.description if plugin_config and plugin_config.description else "",
                "author": plugin_config.author if plugin_config and plugin_config.author else "Unknown",
                "version": plugin_config.version if plugin_config and plugin_config.version else "0.0.0",
                "mode": plugin_ref.mode if isinstance(plugin_ref.mode, str) else plugin_ref.mode.value if plugin_ref.mode else "disabled",
                "priority": plugin_ref.priority,
                "hooks": [hook if isinstance(hook, str) else hook.value for hook in plugin_ref.hooks] if plugin_ref.hooks else [],
                "tags": plugin_ref.tags or [],
                "kind": plugin_config.kind if plugin_config and plugin_config.kind else "",
                "namespace": plugin_config.namespace if plugin_config and plugin_config.namespace else "",
                "status": "enabled" if plugin_ref.mode != PluginMode.DISABLED else "disabled",
                "conditions": plugin_ref.conditions or [],
                "config": plugin_config.config if plugin_config and hasattr(plugin_config, "config") else {},
            }

            # Add manifest info if available
            if hasattr(plugin_ref, "manifest"):
                plugin_dict["manifest"] = {"available_hooks": plugin_ref.manifest.available_hooks, "default_config": plugin_ref.manifest.default_config}

            return plugin_dict

        # Fallback: check config for disabled plugins not in registry
        config = self._plugin_manager._config  # pylint: disable=protected-access
        if config and config.plugins:
            for plugin_config in config.plugins:
                if plugin_config.name == name:
                    return {
                        "name": plugin_config.name,
                        "description": plugin_config.description or "",
                        "author": plugin_config.author or "Unknown",
                        "version": plugin_config.version or "0.0.0",
                        "mode": plugin_config.mode if isinstance(plugin_config.mode, str) else plugin_config.mode.value,
                        "priority": plugin_config.priority or 100,
                        "hooks": [hook if isinstance(hook, str) else hook.value for hook in plugin_config.hooks] if plugin_config.hooks else [],
                        "tags": plugin_config.tags or [],
                        "kind": plugin_config.kind or "",
                        "namespace": plugin_config.namespace or "",
                        "status": "disabled",
                        "conditions": plugin_config.conditions or [],
                        "config": plugin_config.config if hasattr(plugin_config, "config") else {},
                    }

        return None

    async def get_plugin_statistics(self) -> Dict[str, Any]:
        """Get statistics about all plugins.

        Returns:
            Dictionary containing plugin statistics by various dimensions.
        """
        if not self._plugin_manager:
            return {
                "total_plugins": 0,
                "enabled_plugins": 0,
                "disabled_plugins": 0,
                "plugins_by_hook": {},
                "plugins_by_mode": {},
                "plugins_by_tag": {},
                "plugins_by_author": {},
            }

        # Check cache first
        cache = _get_admin_stats_cache()
        cached = await cache.get_plugin_stats()
        if cached is not None:
            return cached

        all_plugins = self.get_all_plugins()

        # Count by status
        enabled_count = sum(1 for p in all_plugins if p["status"] == "enabled")
        disabled_count = sum(1 for p in all_plugins if p["status"] == "disabled")

        # Count by hook
        hooks_count = defaultdict(int)
        for plugin in all_plugins:
            for hook in plugin["hooks"]:
                hooks_count[hook] += 1

        # Count by mode
        mode_count = defaultdict(int)
        for plugin in all_plugins:
            mode_count[plugin["mode"]] += 1

        # Count by tag
        tag_count = defaultdict(int)
        for plugin in all_plugins:
            for tag in plugin["tags"]:
                tag_count[tag] += 1

        # Count by author
        author_count = defaultdict(int)
        for plugin in all_plugins:
            author = plugin.get("author", "Unknown")
            author_count[author] += 1

        stats = {
            "total_plugins": len(all_plugins),
            "enabled_plugins": enabled_count,
            "disabled_plugins": disabled_count,
            "plugins_by_hook": dict(hooks_count),
            "plugins_by_mode": dict(mode_count),
            "plugins_by_tag": dict(sorted(tag_count.items(), key=lambda x: x[1], reverse=True)[:10]),  # Top 10 tags
            "plugins_by_author": dict(sorted(author_count.items(), key=lambda x: x[1], reverse=True)),  # All authors sorted by count
        }

        # Store in cache
        await cache.set_plugin_stats(stats)

        return stats

    def search_plugins(self, query: Optional[str] = None, mode: Optional[str] = None, hook: Optional[str] = None, tag: Optional[str] = None) -> List[Dict[str, Any]]:
        """Search and filter plugins based on criteria.

        Args:
            query: Text search in name, description, author.
            mode: Filter by mode (enforce, permissive, disabled).
            hook: Filter by hook type.
            tag: Filter by tag.

        Returns:
            Filtered list of plugins.
        """
        plugins = self.get_all_plugins()

        # Text search
        if query:
            query_lower = query.lower()
            plugins = [p for p in plugins if query_lower in p["name"].lower() or query_lower in p["description"].lower() or query_lower in p["author"].lower()]

        # Mode filter
        if mode:
            plugins = [p for p in plugins if p["mode"] == mode]

        # Hook filter
        if hook:
            plugins = [p for p in plugins if hook in p["hooks"]]

        # Tag filter
        if tag:
            plugins = [p for p in plugins if tag in p["tags"]]

        return plugins


# Singleton instance
_plugin_service = PluginService()


def get_plugin_service() -> PluginService:
    """Get the singleton plugin service instance.

    Returns:
        The global PluginService instance.
    """
    return _plugin_service
