"""Plugin Manager for Dangerous Pi.

Provides a plugin framework for extending functionality.
Supports dynamic loading, enabling/disabling, and lifecycle management.
"""
import asyncio
import importlib.util
import inspect
import json
from dataclasses import dataclass, asdict
from enum import Enum
from pathlib import Path
from typing import Optional, Dict, Any, List, Callable
import sys

from .. import config


class PluginStatus(str, Enum):
    """Plugin status enum."""
    LOADED = "loaded"
    ENABLED = "enabled"
    DISABLED = "disabled"
    ERROR = "error"


@dataclass
class PluginMetadata:
    """Plugin metadata information."""
    id: str
    name: str
    version: str
    description: str
    author: str
    homepage: Optional[str] = None
    dependencies: List[str] = None
    permissions: List[str] = None

    def __post_init__(self):
        if self.dependencies is None:
            self.dependencies = []
        if self.permissions is None:
            self.permissions = []


@dataclass
class PluginInfo:
    """Plugin runtime information."""
    metadata: PluginMetadata
    status: PluginStatus
    path: Path
    error_message: Optional[str] = None
    enabled: bool = False


class PluginBase:
    """Base class for all plugins.

    Plugins should inherit from this class and implement the required methods.
    """

    def __init__(self):
        """Initialize the plugin."""
        self.metadata: Optional[PluginMetadata] = None
        self.hooks: Dict[str, List[Callable]] = {}

    async def on_load(self):
        """Called when the plugin is loaded.

        Override this to perform initialization tasks.
        """
        pass

    async def on_enable(self):
        """Called when the plugin is enabled.

        Override this to start plugin functionality.
        """
        pass

    async def on_disable(self):
        """Called when the plugin is disabled.

        Override this to stop plugin functionality.
        """
        pass

    async def on_unload(self):
        """Called when the plugin is unloaded.

        Override this to perform cleanup tasks.
        """
        pass

    def register_hook(self, hook_name: str, callback: Callable):
        """Register a hook callback.

        Args:
            hook_name: Name of the hook (e.g., "pm3_command", "update_check")
            callback: Async callback function
        """
        if hook_name not in self.hooks:
            self.hooks[hook_name] = []
        self.hooks[hook_name].append(callback)

    def get_metadata(self) -> PluginMetadata:
        """Get plugin metadata.

        Returns:
            PluginMetadata object

        Raises:
            NotImplementedError if not implemented by plugin
        """
        raise NotImplementedError("Plugin must implement get_metadata()")


class PluginManager:
    """Manages plugin loading, enabling, and lifecycle."""

    def __init__(self):
        """Initialize the plugin manager."""
        self._plugins: Dict[str, PluginInfo] = {}
        self._plugin_instances: Dict[str, PluginBase] = {}
        self._plugin_dir = config.BASE_DIR / "app" / "plugins"
        self._hooks: Dict[str, List[Callable]] = {}
        self._enabled_plugins: List[str] = []

        # Create plugins directory if it doesn't exist
        self._plugin_dir.mkdir(parents=True, exist_ok=True)

    async def discover_plugins(self) -> List[str]:
        """Discover all available plugins in the plugins directory.

        Returns:
            List of discovered plugin IDs
        """
        discovered = []

        # Look for plugin directories
        for plugin_path in self._plugin_dir.iterdir():
            if not plugin_path.is_dir() or plugin_path.name.startswith("_"):
                continue

            # Check for plugin.json metadata file
            metadata_file = plugin_path / "plugin.json"
            if not metadata_file.exists():
                continue

            try:
                # Load metadata
                with open(metadata_file, 'r') as f:
                    metadata_dict = json.load(f)
                    metadata = PluginMetadata(**metadata_dict)

                # Check for main.py
                main_file = plugin_path / "main.py"
                if not main_file.exists():
                    print(f"Plugin {metadata.id} missing main.py")
                    continue

                # Create plugin info
                plugin_info = PluginInfo(
                    metadata=metadata,
                    status=PluginStatus.LOADED,
                    path=plugin_path,
                    enabled=False
                )

                self._plugins[metadata.id] = plugin_info
                discovered.append(metadata.id)
                print(f"Discovered plugin: {metadata.name} v{metadata.version}")

            except Exception as e:
                print(f"Error discovering plugin in {plugin_path}: {e}")
                continue

        return discovered

    async def load_plugin(self, plugin_id: str) -> bool:
        """Load a plugin into memory.

        Args:
            plugin_id: ID of the plugin to load

        Returns:
            True if loaded successfully, False otherwise
        """
        if plugin_id not in self._plugins:
            print(f"Plugin {plugin_id} not found")
            return False

        plugin_info = self._plugins[plugin_id]

        try:
            # Import the plugin module
            main_file = plugin_info.path / "main.py"
            spec = importlib.util.spec_from_file_location(
                f"plugins.{plugin_id}",
                main_file
            )
            module = importlib.util.module_from_spec(spec)
            sys.modules[f"plugins.{plugin_id}"] = module
            spec.loader.exec_module(module)

            # Find the plugin class (should inherit from PluginBase)
            plugin_class = None
            for name, obj in inspect.getmembers(module, inspect.isclass):
                if issubclass(obj, PluginBase) and obj is not PluginBase:
                    plugin_class = obj
                    break

            if not plugin_class:
                raise ValueError("No plugin class found in main.py")

            # Instantiate the plugin
            plugin_instance = plugin_class()
            plugin_instance.metadata = plugin_info.metadata

            # Call on_load lifecycle method
            await plugin_instance.on_load()

            # Store the instance
            self._plugin_instances[plugin_id] = plugin_instance
            plugin_info.status = PluginStatus.LOADED

            print(f"Loaded plugin: {plugin_info.metadata.name}")
            return True

        except Exception as e:
            print(f"Error loading plugin {plugin_id}: {e}")
            plugin_info.status = PluginStatus.ERROR
            plugin_info.error_message = str(e)
            return False

    async def enable_plugin(self, plugin_id: str) -> bool:
        """Enable a loaded plugin.

        Args:
            plugin_id: ID of the plugin to enable

        Returns:
            True if enabled successfully, False otherwise
        """
        if plugin_id not in self._plugin_instances:
            # Try to load it first
            if not await self.load_plugin(plugin_id):
                return False

        plugin_instance = self._plugin_instances[plugin_id]
        plugin_info = self._plugins[plugin_id]

        try:
            # Call on_enable lifecycle method
            await plugin_instance.on_enable()

            # Register plugin hooks
            for hook_name, callbacks in plugin_instance.hooks.items():
                if hook_name not in self._hooks:
                    self._hooks[hook_name] = []
                self._hooks[hook_name].extend(callbacks)

            plugin_info.status = PluginStatus.ENABLED
            plugin_info.enabled = True

            if plugin_id not in self._enabled_plugins:
                self._enabled_plugins.append(plugin_id)

            print(f"Enabled plugin: {plugin_info.metadata.name}")
            return True

        except Exception as e:
            print(f"Error enabling plugin {plugin_id}: {e}")
            plugin_info.status = PluginStatus.ERROR
            plugin_info.error_message = str(e)
            return False

    async def disable_plugin(self, plugin_id: str) -> bool:
        """Disable an enabled plugin.

        Args:
            plugin_id: ID of the plugin to disable

        Returns:
            True if disabled successfully, False otherwise
        """
        if plugin_id not in self._plugin_instances:
            return False

        plugin_instance = self._plugin_instances[plugin_id]
        plugin_info = self._plugins[plugin_id]

        try:
            # Call on_disable lifecycle method
            await plugin_instance.on_disable()

            # Unregister plugin hooks
            for hook_name, callbacks in plugin_instance.hooks.items():
                if hook_name in self._hooks:
                    for callback in callbacks:
                        if callback in self._hooks[hook_name]:
                            self._hooks[hook_name].remove(callback)

            plugin_info.status = PluginStatus.DISABLED
            plugin_info.enabled = False

            if plugin_id in self._enabled_plugins:
                self._enabled_plugins.remove(plugin_id)

            print(f"Disabled plugin: {plugin_info.metadata.name}")
            return True

        except Exception as e:
            print(f"Error disabling plugin {plugin_id}: {e}")
            plugin_info.error_message = str(e)
            return False

    async def unload_plugin(self, plugin_id: str) -> bool:
        """Unload a plugin from memory.

        Args:
            plugin_id: ID of the plugin to unload

        Returns:
            True if unloaded successfully, False otherwise
        """
        if plugin_id not in self._plugin_instances:
            return False

        # Disable first if enabled
        if self._plugins[plugin_id].enabled:
            await self.disable_plugin(plugin_id)

        plugin_instance = self._plugin_instances[plugin_id]

        try:
            # Call on_unload lifecycle method
            await plugin_instance.on_unload()

            # Remove from instances
            del self._plugin_instances[plugin_id]

            # Remove from sys.modules
            module_name = f"plugins.{plugin_id}"
            if module_name in sys.modules:
                del sys.modules[module_name]

            print(f"Unloaded plugin: {plugin_id}")
            return True

        except Exception as e:
            print(f"Error unloading plugin {plugin_id}: {e}")
            return False

    async def trigger_hook(self, hook_name: str, *args, **kwargs) -> List[Any]:
        """Trigger a hook and collect results from all registered callbacks.

        Args:
            hook_name: Name of the hook to trigger
            *args: Positional arguments for hook callbacks
            **kwargs: Keyword arguments for hook callbacks

        Returns:
            List of results from all callbacks
        """
        if hook_name not in self._hooks:
            return []

        results = []
        for callback in self._hooks[hook_name]:
            try:
                if asyncio.iscoroutinefunction(callback):
                    result = await callback(*args, **kwargs)
                else:
                    result = callback(*args, **kwargs)
                results.append(result)
            except Exception as e:
                print(f"Error in hook {hook_name}: {e}")

        return results

    def get_plugin_info(self, plugin_id: str) -> Optional[PluginInfo]:
        """Get information about a plugin.

        Args:
            plugin_id: ID of the plugin

        Returns:
            PluginInfo object or None if not found
        """
        return self._plugins.get(plugin_id)

    def get_all_plugins(self) -> Dict[str, PluginInfo]:
        """Get information about all plugins.

        Returns:
            Dictionary of plugin ID to PluginInfo
        """
        return self._plugins.copy()

    def get_enabled_plugins(self) -> List[str]:
        """Get list of enabled plugin IDs.

        Returns:
            List of enabled plugin IDs
        """
        return self._enabled_plugins.copy()


# Global plugin manager instance
_plugin_manager: Optional[PluginManager] = None


def get_plugin_manager() -> PluginManager:
    """Get the global plugin manager instance."""
    global _plugin_manager
    if _plugin_manager is None:
        _plugin_manager = PluginManager()
    return _plugin_manager
