"""
Plugin manager for discovering, loading, and managing plugins.

This module provides the main interface for plugin management in MarkiTect.
"""

import importlib
import importlib.util
import sys
from pathlib import Path
from typing import Dict, List, Optional, Any, Type
import yaml
import json

from .base import BasePlugin, PluginType, get_plugin_class_from_module
from .registry import plugin_registry


class PluginManager:
    """Main plugin manager for MarkiTect."""

    def __init__(self, config_path: Optional[str] = None):
        """
        Initialize plugin manager.

        Args:
            config_path: Optional path to plugin configuration file
        """
        self.config = self._load_config(config_path)
        self.plugin_directories = self._get_plugin_directories()
        self._discovered_plugins: Dict[str, Dict[str, Any]] = {}

    def discover_plugins(self, refresh: bool = False) -> Dict[str, Dict[str, Any]]:
        """
        Discover all available plugins.

        Args:
            refresh: Force refresh of plugin discovery

        Returns:
            Dictionary of discovered plugins with metadata
        """
        if self._discovered_plugins and not refresh:
            return self._discovered_plugins

        self._discovered_plugins = {}

        # Discover built-in plugins
        self._discover_builtin_plugins()

        # Discover plugins in configured directories
        for directory in self.plugin_directories:
            self._discover_plugins_in_directory(directory)

        # Discover plugins from installed packages
        self._discover_installed_plugins()

        return self._discovered_plugins

    def load_plugin(self, name: str, config: Dict[str, Any] = None) -> Optional[BasePlugin]:
        """
        Load a specific plugin by name.

        Args:
            name: Plugin name
            config: Optional plugin configuration

        Returns:
            Loaded plugin instance or None if failed
        """
        try:
            # Check if already loaded in registry
            if plugin_registry.is_loaded(name):
                return plugin_registry.get_plugin(name, config)

            # Check if plugin is already registered but not loaded
            if name in plugin_registry._plugins:
                return plugin_registry.get_plugin(name, config)

            # Try to discover and load
            discovered = self.discover_plugins()
            if name not in discovered:
                return None
        except Exception:
            # Add debugging info but don't break - continue to discovery logic
            pass

        plugin_info = discovered[name]
        try:
            # Load plugin module
            if 'module_path' in plugin_info:
                module = self._load_module_from_path(plugin_info['module_path'])
            elif 'module_name' in plugin_info:
                module = importlib.import_module(plugin_info['module_name'])
            else:
                return None

            # Find plugin class
            plugin_classes = get_plugin_class_from_module(module)
            if not plugin_classes:
                return None

            # Use first plugin class found (or specific one if specified)
            plugin_class = plugin_classes[0]
            if 'class_name' in plugin_info:
                for cls in plugin_classes:
                    if cls.__name__ == plugin_info['class_name']:
                        plugin_class = cls
                        break

            # Register and load
            plugin_registry.register(plugin_class, name)
            return plugin_registry.get_plugin(name, config)

        except Exception:
            return None

    def load_enabled_plugins(self) -> Dict[str, BasePlugin]:
        """
        Load all plugins marked as enabled in configuration.

        Returns:
            Dictionary of loaded plugins
        """
        enabled_plugins = {}
        plugin_configs = self.config.get('plugins', {})

        for plugin_name, plugin_config in plugin_configs.items():
            if plugin_config.get('enabled', False):
                plugin = self.load_plugin(plugin_name, plugin_config.get('config', {}))
                if plugin:
                    enabled_plugins[plugin_name] = plugin

        return enabled_plugins

    def get_plugins_by_type(self, plugin_type: PluginType) -> List[BasePlugin]:
        """
        Get all loaded plugins of a specific type.

        Args:
            plugin_type: Type of plugins to retrieve

        Returns:
            List of loaded plugin instances
        """
        plugin_names = plugin_registry.get_plugins_by_type(plugin_type)
        plugins = []

        for name in plugin_names:
            plugin = plugin_registry.get_plugin(name)
            if plugin:
                plugins.append(plugin)

        return plugins

    def unload_plugin(self, name: str) -> bool:
        """
        Unload a plugin.

        Args:
            name: Plugin name to unload

        Returns:
            True if unloaded successfully
        """
        return plugin_registry.unregister(name)

    def reload_plugin(self, name: str, config: Dict[str, Any] = None) -> Optional[BasePlugin]:
        """
        Reload a plugin with new configuration.

        Args:
            name: Plugin name
            config: New configuration

        Returns:
            Reloaded plugin instance or None if failed
        """
        if plugin_registry.reload_plugin(name, config):
            return plugin_registry.get_plugin(name)
        return None

    def list_plugins(self, plugin_type: Optional[PluginType] = None) -> Dict[str, Dict[str, Any]]:
        """
        List all available plugins.

        Args:
            plugin_type: Optional type filter

        Returns:
            Dictionary of plugins with metadata
        """
        all_plugins = plugin_registry.list_plugins()

        if plugin_type:
            filtered_plugins = {}
            for name, info in all_plugins.items():
                if info.get('type') == plugin_type.value:
                    filtered_plugins[name] = info
            return filtered_plugins

        return all_plugins

    def validate_plugin_dependencies(self, plugin_name: str) -> List[str]:
        """
        Validate plugin dependencies.

        Args:
            plugin_name: Plugin to validate

        Returns:
            List of missing dependencies
        """
        # This is a simplified implementation
        # In a full implementation, you'd check MarkiTect version,
        # required packages, etc.
        return []

    def _load_config(self, config_path: Optional[str] = None) -> Dict[str, Any]:
        """Load plugin configuration."""
        if config_path is None:
            # Try multiple default locations
            possible_paths = [
                Path('.markitect/plugins.yml'),
                Path('.markitect/plugins.yaml'),
                Path('.markitect/plugins.json'),
                Path('markitect_plugins.yml'),
                Path('markitect_plugins.yaml'),
                Path('markitect_plugins.json')
            ]
        else:
            possible_paths = [Path(config_path)]

        for path in possible_paths:
            if path.exists():
                try:
                    with open(path, 'r') as f:
                        if path.suffix.lower() in ['.yml', '.yaml']:
                            return yaml.safe_load(f) or {}
                        elif path.suffix.lower() == '.json':
                            return json.load(f)
                except Exception:
                    continue

        # Return default configuration
        return {
            'plugin_directories': [
                'plugins',
                '.markitect/plugins',
                str(Path.home() / '.markitect' / 'plugins')
            ],
            'auto_discover': True,
            'auto_load_enabled': True,
            'plugins': {}
        }

    def _get_plugin_directories(self) -> List[Path]:
        """Get list of directories to search for plugins."""
        directories = []
        for dir_path in self.config.get('plugin_directories', []):
            path = Path(dir_path)
            if path.exists() and path.is_dir():
                directories.append(path)
        return directories

    def _discover_builtin_plugins(self) -> None:
        """Discover built-in plugins in the markitect package."""
        try:
            # Import built-in plugin modules
            builtin_modules = [
                'markitect.plugins.builtin.processors',
                'markitect.plugins.builtin.formatters',
                'markitect.plugins.builtin.validators',
                'markitect.plugins.builtin.exporters'
            ]

            for module_name in builtin_modules:
                try:
                    # Import the module, which will trigger @register_plugin decorators
                    module = importlib.import_module(module_name)
                    self._register_plugins_from_module(module, f"builtin.{module_name.split('.')[-1]}")
                except ImportError:
                    continue
        except Exception:
            pass

    def _discover_plugins_in_directory(self, directory: Path) -> None:
        """Discover plugins in a specific directory."""
        for plugin_path in directory.glob('*.py'):
            if plugin_path.name.startswith('__'):
                continue

            try:
                module = self._load_module_from_path(plugin_path)
                self._register_plugins_from_module(module, plugin_path.stem)
            except Exception:
                continue

    def _discover_installed_plugins(self) -> None:
        """Discover plugins from installed Python packages."""
        # This would use entry points to discover plugins
        # For now, this is a placeholder
        pass

    def _load_module_from_path(self, module_path: Path):
        """Load a Python module from file path."""
        spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
        if spec and spec.loader:
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            return module
        return None

    def _register_plugins_from_module(self, module, module_name: str) -> None:
        """Register all plugins found in a module."""
        plugin_classes = get_plugin_class_from_module(module)

        for plugin_class in plugin_classes:
            # For built-in plugins, importing the module will auto-register them
            # via the @register_plugin decorator, so we just need to track them
            plugin_name = f"{module_name}.{plugin_class.__name__}"
            self._discovered_plugins[plugin_name] = {
                'class_name': plugin_class.__name__,
                'module_name': module.__name__ if hasattr(module, '__name__') else module_name,
                'discovered_from': 'module'
            }

            # Also add by registered name if it exists in registry
            # This happens after the @register_plugin decorator runs
            try:
                instance = plugin_class()
                registered_name = instance.metadata.name
                if registered_name and registered_name in plugin_registry._plugins:
                    # Add an alias for the registered name
                    self._discovered_plugins[registered_name] = {
                        'class_name': plugin_class.__name__,
                        'module_name': module.__name__ if hasattr(module, '__name__') else module_name,
                        'discovered_from': 'module',
                        'alias_for': plugin_name
                    }
            except Exception:
                pass