"""
Automation system for automated tasks.

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

Example automation:
    class SendWeeklyReportAutomation(BaseAutomation):
        name = "send_weekly_report"
        display_name = "Send Weekly Report"
        description = "Emails a weekly business report to managers"

        def execute(self, context):
            # Automation 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 AutomationExecutionError(Exception):
    """Raised when an automation fails to execute"""
    pass


class BaseAutomation(ABC):
    """
    Base class for all scheduler automations.

    Subclass this to create custom automated tasks.
    """

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

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

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

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

    def validate_config(self) -> None:
        """
        Validate automation 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 automation '{self.name}'")

    @abstractmethod
    def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Execute the automation'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:
            AutomationExecutionError: If execution fails
        """
        pass

    def can_execute(self, context: Dict[str, Any]) -> tuple[bool, Optional[str]]:
        """
        Check if automation 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"Automation {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 automation 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 AutomationRegistry:
    """
    Registry for managing available automations.
    """

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

    def register(self, automation_class: type[BaseAutomation]) -> None:
        """
        Register an automation class.

        Args:
            automation_class: Automation class to register

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

        if automation_class.name in self._automations:
            raise ValueError(f"Automation '{automation_class.name}' is already registered")

        self._automations[automation_class.name] = automation_class
        logger.info(f"Registered automation: {automation_class.name}")

    def unregister(self, automation_name: str) -> None:
        """Unregister an automation by name"""
        if automation_name in self._automations:
            del self._automations[automation_name]
            logger.info(f"Unregistered automation: {automation_name}")

    def get(self, automation_name: str) -> Optional[type[BaseAutomation]]:
        """Get automation class by name"""
        return self._automations.get(automation_name)

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

        Args:
            automation_name: Name of automation to instantiate
            config: Configuration dictionary

        Returns:
            Automation instance or None if not found
        """
        automation_class = self.get(automation_name)
        if automation_class:
            return automation_class(config=config)
        return None

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

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

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

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


# Global automation registry
registry = AutomationRegistry()


def register_automation(automation_class: type[BaseAutomation]) -> type[BaseAutomation]:
    """
    Decorator to register an automation class.

    Usage:
        @register_automation
        class MyAutomation(BaseAutomation):
            name = "my_automation"
            ...
    """
    registry.register(automation_class)
    return automation_class
