"""
Plugin system for automated tasks.

Plugins are Python classes that define automated tasks that can be scheduled
and executed without requiring resource allocation.

Example plugin:
    class SendWeeklyReportPlugin(BasePlugin):
        name = "send_weekly_report"
        display_name = "Send Weekly Report"
        description = "Emails a weekly business report to managers"

        def execute(self, context):
            # Plugin implementation
            return {"success": True, "message": "Report sent"}
"""

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from django.utils import timezone
import logging

logger = logging.getLogger(__name__)


class PluginExecutionError(Exception):
    """Raised when a plugin fails to execute"""
    pass


class BasePlugin(ABC):
    """
    Base class for all scheduler plugins.

    Subclass this to create custom automated tasks.
    """

    # Plugin metadata (override in subclasses)
    name: str = ""  # Unique identifier (snake_case)
    display_name: str = ""  # Human-readable name
    description: str = ""  # What this plugin does
    category: str = "general"  # Plugin category for organization

    # Configuration schema (override if plugin accepts config)
    config_schema: Dict[str, Any] = {}

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """
        Initialize plugin with configuration.

        Args:
            config: Plugin-specific configuration dictionary
        """
        self.config = config or {}
        self.validate_config()

    def validate_config(self) -> None:
        """
        Validate plugin configuration.
        Override to add custom validation logic.

        Raises:
            ValueError: If configuration is invalid
        """
        if self.config_schema:
            for key, schema in self.config_schema.items():
                if schema.get('required', False) and key not in self.config:
                    raise ValueError(f"Required config key '{key}' missing for plugin '{self.name}'")

    @abstractmethod
    def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Execute the plugin's main task.

        Args:
            context: Execution context containing:
                - business: Current business/tenant instance
                - scheduled_task: ScheduledTask instance that triggered this
                - execution_time: When this execution started
                - user: User who created the scheduled task (if applicable)

        Returns:
            Dictionary with execution results:
                - success: bool - Whether execution succeeded
                - message: str - Human-readable result message
                - data: dict - Any additional data

        Raises:
            PluginExecutionError: If execution fails
        """
        pass

    def can_execute(self, context: Dict[str, Any]) -> tuple[bool, Optional[str]]:
        """
        Check if plugin can execute in current context.
        Override to add pre-execution checks.

        Args:
            context: Execution context

        Returns:
            Tuple of (can_execute: bool, reason: Optional[str])
        """
        return True, None

    def on_success(self, result: Dict[str, Any]) -> None:
        """
        Called after successful execution.
        Override for post-execution logic.
        """
        pass

    def on_failure(self, error: Exception) -> None:
        """
        Called after failed execution.
        Override for error handling logic.
        """
        logger.error(f"Plugin {self.name} failed: {error}", exc_info=True)

    def get_next_run_time(self, last_run: Optional[timezone.datetime]) -> Optional[timezone.datetime]:
        """
        Calculate next run time based on plugin logic.
        Override for custom scheduling logic.

        Args:
            last_run: Last execution time (None if never run)

        Returns:
            Next scheduled run time, or None to use schedule's default logic
        """
        return None

    def __str__(self) -> str:
        return f"{self.display_name} ({self.name})"


class PluginRegistry:
    """
    Registry for managing available plugins.
    """

    def __init__(self):
        self._plugins: Dict[str, type[BasePlugin]] = {}

    def register(self, plugin_class: type[BasePlugin]) -> None:
        """
        Register a plugin class.

        Args:
            plugin_class: Plugin class to register

        Raises:
            ValueError: If plugin name is missing or already registered
        """
        if not plugin_class.name:
            raise ValueError(f"Plugin class {plugin_class.__name__} must define a 'name' attribute")

        if plugin_class.name in self._plugins:
            raise ValueError(f"Plugin '{plugin_class.name}' is already registered")

        self._plugins[plugin_class.name] = plugin_class
        logger.info(f"Registered plugin: {plugin_class.name}")

    def unregister(self, plugin_name: str) -> None:
        """Unregister a plugin by name"""
        if plugin_name in self._plugins:
            del self._plugins[plugin_name]
            logger.info(f"Unregistered plugin: {plugin_name}")

    def get(self, plugin_name: str) -> Optional[type[BasePlugin]]:
        """Get plugin class by name"""
        return self._plugins.get(plugin_name)

    def get_instance(self, plugin_name: str, config: Optional[Dict[str, Any]] = None) -> Optional[BasePlugin]:
        """
        Get plugin instance by name with configuration.

        Args:
            plugin_name: Name of plugin to instantiate
            config: Configuration dictionary

        Returns:
            Plugin instance or None if not found
        """
        plugin_class = self.get(plugin_name)
        if plugin_class:
            return plugin_class(config=config)
        return None

    def list_all(self) -> List[Dict[str, Any]]:
        """
        List all registered plugins with metadata.

        Returns:
            List of plugin metadata dictionaries
        """
        return [
            {
                'name': plugin_class.name,
                'display_name': plugin_class.display_name,
                'description': plugin_class.description,
                'category': plugin_class.category,
                'config_schema': plugin_class.config_schema,
            }
            for plugin_class in self._plugins.values()
        ]

    def list_by_category(self) -> Dict[str, List[Dict[str, Any]]]:
        """
        List plugins grouped by category.

        Returns:
            Dictionary mapping category names to plugin lists
        """
        categories: Dict[str, List[Dict[str, Any]]] = {}
        for plugin_info in self.list_all():
            category = plugin_info['category']
            if category not in categories:
                categories[category] = []
            categories[category].append(plugin_info)
        return categories


# Global plugin registry
registry = PluginRegistry()


def register_plugin(plugin_class: type[BasePlugin]) -> type[BasePlugin]:
    """
    Decorator to register a plugin class.

    Usage:
        @register_plugin
        class MyPlugin(BasePlugin):
            name = "my_plugin"
            ...
    """
    registry.register(plugin_class)
    return plugin_class
