"""Plugin discovery, loading, and lifecycle management system"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/plugin_manager.ipynb.

# %% auto 0
__all__ = ['PluginManager', 'get_plugin_config_schema', 'get_plugin_config', 'update_plugin_config', 'validate_plugin_config',
           'get_all_plugin_schemas', 'reload_plugin']

# %% ../nbs/plugin_manager.ipynb 3
import importlib.metadata
import importlib.util
import inspect
import logging
from pathlib import Path
import sys
from typing import Dict, List, Optional, Type, Any, Tuple
from .core import AudioData, TranscriptionResult
from .plugin_interface import PluginInterface, PluginMeta

# %% ../nbs/plugin_manager.ipynb 5
class PluginManager:
    """Manages plugin discovery, loading, and lifecycle."""
    
    def __init__(self, 
                 plugin_interface: Type[PluginInterface] = PluginInterface,  # The base class/interface plugins must implement
                 entry_point_group: str = "transcription.plugins"  # The entry point group name for plugin discovery
                ):
        """
        Initialize the plugin manager.
        """
        self.plugin_interface = plugin_interface
        self.entry_point_group = entry_point_group
        self.plugins: Dict[str, PluginMeta] = {}
        self._loaded_modules: Dict[str, Any] = {}
        self.logger = logging.getLogger(f"{__name__}.{type(self).__name__}")
    
    def discover_plugins(
        self
    ) -> List[PluginMeta]:  # List of discovered plugin metadata objects
        """
        Discover all installed plugins via entry points.
        
        This method looks for plugins installed as packages that declare
        entry points in the specified group.
        """
        discovered = []
        
        try:
            # For Python 3.10+
            entry_points = importlib.metadata.entry_points()
            
            # Get entry points for our group
            if hasattr(entry_points, 'select'):
                # Python 3.10+ method
                plugin_eps = entry_points.select(group=self.entry_point_group)
            else:
                # Fallback for older approach
                plugin_eps = entry_points.get(self.entry_point_group, [])
        except Exception as e:
            self.logger.error(f"Error accessing entry points: {e}")
            return discovered
        
        for ep in plugin_eps:
            try:
                # Get the entry point name (plugin name)
                plugin_name = ep.name
                
                # Get package metadata
                # In Python 3.11+, ep.dist gives us the distribution
                if hasattr(ep, 'dist') and ep.dist:
                    # Get package name and version from the distribution
                    package_name = ep.dist.name  # This is the package name
                    version = ep.dist.version
                else:
                    # Fallback: try to get distribution info from entry point value
                    # The value is like "package.module:ClassName"
                    package_name = plugin_name  # Use plugin name as fallback
                    version = "unknown"
                    
                    # Try to get the actual package info
                    try:
                        # Extract package name from the entry point value
                        ep_value = str(ep.value) if hasattr(ep, 'value') else str(ep)
                        package_part = ep_value.split(':')[0].split('.')[0]
                        
                        # Try to get distribution info
                        dist = importlib.metadata.distribution(package_part)
                        package_name = dist.name
                        version = dist.version
                    except Exception:
                        pass
                
                meta = PluginMeta(
                    name=plugin_name,
                    version=version,
                    package_name=package_name
                )
                discovered.append(meta)
                self.logger.info(f"Discovered plugin: {meta.name} v{meta.version} from package {meta.package_name}")
                
            except Exception as e:
                self.logger.error(f"Error discovering plugin {ep.name}: {e}")
        
        return discovered
    
    def load_plugin(
        self,
        plugin_meta: PluginMeta,  # The plugin metadata
        config: Optional[Dict[str, Any]] = None  # Optional configuration for the plugin
    ) -> bool:  # True if successfully loaded, False otherwise
        """
        Load and initialize a plugin.
        """
        try:
            # Find the entry point and load it
            entry_points = importlib.metadata.entry_points()
            if hasattr(entry_points, 'select'):
                plugin_eps = entry_points.select(group=self.entry_point_group, name=plugin_meta.name)
            else:
                plugin_eps = [ep for ep in entry_points.get(self.entry_point_group, []) 
                             if ep.name == plugin_meta.name]
            
            if not plugin_eps:
                self.logger.error(f"Plugin {plugin_meta.name} not found in entry points")
                return False
            
            ep = list(plugin_eps)[0]
            plugin_class = ep.load()
            
            # Verify it implements the required interface
            if not issubclass(plugin_class, self.plugin_interface):
                self.logger.error(f"Plugin {plugin_meta.name} does not implement required interface")
                return False
            
            # Instantiate and initialize the plugin
            plugin_instance = plugin_class()
            plugin_instance.initialize(config)
            
            # Store the plugin
            plugin_meta.instance = plugin_instance
            self.plugins[plugin_meta.name] = plugin_meta
            
            self.logger.info(f"Loaded plugin: {plugin_meta.name}")
            return True
            
        except Exception as e:
            self.logger.error(f"Error loading plugin {plugin_meta.name}: {e}")
            return False
    
    def load_plugin_from_module(
        self,
        module_path: str,  # Path to the Python module
        config: Optional[Dict[str, Any]] = None  # Optional configuration for the plugin
    ) -> bool:  # True if successfully loaded, False otherwise
        """
        Load a plugin directly from a Python module file or package.
        Useful for development or local plugins.
        """
        try:
            # Convert to Path object
            path = Path(module_path)
            
            # Load the module
            if path.is_file():
                # Single file module
                spec = importlib.util.spec_from_file_location(path.stem, path)
                module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(module)
            else:
                # Package directory
                sys.path.insert(0, str(path.parent))
                module = importlib.import_module(path.name)
            
            # Find plugin classes in the module
            plugin_classes = []
            for name, obj in inspect.getmembers(module):
                if (inspect.isclass(obj) and 
                    issubclass(obj, self.plugin_interface) and 
                    obj != self.plugin_interface):
                    plugin_classes.append(obj)
            
            if not plugin_classes:
                self.logger.error(f"No plugin classes found in {module_path}")
                return False
            
            # Load the first plugin class found
            plugin_class = plugin_classes[0]
            plugin_instance = plugin_class()
            plugin_instance.initialize(config)
            
            # Create metadata
            meta = PluginMeta(
                name=plugin_instance.name,
                version=plugin_instance.version,
                package_name=str(module_path)
            )
            meta.instance = plugin_instance
            
            self.plugins[meta.name] = meta
            self._loaded_modules[meta.name] = module
            
            self.logger.info(f"Loaded plugin from module: {meta.name}")
            return True
            
        except Exception as e:
            self.logger.error(f"Error loading plugin from {module_path}: {e}")
            return False
    
    def unload_plugin(
        self,
        plugin_name: str  # Name of the plugin to unload
    ) -> bool:  # True if successfully unloaded, False otherwise
        """
        Unload a plugin and call its cleanup method.
        """
        if plugin_name not in self.plugins:
            self.logger.error(f"Plugin {plugin_name} not found")
            return False
        
        try:
            plugin_meta = self.plugins[plugin_name]
            if plugin_meta.instance:
                plugin_meta.instance.cleanup()
            
            del self.plugins[plugin_name]
            
            # Remove from loaded modules if it was loaded that way
            if plugin_name in self._loaded_modules:
                del self._loaded_modules[plugin_name]
            
            self.logger.info(f"Unloaded plugin: {plugin_name}")
            return True
            
        except Exception as e:
            self.logger.error(f"Error unloading plugin {plugin_name}: {e}")
            return False
    
    def get_plugin(
        self,
        plugin_name: str  # The name of the plugin to retrieve
    ) -> Optional[PluginInterface]:  # The plugin instance if found, None otherwise
        """Get a loaded plugin instance by name."""
        if plugin_name in self.plugins:
            return self.plugins[plugin_name].instance
        return None
    
    def list_plugins(
        self
    ) -> List[PluginMeta]:  # List of metadata for all loaded plugins
        """List all loaded plugins."""
        return list(self.plugins.values())
    
    def execute_plugin(
        self,
        plugin_name: str,  # Name of the plugin to execute
        *args,  # Arguments to pass to the plugin
        **kwargs  # Key word arguments to pass to the plugin
    ) -> Any:  # The result of the plugin execution
        """
        Execute a plugin's main functionality.            
        """
        plugin = self.get_plugin(plugin_name)
        if not plugin:
            raise ValueError(f"Plugin {plugin_name} not found or not loaded")
        
        if not self.plugins[plugin_name].enabled:
            raise ValueError(f"Plugin {plugin_name} is disabled")
        
        return plugin.execute(*args, **kwargs)
    
    def enable_plugin(
        self,
        plugin_name: str  # The name of the plugin to enable
    ) -> bool:  # True if plugin was enabled, False if not found
        """Enable a plugin."""
        if plugin_name in self.plugins:
            self.plugins[plugin_name].enabled = True
            return True
        return False
    
    def disable_plugin(
        self,
        plugin_name: str  # The name of the plugin to disable
    ) -> bool:  # True if plugin was disabled, False if not found
        """Disable a plugin without unloading it."""
        if plugin_name in self.plugins:
            self.plugins[plugin_name].enabled = False
            return True
        return False

# %% ../nbs/plugin_manager.ipynb 6
# Add these methods to the PluginManager class above

def get_plugin_config_schema(
    self,
    plugin_name: str  # Name of the plugin
) -> Optional[Dict[str, Any]]:  # Configuration schema or None if plugin not found
    """
    Get the configuration schema for a plugin.
    
    Returns the JSON Schema that describes all configuration options
    available for the specified plugin.
    """
    plugin = self.get_plugin(plugin_name)
    if plugin:
        return plugin.get_config_schema()
    return None

def get_plugin_config(
    self,
    plugin_name: str  # Name of the plugin
) -> Optional[Dict[str, Any]]:  # Current configuration or None if plugin not found
    """
    Get the current configuration of a plugin.
    
    Returns the actual configuration values being used by the plugin,
    including any defaults.
    """
    plugin = self.get_plugin(plugin_name)
    if plugin:
        return plugin.get_current_config()
    return None

def update_plugin_config(
    self,
    plugin_name: str,  # Name of the plugin
    config: Dict[str, Any],  # New configuration
    merge: bool = True  # Whether to merge with existing config or replace entirely
) -> bool:  # True if successful, False otherwise
    """
    Update a plugin's configuration and reinitialize it.
    
    Args:
        plugin_name: Name of the plugin to update
        config: New configuration dictionary
        merge: If True, merge with existing config. If False, replace entirely.
    
    Returns:
        True if configuration was successfully updated, False otherwise.
    """
    plugin = self.get_plugin(plugin_name)
    if not plugin:
        self.logger.error(f"Plugin {plugin_name} not found")
        return False
    
    try:
        # Get current config if merging
        if merge:
            current_config = plugin.get_current_config()
            config = {**current_config, **config}
        
        # Validate the new configuration
        is_valid, error = plugin.validate_config(config)
        if not is_valid:
            self.logger.error(f"Invalid configuration for {plugin_name}: {error}")
            return False
        
        # Clean up existing resources
        plugin.cleanup()
        
        # Reinitialize with new config
        plugin.initialize(config)
        
        self.logger.info(f"Updated configuration for plugin: {plugin_name}")
        return True
        
    except Exception as e:
        self.logger.error(f"Error updating plugin {plugin_name} configuration: {e}")
        return False

def validate_plugin_config(
    self,
    plugin_name: str,  # Name of the plugin
    config: Dict[str, Any]  # Configuration to validate
) -> Tuple[bool, Optional[str]]:  # (is_valid, error_message)
    """
    Validate a configuration dictionary for a plugin without applying it.
    
    Returns:
        Tuple of (is_valid, error_message). If valid, error_message is None.
    """
    plugin = self.get_plugin(plugin_name)
    if not plugin:
        return False, f"Plugin {plugin_name} not found"
    
    return plugin.validate_config(config)

def get_all_plugin_schemas(
    self
) -> Dict[str, Dict[str, Any]]:  # Dictionary mapping plugin names to their schemas
    """
    Get configuration schemas for all loaded plugins.
    
    Returns a dictionary where keys are plugin names and values are
    their configuration schemas.
    """
    schemas = {}
    for plugin_name in self.plugins:
        plugin = self.get_plugin(plugin_name)
        if plugin:
            schemas[plugin_name] = plugin.get_config_schema()
    return schemas

def reload_plugin(
    self,
    plugin_name: str,  # Name of the plugin to reload
    config: Optional[Dict[str, Any]] = None  # Optional new configuration
) -> bool:  # True if successful, False otherwise
    """
    Reload a plugin with optional new configuration.
    
    This is useful when you want to completely restart a plugin,
    for example after updating its code during development.
    """
    if plugin_name not in self.plugins:
        self.logger.error(f"Plugin {plugin_name} not found")
        return False
    
    try:
        # Get the plugin metadata
        plugin_meta = self.plugins[plugin_name]
        
        # Get current config if not provided
        if config is None and plugin_meta.instance:
            config = plugin_meta.instance.get_current_config()
        
        # Unload the plugin
        if plugin_meta.instance:
            plugin_meta.instance.cleanup()
        
        # Reload based on how it was originally loaded
        if plugin_name in self._loaded_modules:
            # Was loaded from a module file
            module_path = plugin_meta.package_name
            del self.plugins[plugin_name]
            del self._loaded_modules[plugin_name]
            return self.load_plugin_from_module(module_path, config)
        else:
            # Was loaded from entry points
            plugin_meta.instance = None
            return self.load_plugin(plugin_meta, config)
            
    except Exception as e:
        self.logger.error(f"Error reloading plugin {plugin_name}: {e}")
        return False

# Add to the PluginManager class __init__ to include Tuple import
PluginManager.get_plugin_config_schema = get_plugin_config_schema
PluginManager.get_plugin_config = get_plugin_config
PluginManager.update_plugin_config = update_plugin_config
PluginManager.validate_plugin_config = validate_plugin_config
PluginManager.get_all_plugin_schemas = get_all_plugin_schemas
PluginManager.reload_plugin = reload_plugin
