""" 提供者 Fallback Service

Implements automatic fallback mechanism for AI service providers.
When a primary provider fails, automatically switches to backup providers.

Requirement 7.5: Implement故障转移机制
""" 
import logging
from typing import List, Optional, Dict, Any, Callable
from src.services.provider.base import (
    AIProvider,
    ImageProvider,
    VideoProvider,
    TextProvider,
    AudioProvider,
    ServiceResponse,
    TaskStatus
)
from src.services.provider.registry import ModelRegistry, ModelType
from src.services.provider.health import health_monitor, HealthStatus
from src.utils.errors import ModelNotAvailableException

logger = logging.getLogger(__name__)


class ProviderService:
    """ 服务 for managing provider fallback and failover.
    
    Implements automatic switching to backup providers when primary fails.
    Requirement 7.5: Support fallback to alternative providers
    """
    
    @staticmethod
    async def generate_with_fallback(
        primary_model: str,
        fallback_models: Optional[List[str]] = None,
        operation: str = "generate",
        **params
    ) -> ServiceResponse:
        """
        Execute generation with automatic fallback.
        
        Args:
            primary_model: Primary model ID to try first
            fallback_models: List of fallback model IDs to try if primary fails.
                           If None, will try to load from config or auto-detect.
            operation: Operation name (e.g., 'generate_image', 'generate_video')
            **params: Parameters to pass to the generation method
            
        Returns:
            ServiceResponse from successful provider
            
        Raises:
            ModelNotAvailableException: If all providers fail
            
        Example:
            ```python
            response = await ProviderService.generate_with_fallback(
                primary_model="flux-dev",
                fallback_models=["wan2.6-t2i", "qwen-image-plus"],
                operation="generate_image",
                prompt="a beautiful landscape",
                size="1024*1024"
            )
            ```
        """
        # Load fallback models from config if not provided
        if fallback_models is None:
            fallback_models = ProviderService.get_fallback_config(primary_model)
            if fallback_models:
                logger.info(f"Loaded fallback models from config for {primary_model}: {fallback_models}")
        
        # If still None, use empty list (no fallback)
        if fallback_models is None:
            fallback_models = []
        
        models_to_try = [primary_model] + fallback_models
        last_error = None
        
        for model_id in models_to_try:
            try:
                # 获取 service instance
                service = ModelRegistry.get(model_id)
                if not service:
                    logger.warning(f"Model {model_id} not found in registry, skipping")
                    continue
                
                # Check health status
                health = health_monitor.get_health(model_id)
                if health and health.status == HealthStatus.UNHEALTHY:
                    logger.warning(f"Model {model_id} is unhealthy, skipping")
                    continue
                
                # 获取 the operation method
                if not hasattr(service, operation):
                    logger.warning(f"Model {model_id} does not support operation {operation}")
                    continue
                
                method = getattr(service, operation)
                
                # Execute operation
                logger.info(f"Attempting {operation} with model {model_id}")
                response = await method(**params)
                
                # Check if response indicates failure
                if response.status == TaskStatus.FAILED:
                    logger.warning(f"Model {model_id} returned FAILED status: {response.error}")
                    last_error = response.error
                    
                    # Mark as unhealthy if it has mark_unhealthy method
                    if hasattr(service, 'mark_unhealthy'):
                        service.mark_unhealthy()
                    
                    continue
                
                # 成功! Log and return
                if model_id != primary_model:
                    logger.info(f"Successfully failed over from {primary_model} to {model_id}")
                
                return response
                
            except Exception as e:
                logger.error(f"Error with model {model_id}: {str(e)}")
                last_error = str(e)
                
                # Mark as unhealthy
                service = ModelRegistry.get(model_id)
                if service and hasattr(service, 'mark_unhealthy'):
                    service.mark_unhealthy()
                
                continue
        
        # All providers failed
        error_msg = f"All providers failed for {operation}. Last error: {last_error}"
        logger.error(error_msg)
        raise ModelNotAvailableException(
            primary_model,
            error_msg
        )
    
    @staticmethod
    async def generate_image_with_fallback(
        primary_model: str,
        fallback_models: Optional[List[str]] = None,
        **params
    ) -> ServiceResponse:
        """Generate image with automatic fallback.
        
        Args:
            primary_model: Primary image model ID
            fallback_models: Optional list of fallback models (auto-detected if None)
            **params: Image generation parameters
            
        Returns:
            ServiceResponse with generated images
        """
        # Auto-detect fallback models if not provided
        if fallback_models is None:
            fallback_models = ProviderService._get_fallback_models(
                primary_model,
                ModelType.IMAGE,
                params
            )
        
        # Determine operation based on parameters
        if params.get("image_inputs") or params.get("images") or params.get("ref_images") or params.get("reference_images"):
            operation = "generate_image_from_image"
        else:
            operation = "generate_image"
        
        return await ProviderService.generate_with_fallback(
            primary_model=primary_model,
            fallback_models=fallback_models,
            operation=operation,
            **params
        )
    
    @staticmethod
    async def generate_video_with_fallback(
        primary_model: str,
        fallback_models: Optional[List[str]] = None,
        **params
    ) -> ServiceResponse:
        """Generate video with automatic fallback.
        
        Args:
            primary_model: Primary video model ID
            fallback_models: Optional list of fallback models (auto-detected if None)
            **params: Video generation parameters
            
        Returns:
            ServiceResponse with generated video
        """
        # Auto-detect fallback models if not provided
        if fallback_models is None:
            fallback_models = ProviderService._get_fallback_models(
                primary_model,
                ModelType.VIDEO,
                params
            )
        
        # Determine operation based on parameters
        if params.get("image") or params.get("image_url"):
            operation = "generate_video_from_image"
        else:
            operation = "generate_video_from_text"
        
        return await ProviderService.generate_with_fallback(
            primary_model=primary_model,
            fallback_models=fallback_models,
            operation=operation,
            **params
        )
    
    @staticmethod
    async def generate_text_with_fallback(
        primary_model: str,
        fallback_models: Optional[List[str]] = None,
        **params
    ) -> ServiceResponse:
        """Generate text with automatic fallback.
        
        Args:
            primary_model: Primary LLM model ID
            fallback_models: Optional list of fallback models (auto-detected if None)
            **params: Text generation parameters
            
        Returns:
            ServiceResponse with generated text
        """
        # Auto-detect fallback models if not provided
        if fallback_models is None:
            fallback_models = ProviderService._get_fallback_models(
                primary_model,
                ModelType.LLM,
                params
            )
        
        return await ProviderService.generate_with_fallback(
            primary_model=primary_model,
            fallback_models=fallback_models,
            operation="generate_text",
            **params
        )
    
    @staticmethod
    def _get_fallback_models(
        primary_model: str,
        model_type: ModelType,
        params: Dict[str, Any]
    ) -> List[str]:
        """
        Auto-detect suitable fallback models based on requirements.
        
        Args:
            primary_model: Primary model ID
            model_type: Type of model
            params: Generation parameters (used to determine required capabilities)
            
        Returns:
            List of suitable fallback model IDs
        """
        # Determine required capabilities from params
        required_capabilities = {}
        
        if model_type == ModelType.IMAGE:
            # Check if reference image support is needed
            if params.get("image_inputs") or params.get("images") or params.get("ref_images") or params.get("reference_images"):
                required_capabilities["supportsRefImage"] = True
        
        # 查找 suitable models
        suitable_models = ModelRegistry.find_services(
            model_type=model_type,
            capabilities=required_capabilities if required_capabilities else None,
            enabled_only=True
        )
        
        # 过滤 out primary model and unhealthy models
        fallback_models = []
        for model in suitable_models:
            model_id = model["id"]
            
            # Skip primary model
            if model_id == primary_model:
                continue
            
            # Check health
            health = health_monitor.get_health(model_id)
            if health and health.status == HealthStatus.UNHEALTHY:
                continue
            
            fallback_models.append(model_id)
        
        logger.info(f"Auto-detected {len(fallback_models)} fallback models for {primary_model}: {fallback_models}")
        return fallback_models
    
    @staticmethod
    def configure_fallback(
        model_id: str,
        fallback_models: List[str]
    ):
        """ 配置ure fallback models for a specific model.
        
        This allows pre-configuring fallback chains that will be used
        automatically when the primary model fails.
        
        Args:
            model_id: Primary model ID
            fallback_models: List of fallback model IDs in priority order
            
        Example:
            ```python
            ProviderService.configure_fallback(
                model_id="flux-dev",
                fallback_models=["wan2.6-t2i", "qwen-image-plus"]
            )
            ```
        """
        # Store in registry metadata
        config = ModelRegistry.get_config(model_id)
        if config:
            if not isinstance(config, dict):
                config = config.model_dump() if hasattr(config, 'model_dump') else {}
            
            config["fallback_models"] = fallback_models
            logger.info(f"Configured fallback for {model_id}: {fallback_models}")
        else:
            logger.warning(f"Cannot configure fallback: model {model_id} not found")
    
    @staticmethod
    def get_fallback_config(model_id: str) -> Optional[List[str]]:
        """ 获取 configured fallback models for a model.
        
        Args:
            model_id: Model ID
            
        Returns:
            List of fallback model IDs, or None if not configured
        """ 
        config = ModelRegistry.get_config(model_id)
        if config:
            if isinstance(config, dict):
                return config.get("fallback_models")
            elif hasattr(config, 'fallback_models'):
                return config.fallback_models
        return None

