"""
PluginLoader - Dynamic plugin discovery and loading.

Provides:
- Configuration-driven plugin loading
- Directory-based plugin discovery
- Hot-reloading support
- Plugin validation
"""

import importlib
import importlib.util
import json
import logging
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Optional

from .registry import AdapterRegistry, PluginMetadata, PluginStatus

logger = logging.getLogger(__name__)


@dataclass
class PluginConfig:
    """Configuration for a plugin."""

    name: str
    enabled: bool = True
    factory_module: str = ""  # e.g., "agent_orchestrator.adapters.claude_code_cli"
    factory_function: str = ""  # e.g., "create_claude_code_adapter"
    metadata: dict[str, Any] = field(default_factory=dict)
    default_config: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "PluginConfig":
        """Create from dictionary."""
        return cls(
            name=data["name"],
            enabled=data.get("enabled", True),
            factory_module=data.get("factory_module", ""),
            factory_function=data.get("factory_function", ""),
            metadata=data.get("metadata", {}),
            default_config=data.get("default_config", {}),
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "name": self.name,
            "enabled": self.enabled,
            "factory_module": self.factory_module,
            "factory_function": self.factory_function,
            "metadata": self.metadata,
            "default_config": self.default_config,
        }


class PluginLoader:
    """
    Loads and manages plugins for the adapter registry.

    Supports:
    - Loading from configuration dictionaries
    - Loading from JSON/YAML config files
    - Discovering plugins in directories
    - Hot-reloading plugins

    Example:
        loader = PluginLoader(registry)

        # Load from config
        loader.load_from_config({
            "plugins": [
                {
                    "name": "claude-code",
                    "factory_module": "agent_orchestrator.adapters.claude_code_cli",
                    "factory_function": "create_claude_code_adapter",
                    "metadata": {"capabilities": ["code_edit"]},
                }
            ]
        })

        # Discover plugins in directory
        loader.discover_plugins(Path("./plugins"))
    """

    def __init__(self, registry: AdapterRegistry) -> None:
        """
        Initialize the plugin loader.

        Args:
            registry: The adapter registry to load plugins into
        """
        self.registry = registry
        self._loaded_modules: dict[str, Any] = {}
        self._plugin_configs: dict[str, PluginConfig] = {}

    def load_from_config(self, config: dict[str, Any]) -> list[str]:
        """
        Load plugins from a configuration dictionary.

        Args:
            config: Configuration with "plugins" list

        Returns:
            List of successfully loaded plugin names
        """
        plugins = config.get("plugins", [])
        loaded = []

        for plugin_data in plugins:
            try:
                plugin_config = PluginConfig.from_dict(plugin_data)
                if self._load_plugin(plugin_config):
                    loaded.append(plugin_config.name)
            except Exception as e:
                logger.error(f"Failed to load plugin config: {e}")

        return loaded

    def load_from_file(self, path: Path) -> list[str]:
        """
        Load plugins from a JSON configuration file.

        Args:
            path: Path to the config file

        Returns:
            List of successfully loaded plugin names
        """
        if not path.exists():
            logger.warning(f"Plugin config file not found: {path}")
            return []

        try:
            with open(path) as f:
                config = json.load(f)
            return self.load_from_config(config)
        except Exception as e:
            logger.error(f"Failed to load plugin config from {path}: {e}")
            return []

    def _load_plugin(self, config: PluginConfig) -> bool:
        """
        Load a single plugin from configuration.

        Returns True if loaded successfully.
        """
        if not config.enabled:
            logger.info(f"Plugin {config.name} is disabled, skipping")
            return False

        if not config.factory_module or not config.factory_function:
            logger.error(f"Plugin {config.name} missing factory_module or factory_function")
            return False

        try:
            # Import the module
            module = importlib.import_module(config.factory_module)
            self._loaded_modules[config.name] = module

            # Get the factory function
            factory = getattr(module, config.factory_function)

            # Create metadata
            metadata = PluginMetadata(
                name=config.name,
                version=config.metadata.get("version", "1.0.0"),
                description=config.metadata.get("description", ""),
                author=config.metadata.get("author", ""),
                adapter_type=config.metadata.get("adapter_type", ""),
                capabilities=config.metadata.get("capabilities", []),
            )

            # Register with registry
            self.registry.register_factory(
                name=config.name,
                factory=factory,
                metadata=metadata,
                default_config=config.default_config,
                replace=True,
            )

            self._plugin_configs[config.name] = config
            logger.info(f"Loaded plugin: {config.name}")
            return True

        except ImportError as e:
            logger.error(f"Failed to import module for plugin {config.name}: {e}")
            return False
        except AttributeError as e:
            logger.error(f"Factory function not found for plugin {config.name}: {e}")
            return False
        except Exception as e:
            logger.error(f"Failed to load plugin {config.name}: {e}")
            return False

    def discover_plugins(self, directory: Path) -> list[str]:
        """
        Discover and load plugins from a directory.

        Looks for Python files with a `register_plugin` function or
        a `PLUGIN_CONFIG` dictionary.

        Args:
            directory: Path to plugins directory

        Returns:
            List of discovered plugin names
        """
        if not directory.exists():
            logger.warning(f"Plugin directory not found: {directory}")
            return []

        discovered = []

        for py_file in directory.glob("*.py"):
            if py_file.name.startswith("_"):
                continue

            try:
                name = self._load_plugin_file(py_file)
                if name:
                    discovered.append(name)
            except Exception as e:
                logger.error(f"Failed to load plugin file {py_file}: {e}")

        return discovered

    def _load_plugin_file(self, path: Path) -> Optional[str]:
        """
        Load a plugin from a Python file.

        Returns the plugin name if loaded successfully.
        """
        module_name = f"plugins.{path.stem}"

        # Load the module
        spec = importlib.util.spec_from_file_location(module_name, path)
        if not spec or not spec.loader:
            return None

        module = importlib.util.module_from_spec(spec)
        sys.modules[module_name] = module
        spec.loader.exec_module(module)

        # Check for register_plugin function
        if hasattr(module, "register_plugin"):
            register_fn = getattr(module, "register_plugin")
            name = register_fn(self.registry)
            if name:
                self._loaded_modules[name] = module
                logger.info(f"Discovered plugin via register_plugin: {name}")
                return name

        # Check for PLUGIN_CONFIG dictionary
        if hasattr(module, "PLUGIN_CONFIG"):
            config_dict = getattr(module, "PLUGIN_CONFIG")
            config = PluginConfig.from_dict(config_dict)

            # Find the factory function in the module
            if hasattr(module, config.factory_function):
                factory = getattr(module, config.factory_function)

                metadata = PluginMetadata(
                    name=config.name,
                    version=config.metadata.get("version", "1.0.0"),
                    description=config.metadata.get("description", ""),
                    capabilities=config.metadata.get("capabilities", []),
                )

                self.registry.register_factory(
                    name=config.name,
                    factory=factory,
                    metadata=metadata,
                    default_config=config.default_config,
                )

                self._loaded_modules[config.name] = module
                self._plugin_configs[config.name] = config
                logger.info(f"Discovered plugin via PLUGIN_CONFIG: {config.name}")
                return config.name

        return None

    def reload_plugin(self, name: str) -> bool:
        """
        Reload a plugin by name.

        Returns True if reloaded successfully.
        """
        if name not in self._plugin_configs:
            logger.warning(f"Cannot reload unknown plugin: {name}")
            return False

        config = self._plugin_configs[name]

        # Unregister existing
        self.registry.unregister_factory(name)

        # Reload module
        if name in self._loaded_modules:
            try:
                importlib.reload(self._loaded_modules[name])
            except Exception as e:
                logger.error(f"Failed to reload module for plugin {name}: {e}")
                return False

        # Re-load plugin
        return self._load_plugin(config)

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

        Returns True if unloaded successfully.
        """
        if name not in self._loaded_modules:
            return False

        self.registry.unregister_factory(name)

        if name in self._loaded_modules:
            del self._loaded_modules[name]
        if name in self._plugin_configs:
            del self._plugin_configs[name]

        logger.info(f"Unloaded plugin: {name}")
        return True

    def get_loaded_plugins(self) -> list[str]:
        """Get list of loaded plugin names."""
        return list(self._plugin_configs.keys())

    def get_plugin_config(self, name: str) -> Optional[PluginConfig]:
        """Get configuration for a loaded plugin."""
        return self._plugin_configs.get(name)

    def create_default_config(self) -> dict[str, Any]:
        """
        Create a default plugin configuration with built-in adapters.

        Returns a configuration dictionary ready for customization.
        """
        return {
            "plugins": [
                {
                    "name": "claude-code",
                    "enabled": True,
                    "factory_module": "agent_orchestrator.adapters.claude_code_cli",
                    "factory_function": "create_claude_code_adapter",
                    "metadata": {
                        "version": "1.0.0",
                        "description": "Claude Code CLI adapter",
                        "adapter_type": "cli",
                        "capabilities": [
                            "code_edit",
                            "file_read",
                            "file_write",
                            "terminal",
                            "git",
                        ],
                    },
                    "default_config": {
                        "timeout_seconds": 300,
                    },
                },
                {
                    "name": "gemini-cli",
                    "enabled": True,
                    "factory_module": "agent_orchestrator.adapters.gemini_cli",
                    "factory_function": "create_gemini_adapter",
                    "metadata": {
                        "version": "1.0.0",
                        "description": "Gemini CLI adapter",
                        "adapter_type": "cli",
                        "capabilities": [
                            "code_edit",
                            "file_read",
                            "search",
                            "large_context",
                        ],
                    },
                    "default_config": {
                        "search_grounding": True,
                        "timeout_seconds": 300,
                    },
                },
                {
                    "name": "codex-cli",
                    "enabled": True,
                    "factory_module": "agent_orchestrator.adapters.codex_cli",
                    "factory_function": "create_codex_adapter",
                    "metadata": {
                        "version": "1.0.0",
                        "description": "OpenAI Codex CLI adapter",
                        "adapter_type": "cli",
                        "capabilities": [
                            "code_edit",
                            "file_read",
                            "autonomous",
                        ],
                    },
                    "default_config": {
                        "autonomy_mode": "auto_edit",
                        "timeout_seconds": 300,
                    },
                },
                {
                    "name": "claude-sdk",
                    "enabled": True,
                    "factory_module": "agent_orchestrator.adapters.claude_sdk",
                    "factory_function": "create_claude_sdk_adapter",
                    "metadata": {
                        "version": "1.0.0",
                        "description": "Claude Agent SDK adapter",
                        "adapter_type": "api",
                        "capabilities": [
                            "code_edit",
                            "fast",
                            "streaming",
                        ],
                    },
                    "default_config": {
                        "model": "claude-sonnet-4-20250514",
                        "max_tokens": 4096,
                    },
                },
                {
                    "name": "openai-agents",
                    "enabled": True,
                    "factory_module": "agent_orchestrator.adapters.openai_agents",
                    "factory_function": "create_openai_agents_adapter",
                    "metadata": {
                        "version": "1.0.0",
                        "description": "OpenAI Agents API adapter",
                        "adapter_type": "api",
                        "capabilities": [
                            "code_edit",
                            "fast",
                            "function_calling",
                        ],
                    },
                    "default_config": {
                        "model": "gpt-4o",
                    },
                },
            ],
        }

    def save_config(self, path: Path) -> None:
        """
        Save current plugin configuration to a file.

        Args:
            path: Path to save the config file
        """
        config = {
            "plugins": [
                cfg.to_dict() for cfg in self._plugin_configs.values()
            ]
        }

        with open(path, "w") as f:
            json.dump(config, f, indent=2)

        logger.info(f"Saved plugin config to {path}")
