"""
Centralized error handling and recovery system.
Provides structured error management with automatic recovery procedures.
"""

import asyncio
import logging
from typing import Dict, List, Optional, Any, Callable, Type
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
import traceback
import inspect

from ...shared.config import settings
from ...shared.database import db_manager
from ...shared.models.system import ErrorLog, ErrorSeverity, ErrorCategory


logger = logging.getLogger(__name__)


class RecoveryAction(str, Enum):
    """Available recovery actions."""
    NONE = "none"
    RETRY = "retry"
    RESET_COMPONENT = "reset_component"
    RESTART_SERVICE = "restart_service"
    EMERGENCY_STOP = "emergency_stop"
    USER_INTERVENTION = "user_intervention"


@dataclass
class ErrorHandler:
    """Error handler configuration."""
    exception_types: List[Type[Exception]]
    recovery_action: RecoveryAction
    max_retries: int = 3
    retry_delay: float = 1.0
    escalation_threshold: int = 5
    auto_recovery: bool = True
    requires_user_acknowledgment: bool = False
    recovery_function: Optional[Callable[[], bool]] = None
    description: str = ""


@dataclass
class ErrorContext:
    """Context information for error occurrence."""
    component: str
    function_name: str
    error_id: str
    timestamp: datetime
    exception: Exception
    stack_trace: str
    retry_count: int = 0
    recovery_attempts: int = 0
    user_notified: bool = False
    additional_data: Dict[str, Any] = None


class ErrorHandlingSystem:
    """
    Centralized error handling and recovery system.
    Manages error classification, recovery procedures, and escalation.
    """
    
    def __init__(self):
        self.error_handlers: Dict[str, ErrorHandler] = {}
        self.active_errors: Dict[str, ErrorContext] = {}
        self.error_statistics: Dict[str, int] = {}
        self.recovery_tasks: Dict[str, asyncio.Task] = {}
        
        # Initialize standard error handlers
        self._initialize_standard_handlers()
        
        # Error escalation callbacks
        self.escalation_callbacks: List[Callable[[ErrorContext], None]] = []
        
    def _initialize_standard_handlers(self) -> None:
        """Initialize standard error handlers for common exceptions."""
        
        # Communication errors
        self.register_handler(
            "communication_error",
            ErrorHandler(
                exception_types=[ConnectionError, TimeoutError, OSError],
                recovery_action=RecoveryAction.RETRY,
                max_retries=3,
                retry_delay=2.0,
                escalation_threshold=10,
                description="Modbus communication errors"
            )
        )
        
        # Hardware errors
        self.register_handler(
            "hardware_error",
            ErrorHandler(
                exception_types=[RuntimeError],
                recovery_action=RecoveryAction.RESET_COMPONENT,
                max_retries=2,
                retry_delay=5.0,
                escalation_threshold=3,
                requires_user_acknowledgment=True,
                description="Hardware component errors"
            )
        )
        
        # Safety-critical errors
        self.register_handler(
            "safety_error",
            ErrorHandler(
                exception_types=[ValueError],  # Temperature/safety limit violations
                recovery_action=RecoveryAction.EMERGENCY_STOP,
                max_retries=0,
                auto_recovery=False,
                requires_user_acknowledgment=True,
                description="Safety-critical errors requiring immediate stop"
            )
        )
        
        # Database errors
        self.register_handler(
            "database_error",
            ErrorHandler(
                exception_types=[Exception],  # SQLAlchemy exceptions would go here
                recovery_action=RecoveryAction.RETRY,
                max_retries=5,
                retry_delay=1.0,
                escalation_threshold=15,
                description="Database connection and query errors"
            )
        )
        
        # Generic application errors
        self.register_handler(
            "application_error",
            ErrorHandler(
                exception_types=[Exception],
                recovery_action=RecoveryAction.USER_INTERVENTION,
                max_retries=0,
                auto_recovery=False,
                requires_user_acknowledgment=True,
                description="General application errors requiring investigation"
            )
        )
    
    def register_handler(self, handler_id: str, handler: ErrorHandler) -> None:
        """Register an error handler."""
        self.error_handlers[handler_id] = handler
        logger.info(f"Registered error handler: {handler_id}")
    
    def get_handler_for_exception(self, exception: Exception) -> Optional[ErrorHandler]:
        """Get the appropriate error handler for an exception."""
        exception_type = type(exception)
        
        # Look for exact match first
        for handler in self.error_handlers.values():
            if exception_type in handler.exception_types:
                return handler
        
        # Look for base class match
        for handler in self.error_handlers.values():
            for handled_type in handler.exception_types:
                if issubclass(exception_type, handled_type):
                    return handler
        
        # Return generic handler if no specific match
        return self.error_handlers.get("application_error")
    
    async def handle_error(
        self,
        exception: Exception,
        component: str,
        additional_context: Optional[Dict[str, Any]] = None
    ) -> bool:
        """
        Handle an error with appropriate recovery action.
        Returns True if error was handled successfully.
        """
        try:
            # Get caller information
            frame = inspect.currentframe().f_back
            function_name = frame.f_code.co_name if frame else "unknown"
            
            # Create error context
            error_id = f"{component}_{function_name}_{type(exception).__name__}_{hash(str(exception)) % 10000}"
            
            context = ErrorContext(
                component=component,
                function_name=function_name,
                error_id=error_id,
                timestamp=datetime.now(),
                exception=exception,
                stack_trace=traceback.format_exc(),
                additional_data=additional_context or {}
            )
            
            # Check if this is a recurring error
            if error_id in self.active_errors:
                existing_context = self.active_errors[error_id]
                existing_context.retry_count += 1
                existing_context.timestamp = datetime.now()
                context = existing_context
            else:
                self.active_errors[error_id] = context
            
            # Update statistics
            self.error_statistics[error_id] = self.error_statistics.get(error_id, 0) + 1
            
            # Get appropriate handler
            handler = self.get_handler_for_exception(exception)
            if not handler:
                logger.error(f"No handler found for exception: {type(exception).__name__}")
                return False
            
            # Log the error
            await self._log_error(context, handler)
            
            # Execute recovery action
            recovery_success = await self._execute_recovery_action(context, handler)
            
            # Check for escalation
            if not recovery_success or context.retry_count > handler.escalation_threshold:
                await self._escalate_error(context, handler)
            
            return recovery_success
            
        except Exception as e:
            logger.critical(f"Error in error handler: {e}")
            return False
    
    async def _execute_recovery_action(self, context: ErrorContext, handler: ErrorHandler) -> bool:
        """Execute the recovery action for an error."""
        try:
            logger.info(f"Executing recovery action {handler.recovery_action.value} for {context.error_id}")
            
            if handler.recovery_action == RecoveryAction.NONE:
                return True
            
            elif handler.recovery_action == RecoveryAction.RETRY:
                if context.retry_count < handler.max_retries:
                    await asyncio.sleep(handler.retry_delay)
                    return True
                else:
                    logger.warning(f"Max retries exceeded for {context.error_id}")
                    return False
            
            elif handler.recovery_action == RecoveryAction.RESET_COMPONENT:
                return await self._reset_component(context.component)
            
            elif handler.recovery_action == RecoveryAction.RESTART_SERVICE:
                return await self._restart_service(context.component)
            
            elif handler.recovery_action == RecoveryAction.EMERGENCY_STOP:
                return await self._execute_emergency_stop(context)
            
            elif handler.recovery_action == RecoveryAction.USER_INTERVENTION:
                await self._request_user_intervention(context, handler)
                return False  # Requires manual intervention
            
            else:
                logger.error(f"Unknown recovery action: {handler.recovery_action}")
                return False
                
        except Exception as e:
            logger.error(f"Error executing recovery action: {e}")
            return False
    
    async def _reset_component(self, component: str) -> bool:
        """Reset a specific component."""
        try:
            logger.info(f"Resetting component: {component}")
            
            # Import here to avoid circular imports
            from ..hardware.hardware_manager import hardware_manager
            from ..recipe.recipe_controller import recipe_controller
            
            if component.lower() == "hardware":
                # Reinitialize hardware manager
                await hardware_manager.shutdown()
                return await hardware_manager.initialize()
            
            elif component.lower() == "recipe":
                # Stop current recipe if running
                await recipe_controller.stop_recipe(reason="Component reset")
                return True
            
            else:
                logger.warning(f"Unknown component for reset: {component}")
                return False
                
        except Exception as e:
            logger.error(f"Error resetting component {component}: {e}")
            return False
    
    async def _restart_service(self, service: str) -> bool:
        """Restart a specific service."""
        try:
            logger.info(f"Restarting service: {service}")
            
            # This would restart the specific service
            # Implementation depends on service architecture
            
            return True
            
        except Exception as e:
            logger.error(f"Error restarting service {service}: {e}")
            return False
    
    async def _execute_emergency_stop(self, context: ErrorContext) -> bool:
        """Execute emergency stop procedure."""
        try:
            logger.critical(f"EMERGENCY STOP triggered by error: {context.error_id}")
            
            # Import here to avoid circular imports
            from ..hardware.hardware_manager import hardware_manager
            from ..recipe.recipe_controller import recipe_controller
            
            # Stop recipe controller
            await recipe_controller.emergency_stop()
            
            # Emergency stop hardware
            await hardware_manager.emergency_stop()
            
            return True
            
        except Exception as e:
            logger.critical(f"Error during emergency stop: {e}")
            return False
    
    async def _request_user_intervention(self, context: ErrorContext, handler: ErrorHandler) -> None:
        """Request user intervention for error resolution."""
        try:
            logger.warning(f"User intervention required for: {context.error_id}")
            
            # Mark as requiring user attention
            context.user_notified = True
            
            # Create high-priority alarm through safety monitor
            from .safety_monitor import safety_monitor
            
            await safety_monitor._create_alarm(
                f"USER_INTERVENTION_{context.error_id}",
                "User intervention required",
                f"Error in {context.component}: {str(context.exception)}",
                ErrorCategory.SYSTEM,
                "HIGH"  # This should be imported from safety_monitor but avoiding circular import
            )
            
        except Exception as e:
            logger.error(f"Error requesting user intervention: {e}")
    
    async def _escalate_error(self, context: ErrorContext, handler: ErrorHandler) -> None:
        """Escalate error to higher level handling."""
        try:
            logger.critical(f"Escalating error: {context.error_id}")
            
            # Notify escalation callbacks
            for callback in self.escalation_callbacks:
                try:
                    callback(context)
                except Exception as e:
                    logger.error(f"Error in escalation callback: {e}")
            
            # For critical escalations, trigger emergency stop
            if self.error_statistics[context.error_id] > handler.escalation_threshold * 2:
                logger.critical(f"Critical escalation threshold reached for {context.error_id}")
                await self._execute_emergency_stop(context)
            
        except Exception as e:
            logger.error(f"Error during error escalation: {e}")
    
    async def _log_error(self, context: ErrorContext, handler: ErrorHandler) -> None:
        """Log error to database and system logs."""
        try:
            # Determine severity based on recovery action and retry count
            if handler.recovery_action == RecoveryAction.EMERGENCY_STOP:
                severity = ErrorSeverity.CRITICAL
            elif context.retry_count > handler.escalation_threshold:
                severity = ErrorSeverity.HIGH
            elif handler.requires_user_acknowledgment:
                severity = ErrorSeverity.HIGH
            else:
                severity = ErrorSeverity.MEDIUM
            
            # Determine category based on component
            category_map = {
                "hardware": ErrorCategory.HARDWARE,
                "communication": ErrorCategory.COMMUNICATION,
                "modbus": ErrorCategory.COMMUNICATION,
                "recipe": ErrorCategory.PROCESS,
                "safety": ErrorCategory.SAFETY,
                "database": ErrorCategory.SYSTEM,
            }
            
            category = category_map.get(context.component.lower(), ErrorCategory.SYSTEM)
            
            # Log to system logger
            log_method = getattr(logger, severity.value.lower(), logger.error)
            log_method(f"[{context.component}] {context.function_name}: {str(context.exception)}")
            
            # Log to database
            async with db_manager.get_async_session() as session:
                error_log = ErrorLog(
                    error_code=context.error_id,
                    category=category,
                    severity=severity,
                    title=f"Error in {context.component}",
                    message=str(context.exception),
                    component=context.component,
                    stack_trace=context.stack_trace,
                    additional_data=context.additional_data,
                    occurrence_count=self.error_statistics[context.error_id],
                    first_occurrence=context.timestamp if self.error_statistics[context.error_id] == 1 else None,
                    last_occurrence=context.timestamp,
                )
                
                session.add(error_log)
                await session.commit()
                
        except Exception as e:
            logger.error(f"Error logging error to database: {e}")
    
    async def acknowledge_error(self, error_id: str, user: str) -> bool:
        """Acknowledge an error that requires user intervention."""
        try:
            if error_id in self.active_errors:
                context = self.active_errors[error_id]
                
                # Remove from active errors
                del self.active_errors[error_id]
                
                # Cancel any recovery tasks
                if error_id in self.recovery_tasks:
                    self.recovery_tasks[error_id].cancel()
                    del self.recovery_tasks[error_id]
                
                logger.info(f"Error acknowledged by {user}: {error_id}")
                return True
                
        except Exception as e:
            logger.error(f"Error acknowledging error: {e}")
        
        return False
    
    def get_active_errors(self) -> List[ErrorContext]:
        """Get list of active errors."""
        return list(self.active_errors.values())
    
    def get_error_statistics(self) -> Dict[str, Any]:
        """Get error handling statistics."""
        return {
            "active_errors": len(self.active_errors),
            "total_error_types": len(self.error_statistics),
            "error_counts": self.error_statistics.copy(),
            "recovery_tasks": len(self.recovery_tasks),
            "registered_handlers": len(self.error_handlers),
        }
    
    def add_escalation_callback(self, callback: Callable[[ErrorContext], None]) -> None:
        """Add callback function for error escalation."""
        self.escalation_callbacks.append(callback)
    
    async def clear_resolved_errors(self, max_age_hours: int = 24) -> int:
        """Clear resolved errors older than specified age."""
        try:
            cutoff_time = datetime.now() - timedelta(hours=max_age_hours)
            cleared_count = 0
            
            errors_to_remove = []
            for error_id, context in self.active_errors.items():
                if context.timestamp < cutoff_time:
                    errors_to_remove.append(error_id)
            
            for error_id in errors_to_remove:
                del self.active_errors[error_id]
                cleared_count += 1
            
            logger.info(f"Cleared {cleared_count} resolved errors")
            return cleared_count
            
        except Exception as e:
            logger.error(f"Error clearing resolved errors: {e}")
            return 0


# Global error handling system
error_handler = ErrorHandlingSystem()


# Decorator for automatic error handling
def handle_errors(component: str = "unknown", additional_context: Optional[Dict[str, Any]] = None):
    """Decorator to automatically handle errors in functions."""
    def decorator(func):
        async def async_wrapper(*args, **kwargs):
            try:
                return await func(*args, **kwargs)
            except Exception as e:
                success = await error_handler.handle_error(e, component, additional_context)
                if not success:
                    raise
                
        def sync_wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                # For sync functions, we can't await the error handler
                # So we log and re-raise
                logger.error(f"Error in {component}.{func.__name__}: {e}")
                raise
                
        return async_wrapper if inspect.iscoroutinefunction(func) else sync_wrapper
    
    return decorator