"""
Workflow integration utilities for seamless data flow between PrecipGen components.

This module provides utilities to ensure data flows seamlessly between components,
handles automatic format conversion, and maintains parameter manifest compatibility
across all engines.
"""

import pandas as pd
import numpy as np
from typing import Dict, Any, Optional, Union, Tuple, List
from datetime import datetime
import logging
from pathlib import Path
import json

from ..engines.analytical import ParameterManifest, MonthlyParams, TrendAnalysis
from ..simulation.monte_carlo import PrecipitationSimulator
from ..data.validator import DataValidator
from .data_exchange import DataExchangeFormat, ValidationReport

logger = logging.getLogger(__name__)


class WorkflowCache:
    """
    Enhanced caching system for workflow intermediate results to avoid redundant computations.
    
    Provides intelligent caching with size limits, TTL, and performance monitoring
    to optimize workflow execution while managing memory usage.
    """
    
    def __init__(self, max_size: int = 100, ttl_hours: float = 24.0):
        """
        Initialize workflow cache with size and time limits.
        
        Args:
            max_size: Maximum number of cache entries
            ttl_hours: Time-to-live for cache entries in hours
        """
        self._cache = {}
        self._cache_metadata = {}
        self._access_times = {}
        self._max_size = max_size
        self._ttl_seconds = ttl_hours * 3600
        self._hit_count = 0
        self._miss_count = 0
    
    def get_cache_key(self, data: pd.Series, config: Dict[str, Any]) -> str:
        """Generate cache key based on data characteristics and configuration."""
        try:
            # Create a hash based on data characteristics
            data_hash = hash((
                len(data),
                data.index.min().isoformat() if hasattr(data.index, 'min') else 'no_index',
                data.index.max().isoformat() if hasattr(data.index, 'max') else 'no_index',
                float(data.sum()) if len(data) > 0 else 0.0,
                float(data.std()) if len(data) > 1 else 0.0
            ))
            
            # Include configuration in hash
            config_str = json.dumps(config, sort_keys=True, default=str)
            config_hash = hash(config_str)
            
            return f"{data_hash}_{config_hash}"
        except Exception as e:
            logger.warning(f"Failed to generate cache key: {e}")
            # Fallback to simple key
            return f"fallback_{len(data)}_{hash(str(config))}"
    
    def get(self, cache_key: str) -> Optional[Any]:
        """Get cached result with TTL checking."""
        if cache_key not in self._cache:
            self._miss_count += 1
            return None
        
        # Check TTL
        created_time = datetime.fromisoformat(self._cache_metadata[cache_key]['created_at'])
        if (datetime.now() - created_time).total_seconds() > self._ttl_seconds:
            logger.debug(f"Cache entry expired for key: {cache_key}")
            self._evict_entry(cache_key)
            self._miss_count += 1
            return None
        
        # Update access time and return result
        self._access_times[cache_key] = datetime.now()
        self._cache_metadata[cache_key]['access_count'] += 1
        self._hit_count += 1
        logger.debug(f"Cache hit for key: {cache_key}")
        return self._cache[cache_key]
    
    def set(self, cache_key: str, result: Any, metadata: Optional[Dict[str, Any]] = None) -> None:
        """Store result in cache with size management."""
        # Check if we need to evict entries
        if len(self._cache) >= self._max_size:
            self._evict_lru_entries(1)
        
        # Store result with metadata
        self._cache[cache_key] = result
        self._cache_metadata[cache_key] = {
            'created_at': datetime.now().isoformat(),
            'access_count': 0,
            'metadata': metadata or {},
            'result_size_estimate': len(str(result))
        }
        self._access_times[cache_key] = datetime.now()
        
        logger.debug(f"Cached result for key: {cache_key}")
    
    def _evict_entry(self, cache_key: str) -> None:
        """Remove a single cache entry."""
        if cache_key in self._cache:
            del self._cache[cache_key]
            del self._cache_metadata[cache_key]
            if cache_key in self._access_times:
                del self._access_times[cache_key]
    
    def _evict_lru_entries(self, count: int) -> None:
        """Evict least recently used entries."""
        if not self._access_times:
            return
        
        # Sort by access time and remove oldest
        sorted_keys = sorted(self._access_times.items(), key=lambda x: x[1])
        for cache_key, _ in sorted_keys[:count]:
            logger.debug(f"Evicting LRU cache entry: {cache_key}")
            self._evict_entry(cache_key)
    
    def clear(self) -> None:
        """Clear all cached results."""
        entry_count = len(self._cache)
        self._cache.clear()
        self._cache_metadata.clear()
        self._access_times.clear()
        self._hit_count = 0
        self._miss_count = 0
        logger.info(f"Workflow cache cleared ({entry_count} entries)")
    
    def cleanup_expired(self) -> int:
        """Remove expired cache entries and return count of removed entries."""
        expired_keys = []
        current_time = datetime.now()
        
        for cache_key, metadata in self._cache_metadata.items():
            created_time = datetime.fromisoformat(metadata['created_at'])
            if (current_time - created_time).total_seconds() > self._ttl_seconds:
                expired_keys.append(cache_key)
        
        for cache_key in expired_keys:
            self._evict_entry(cache_key)
        
        if expired_keys:
            logger.info(f"Cleaned up {len(expired_keys)} expired cache entries")
        
        return len(expired_keys)
    
    def get_cache_info(self) -> Dict[str, Any]:
        """Get comprehensive cache statistics."""
        total_requests = self._hit_count + self._miss_count
        hit_ratio = self._hit_count / max(1, total_requests)
        
        return {
            'total_entries': len(self._cache),
            'max_size': self._max_size,
            'ttl_hours': self._ttl_seconds / 3600,
            'hit_count': self._hit_count,
            'miss_count': self._miss_count,
            'hit_ratio': hit_ratio,
            'memory_usage_estimate_mb': sum(
                metadata.get('result_size_estimate', 0) 
                for metadata in self._cache_metadata.values()
            ) / (1024 * 1024),
            'oldest_entry_age_hours': self._get_oldest_entry_age_hours(),
            'most_accessed_entry': self._get_most_accessed_entry()
        }
    
    def _get_oldest_entry_age_hours(self) -> Optional[float]:
        """Get age of oldest cache entry in hours."""
        if not self._cache_metadata:
            return None
        
        oldest_time = min(
            datetime.fromisoformat(metadata['created_at'])
            for metadata in self._cache_metadata.values()
        )
        return (datetime.now() - oldest_time).total_seconds() / 3600
    
    def _get_most_accessed_entry(self) -> Optional[str]:
        """Get the most frequently accessed cache entry."""
        if not self._cache_metadata:
            return None
        
        return max(
            self._cache_metadata.items(),
            key=lambda x: x[1]['access_count']
        )[0]


class DataFormatConverter:
    """
    Automatic data format conversion utilities for seamless workflow integration.
    """
    
    @staticmethod
    def ensure_precipitation_series(data: Union[pd.Series, np.ndarray, List[float], Dict[str, Any]],
                                  preserve_metadata: bool = True) -> pd.Series:
        """
        Convert various data formats to standardized precipitation Series.
        
        Args:
            data: Input data in various formats
            preserve_metadata: Whether to preserve existing metadata
            
        Returns:
            Standardized pandas Series with datetime index and metadata
        """
        if isinstance(data, pd.Series):
            # Already a Series - ensure datetime index
            result = data.copy()
            if not isinstance(result.index, pd.DatetimeIndex):
                # Create datetime index if missing
                start_date = pd.Timestamp('2000-01-01')
                result.index = pd.date_range(start_date, periods=len(result), freq='D')
            
            # Ensure proper name
            if result.name is None:
                result.name = 'precipitation'
            
            return result
        
        elif isinstance(data, dict):
            # Dictionary format from API
            if 'values' in data:
                values = data['values']
                if 'dates' in data:
                    dates = pd.to_datetime(data['dates'])
                    result = pd.Series(values, index=dates, name='precipitation')
                else:
                    start_date = pd.Timestamp('2000-01-01')
                    result = pd.Series(values, 
                                     index=pd.date_range(start_date, periods=len(values), freq='D'),
                                     name='precipitation')
                
                # Add metadata from dictionary
                if preserve_metadata and 'metadata' in data:
                    result.attrs.update(data['metadata'])
                
                return result
            else:
                raise ValueError("Dictionary format must contain 'values' key")
        
        elif isinstance(data, (np.ndarray, list)):
            # Array or list format
            values = np.array(data) if isinstance(data, list) else data
            start_date = pd.Timestamp('2000-01-01')
            result = pd.Series(values,
                             index=pd.date_range(start_date, periods=len(values), freq='D'),
                             name='precipitation')
            return result
        
        else:
            raise ValueError(f"Unsupported data format: {type(data)}")
    
    @staticmethod
    def standardize_parameter_manifest(manifest: ParameterManifest) -> ParameterManifest:
        """
        Ensure parameter manifest has all required fields for workflow compatibility.
        
        Args:
            manifest: Input parameter manifest
            
        Returns:
            Standardized parameter manifest
        """
        # Ensure metadata has required fields
        if 'workflow_version' not in manifest.metadata:
            manifest.metadata['workflow_version'] = '1.0'
        
        if 'created_at' not in manifest.metadata:
            manifest.metadata['created_at'] = datetime.now().isoformat()
        
        if 'data_source' not in manifest.metadata:
            manifest.metadata['data_source'] = 'unknown'
        
        # Ensure all months have parameters
        for month in range(1, 13):
            if month not in manifest.overall_parameters:
                logger.warning(f"Missing parameters for month {month}, using defaults")
                # Use average of available months as fallback
                available_params = list(manifest.overall_parameters.values())
                if available_params:
                    avg_params = MonthlyParams(
                        p_ww=np.mean([p.p_ww for p in available_params]),
                        p_wd=np.mean([p.p_wd for p in available_params]),
                        alpha=np.mean([p.alpha for p in available_params]),
                        beta=np.mean([p.beta for p in available_params])
                    )
                    manifest.overall_parameters[month] = avg_params
                else:
                    # Use generic defaults
                    manifest.overall_parameters[month] = MonthlyParams(
                        p_ww=0.5, p_wd=0.3, alpha=1.0, beta=5.0
                    )
        
        return manifest
    
    @staticmethod
    def extract_metadata_from_series(data: pd.Series) -> Dict[str, Any]:
        """
        Extract comprehensive metadata from pandas Series for workflow integration.
        
        Args:
            data: Input pandas Series
            
        Returns:
            Metadata dictionary
        """
        metadata = {}
        
        # Extract from Series attributes
        if hasattr(data, 'attrs') and data.attrs:
            metadata.update(data.attrs)
        
        # Add computed metadata
        metadata.update({
            'data_length': len(data),
            'data_type': 'precipitation_mm',
            'has_datetime_index': isinstance(data.index, pd.DatetimeIndex),
            'series_name': data.name or 'precipitation',
            'computed_stats': {
                'mean': float(data.mean()),
                'std': float(data.std()),
                'min': float(data.min()),
                'max': float(data.max()),
                'sum': float(data.sum())
            }
        })
        
        # Add temporal metadata if datetime index
        if isinstance(data.index, pd.DatetimeIndex):
            metadata.update({
                'temporal_range': {
                    'start': data.index.min().isoformat(),
                    'end': data.index.max().isoformat(),
                    'frequency': str(data.index.freq) if data.index.freq else 'D'
                }
            })
        
        return metadata


class WorkflowIntegrator:
    """
    Main workflow integration class that orchestrates seamless data flow
    between PrecipGen components with robust error handling and performance optimization.
    """
    
    def __init__(self, enable_caching: bool = True, cache_size: int = 100, cache_ttl_hours: float = 24.0):
        """
        Initialize workflow integrator with enhanced error handling and caching.
        
        Args:
            enable_caching: Whether to enable result caching
            cache_size: Maximum number of cache entries
            cache_ttl_hours: Cache time-to-live in hours
        """
        self.cache = WorkflowCache(max_size=cache_size, ttl_hours=cache_ttl_hours) if enable_caching else None
        self.converter = DataFormatConverter()
        self.validator = DataValidator()
        self.exchange = DataExchangeFormat()
        
        # Import error handling utilities
        from ..utils.error_handling import (
            error_recovery_manager, performance_optimizer, degradation_manager
        )
        self.error_manager = error_recovery_manager
        self.performance_optimizer = performance_optimizer
        self.degradation_manager = degradation_manager
        
        # Register workflow-specific recovery strategies
        self._register_workflow_recovery_strategies()
        
        logger.info(f"WorkflowIntegrator initialized (caching: {enable_caching})")
    
    def _register_workflow_recovery_strategies(self) -> None:
        """Register workflow-specific error recovery strategies."""
        
        def data_loading_recovery(error: Exception, operation: str, context: Dict[str, Any]) -> pd.Series:
            """Recovery strategy for data loading failures."""
            logger.warning(f"Attempting data loading recovery for: {error}")
            
            # Try alternative data sources if available
            if 'alternative_sources' in context:
                for alt_source in context['alternative_sources']:
                    try:
                        from ..simple_api import load_csv_precipitation_data
                        return load_csv_precipitation_data(alt_source)
                    except Exception as alt_error:
                        logger.debug(f"Alternative source failed: {alt_error}")
                        continue
            
            # Create minimal synthetic data as last resort
            logger.warning("Creating minimal synthetic data for workflow continuation")
            dates = pd.date_range('2000-01-01', periods=365, freq='D')
            synthetic_data = pd.Series(
                np.random.exponential(2.0, len(dates)),
                index=dates,
                name='precipitation'
            )
            synthetic_data.attrs = {
                'source': 'synthetic_fallback',
                'station_name': 'Synthetic Station',
                'warning': 'This is synthetic fallback data'
            }
            return synthetic_data
        
        def analysis_recovery(error: Exception, operation: str, context: Dict[str, Any]) -> Dict[str, Any]:
            """Recovery strategy for analysis failures."""
            logger.warning(f"Attempting analysis recovery for: {error}")
            
            # Use simplified analysis parameters
            from ..engines.analytical import MonthlyParams, ParameterManifest
            
            default_params = {}
            for month in range(1, 13):
                default_params[month] = MonthlyParams(
                    p_ww=0.5, p_wd=0.3, alpha=1.0, beta=5.0
                )
            
            manifest = ParameterManifest(
                metadata={
                    'source': 'fallback_parameters',
                    'created_at': datetime.now().isoformat(),
                    'warning': 'Using default parameters due to analysis failure'
                },
                overall_parameters=default_params,
                trend_analysis=None,
                sliding_window_stats=None
            )
            
            return {
                'monthly_parameters': default_params,
                'parameter_manifest': manifest,
                'trend_analysis': None,
                'analysis_config': context.get('config', {})
            }
        
        def simulation_recovery(error: Exception, operation: str, context: Dict[str, Any]) -> Dict[str, pd.DataFrame]:
            """Recovery strategy for simulation failures."""
            logger.warning(f"Attempting simulation recovery for: {error}")
            
            # Create minimal simulation results
            n_days = context.get('n_days', 365)
            n_realizations = context.get('n_realizations', 10)
            
            # Generate simple synthetic data
            dates = pd.date_range('2000-01-01', periods=n_days, freq='D')
            synthetic_results = np.random.exponential(2.0, (n_days, n_realizations))
            
            result_df = pd.DataFrame(synthetic_results, index=dates)
            
            return {
                'wgen': result_df,
                'fallback_warning': 'Simulation failed, using synthetic fallback data'
            }
        
        # Register recovery strategies
        from ..utils.exceptions import DataLoadError, ParameterEstimationError, SimulationError
        
        self.error_manager.register_recovery_strategy(DataLoadError, data_loading_recovery)
        self.error_manager.register_recovery_strategy(ParameterEstimationError, analysis_recovery)
        self.error_manager.register_recovery_strategy(SimulationError, simulation_recovery)
    
    def create_integrated_workflow(self, 
                                 data_source: Union[str, pd.Series, Dict[str, Any]],
                                 analysis_config: Optional[Dict[str, Any]] = None,
                                 simulation_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        Create a complete integrated workflow from data source to simulation results
        with comprehensive error handling and performance optimization.
        
        This method demonstrates seamless integration by:
        1. Loading/converting data to standard format with error recovery
        2. Performing analysis with caching and graceful degradation
        3. Generating parameter manifest with fallback strategies
        4. Running simulation with error recovery
        5. Maintaining metadata and performance metrics throughout
        
        Args:
            data_source: Data source (file path, Series, or dict)
            analysis_config: Configuration for analysis phase
            simulation_config: Configuration for simulation phase
            
        Returns:
            Complete workflow results with all intermediate products and error information
        """
        workflow_start_time = datetime.now()
        workflow_metadata = {}
        error_summary = []
        performance_metrics = {}
        
        try:
            # Step 1: Data Loading and Standardization with Error Handling
            logger.info("Step 1: Loading and standardizing data")
            step_start = datetime.now()
            
            try:
                if isinstance(data_source, str):
                    # Load from file with recovery
                    from ..simple_api import load_csv_precipitation_data
                    data_series = load_csv_precipitation_data(data_source)
                else:
                    # Convert existing data
                    data_series = self.converter.ensure_precipitation_series(data_source)
                
                # Extract metadata for workflow tracking
                workflow_metadata = self.converter.extract_metadata_from_series(data_series)
                
            except Exception as e:
                logger.error(f"Data loading failed: {e}")
                recovery_success, data_series = self.error_manager.handle_error_with_recovery(
                    e, 'data_loading', {'data_source': data_source}
                )
                
                if not recovery_success:
                    return self._create_failure_response(e, workflow_metadata, "data_loading")
                
                workflow_metadata = self.converter.extract_metadata_from_series(data_series)
                error_summary.append(f"Data loading recovered from: {type(e).__name__}")
            
            performance_metrics['data_loading_time'] = (datetime.now() - step_start).total_seconds()
            
            # Step 2: Analysis with Caching and Error Recovery
            logger.info("Step 2: Performing analysis")
            step_start = datetime.now()
            analysis_config = analysis_config or {}
            
            try:
                # Check cache for analysis results
                cache_key = None
                if self.cache:
                    cache_key = self.cache.get_cache_key(data_series, analysis_config)
                    cached_analysis = self.cache.get(cache_key)
                    if cached_analysis:
                        logger.info("Using cached analysis results")
                        analysis_results = cached_analysis
                        performance_metrics['analysis_cached'] = True
                    else:
                        analysis_results = self._perform_analysis_with_recovery(data_series, analysis_config)
                        self.cache.set(cache_key, analysis_results, workflow_metadata)
                        performance_metrics['analysis_cached'] = False
                else:
                    analysis_results = self._perform_analysis_with_recovery(data_series, analysis_config)
                    performance_metrics['analysis_cached'] = False
                
            except Exception as e:
                logger.error(f"Analysis failed: {e}")
                recovery_success, analysis_results = self.error_manager.handle_error_with_recovery(
                    e, 'analysis', {'data': data_series, 'config': analysis_config}
                )
                
                if not recovery_success:
                    return self._create_failure_response(e, workflow_metadata, "analysis")
                
                error_summary.append(f"Analysis recovered from: {type(e).__name__}")
            
            performance_metrics['analysis_time'] = (datetime.now() - step_start).total_seconds()
            
            # Step 3: Parameter Manifest Generation with Validation
            logger.info("Step 3: Generating parameter manifest")
            step_start = datetime.now()
            
            try:
                parameter_manifest = analysis_results['parameter_manifest']
                parameter_manifest = self.converter.standardize_parameter_manifest(parameter_manifest)
                
                # Validate parameter manifest
                validation_report = self.validate_workflow_compatibility(data_series, parameter_manifest)
                if not validation_report.is_valid:
                    logger.warning(f"Parameter validation issues: {validation_report.errors}")
                    error_summary.extend([f"Validation: {error}" for error in validation_report.errors])
                
            except Exception as e:
                logger.error(f"Parameter manifest generation failed: {e}")
                # Use fallback parameters
                from ..engines.analytical import MonthlyParams, ParameterManifest
                
                default_params = {}
                for month in range(1, 13):
                    default_params[month] = MonthlyParams(p_ww=0.5, p_wd=0.3, alpha=1.0, beta=5.0)
                
                parameter_manifest = ParameterManifest(
                    metadata={'source': 'fallback', 'error': str(e)},
                    overall_parameters=default_params,
                    trend_analysis=None,
                    sliding_window_stats=None
                )
                error_summary.append(f"Using fallback parameters due to: {type(e).__name__}")
            
            performance_metrics['parameter_generation_time'] = (datetime.now() - step_start).total_seconds()
            
            # Step 4: Simulation Setup and Execution with Error Recovery
            logger.info("Step 4: Running simulation")
            step_start = datetime.now()
            simulation_config = simulation_config or {'n_realizations': 100}
            
            try:
                # Create simulator with parameter-only approach
                simulator = PrecipitationSimulator(
                    parameters=parameter_manifest,
                    historical_data=data_series if simulation_config.get('include_bootstrap', False) else None
                )
                
                # Run simulation with error handling
                simulation_results = simulator.run_monte_carlo_simulation(
                    n_realizations=simulation_config.get('n_realizations', 100),
                    methods=simulation_config.get('methods', ['wgen'])
                )
                
            except Exception as e:
                logger.error(f"Simulation failed: {e}")
                recovery_success, simulation_results = self.error_manager.handle_error_with_recovery(
                    e, 'simulation', {
                        'n_days': len(data_series),
                        'n_realizations': simulation_config.get('n_realizations', 100)
                    }
                )
                
                if not recovery_success:
                    return self._create_failure_response(e, workflow_metadata, "simulation")
                
                error_summary.append(f"Simulation recovered from: {type(e).__name__}")
            
            performance_metrics['simulation_time'] = (datetime.now() - step_start).total_seconds()
            
            # Step 5: Compile Complete Results with Performance Metrics
            logger.info("Step 5: Compiling workflow results")
            
            total_workflow_time = (datetime.now() - workflow_start_time).total_seconds()
            performance_metrics['total_workflow_time'] = total_workflow_time
            
            # Clean up expired cache entries
            if self.cache:
                expired_count = self.cache.cleanup_expired()
                if expired_count > 0:
                    performance_metrics['cache_cleanup_count'] = expired_count
            
            workflow_results = {
                'success': True,
                'workflow_metadata': workflow_metadata,
                'data_summary': {
                    'source': workflow_metadata.get('source', 'unknown'),
                    'station_name': workflow_metadata.get('station_name', 'Unknown'),
                    'data_length': len(data_series),
                    'temporal_range': workflow_metadata.get('temporal_range', {})
                },
                'analysis_results': analysis_results,
                'parameter_manifest': parameter_manifest.to_dict(),
                'simulation_results': self._standardize_simulation_results(simulation_results),
                'performance_metrics': performance_metrics,
                'error_summary': error_summary,
                'cache_info': self.cache.get_cache_info() if self.cache else None,
                'error_recovery_summary': self.error_manager.get_error_summary(),
                'degradation_status': self.degradation_manager.get_degradation_status(),
                'workflow_config': {
                    'analysis_config': analysis_config,
                    'simulation_config': simulation_config,
                    'caching_enabled': self.cache is not None,
                    'error_recovery_enabled': True
                }
            }
            
            logger.info(f"Integrated workflow completed successfully in {total_workflow_time:.2f}s")
            return workflow_results
            
        except Exception as e:
            logger.error(f"Integrated workflow failed catastrophically: {str(e)}")
            return self._create_failure_response(e, workflow_metadata, "workflow", error_summary)
    
    def _perform_analysis_with_recovery(self, data_series: pd.Series, config: Dict[str, Any]) -> Dict[str, Any]:
        """Perform analysis phase of workflow with error recovery."""
        try:
            return self._perform_analysis(data_series, config)
        except Exception as e:
            logger.warning(f"Primary analysis failed, attempting recovery: {e}")
            
            # Try simplified analysis
            try:
                simplified_config = config.copy()
                simplified_config['include_trends'] = False  # Disable complex trend analysis
                return self._perform_analysis(data_series, simplified_config)
            except Exception as recovery_error:
                logger.error(f"Analysis recovery also failed: {recovery_error}")
                raise e  # Re-raise original error
    
    def _create_failure_response(self, error: Exception, metadata: Dict[str, Any], 
                               stage: str, error_summary: Optional[List[str]] = None) -> Dict[str, Any]:
        """Create standardized failure response."""
        return {
            'success': False,
            'error': {
                'type': type(error).__name__,
                'message': str(error),
                'stage': stage,
                'timestamp': datetime.now().isoformat()
            },
            'workflow_metadata': metadata,
            'error_summary': error_summary or [],
            'error_recovery_summary': self.error_manager.get_error_summary(),
            'degradation_status': self.degradation_manager.get_degradation_status(),
            'guidance': self._get_error_guidance(error, stage)
        }
    
    def _get_error_guidance(self, error: Exception, stage: str) -> str:
        """Provide user guidance for error resolution."""
        guidance_map = {
            'data_loading': "Check that the data file exists and is in the correct format. "
                          "Ensure the file path is accessible and the data contains valid precipitation values.",
            'analysis': "Verify that the data has sufficient quality and completeness for analysis. "
                       "Consider adjusting quality thresholds or using a different time period.",
            'simulation': "Check that the parameter manifest is valid and contains parameters for all months. "
                         "Ensure simulation configuration is appropriate for the available data.",
            'workflow': "Review the entire workflow configuration and input data. "
                       "Check logs for specific component failures and their guidance."
        }
        
        base_guidance = guidance_map.get(stage, "Review the error details and check the documentation.")
        
        if hasattr(error, 'guidance') and error.guidance:
            return f"{base_guidance} Specific guidance: {error.guidance}"
        
        return base_guidance
    
    def _perform_analysis(self, data_series: pd.Series, config: Dict[str, Any]) -> Dict[str, Any]:
        """Perform analysis phase of workflow."""
        from ..engines.analytical import AnalyticalEngine
        
        # Create analytical engine
        wet_day_threshold = config.get('wet_day_threshold', 0.0254)
        engine = AnalyticalEngine(data_series, wet_day_threshold=wet_day_threshold)
        engine.initialize()
        
        # Perform analysis
        monthly_params = engine.calculate_monthly_parameters()
        parameter_manifest = engine.generate_parameter_manifest()
        
        # Add trend analysis if requested
        trend_analysis = None
        if config.get('include_trends', False):
            try:
                window_analysis = engine.perform_sliding_window_analysis(
                    window_years=config.get('window_years', 30)
                )
                trend_analysis = engine.extract_trends(window_analysis)
                parameter_manifest.trend_analysis = trend_analysis
            except Exception as e:
                logger.warning(f"Trend analysis failed: {e}")
        
        return {
            'monthly_parameters': monthly_params,
            'parameter_manifest': parameter_manifest,
            'trend_analysis': trend_analysis,
            'analysis_config': config
        }
    
    def _standardize_simulation_results(self, results: Dict[str, pd.DataFrame]) -> Dict[str, Any]:
        """Convert simulation results to standardized format."""
        standardized = {}
        
        for method, data in results.items():
            standardized[method] = {
                'data_type': 'simulation_results',
                'method': method,
                'shape': data.shape,
                'realizations': data.shape[1] if len(data.shape) > 1 else 1,
                'time_steps': len(data),
                'summary_stats': {
                    'mean': float(data.mean().mean()) if hasattr(data.mean(), 'mean') else float(data.mean()),
                    'std': float(data.std().mean()) if hasattr(data.std(), 'mean') else float(data.std()),
                    'min': float(data.min().min()) if hasattr(data.min(), 'min') else float(data.min()),
                    'max': float(data.max().max()) if hasattr(data.max(), 'max') else float(data.max())
                }
            }
        
        return standardized
    
    def validate_workflow_compatibility(self, 
                                      data: pd.Series,
                                      manifest: ParameterManifest) -> ValidationReport:
        """
        Validate that data and parameter manifest are compatible for workflow integration.
        
        Args:
            data: Input precipitation data
            manifest: Parameter manifest
            
        Returns:
            Validation report
        """
        report = ValidationReport(is_valid=True)
        
        try:
            # Validate data format
            if not isinstance(data, pd.Series):
                report.add_error("Data must be pandas Series")
            
            if not isinstance(data.index, pd.DatetimeIndex):
                report.add_warning("Data should have datetime index for optimal workflow integration")
            
            # Validate parameter manifest
            if not isinstance(manifest, ParameterManifest):
                report.add_error("Parameter manifest must be ParameterManifest instance")
            
            # Check parameter completeness
            missing_months = []
            for month in range(1, 13):
                if month not in manifest.overall_parameters:
                    missing_months.append(month)
            
            if missing_months:
                report.add_error(f"Missing parameters for months: {missing_months}")
            
            # Check metadata compatibility
            data_metadata = self.converter.extract_metadata_from_series(data)
            manifest_metadata = manifest.metadata
            
            # Validate temporal compatibility
            if 'temporal_range' in data_metadata and 'data_period' in manifest_metadata:
                data_start = data_metadata['temporal_range']['start']
                manifest_start = manifest_metadata['data_period'].get('start')
                
                if manifest_start and data_start != manifest_start:
                    report.add_warning("Data and manifest temporal ranges don't match")
            
            # Add success metrics
            report.metrics['data_length'] = len(data)
            report.metrics['parameter_months'] = len(manifest.overall_parameters)
            report.metrics['has_trend_analysis'] = manifest.trend_analysis is not None
            
        except Exception as e:
            report.add_error(f"Validation failed: {str(e)}")
        
        return report
    
    def optimize_workflow_performance(self, 
                                    data_size: int,
                                    analysis_complexity: str = 'standard') -> Dict[str, Any]:
        """
        Provide performance optimization recommendations for workflow execution.
        
        Args:
            data_size: Size of input data
            analysis_complexity: Complexity level ('simple', 'standard', 'comprehensive')
            
        Returns:
            Performance optimization recommendations
        """
        recommendations = {
            'caching_strategy': 'enabled',
            'memory_management': 'standard',
            'processing_approach': 'batch'
        }
        
        # Adjust based on data size
        if data_size > 100000:  # Large dataset
            recommendations.update({
                'memory_management': 'streaming',
                'processing_approach': 'chunked',
                'cache_size_limit': '500MB'
            })
        elif data_size < 1000:  # Small dataset
            recommendations.update({
                'caching_strategy': 'disabled',
                'memory_management': 'in_memory',
                'processing_approach': 'direct'
            })
        
        # Adjust based on complexity
        if analysis_complexity == 'comprehensive':
            recommendations.update({
                'parallel_processing': True,
                'intermediate_checkpoints': True,
                'result_compression': True
            })
        elif analysis_complexity == 'simple':
            recommendations.update({
                'parallel_processing': False,
                'intermediate_checkpoints': False,
                'fast_mode': True
            })
        
        return recommendations