"""
Advanced Analytics and Monitoring System for Toxo

This module implements production-grade analytics capabilities including:
- Real-time performance monitoring and alerting
- Predictive analytics and trend analysis
- Anomaly detection and automatic remediation
- Advanced metrics collection and visualization
- Performance optimization recommendations
- A/B testing framework
- Machine learning model performance analysis
- Resource utilization forecasting
"""

import asyncio
import numpy as np
import pandas as pd
import json
import time
import pickle
from typing import Dict, Any, List, Optional, Union, Callable, Tuple
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from enum import Enum
from pathlib import Path
from collections import defaultdict, deque
import threading
import logging
from contextlib import contextmanager

# ML libraries
from sklearn.ensemble import IsolationForest, RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import DBSCAN
from sklearn.decomposition import PCA
from sklearn.metrics import mean_squared_error, accuracy_score
import scipy.stats as stats
from scipy import signal

# Time series analysis
try:
    import statsmodels.api as sm
    from statsmodels.tsa.arima.model import ARIMA
    from statsmodels.tsa.seasonal import seasonal_decompose
    STATSMODELS_AVAILABLE = True
except ImportError:
    STATSMODELS_AVAILABLE = False

# Visualization (optional)
try:
    import plotly.graph_objects as go
    import plotly.express as px
    from plotly.subplots import make_subplots
    PLOTLY_AVAILABLE = True
except ImportError:
    PLOTLY_AVAILABLE = False

# Prometheus and OpenTelemetry
try:
    from prometheus_client import Counter, Histogram, Gauge, Info, CollectorRegistry, generate_latest
    PROMETHEUS_AVAILABLE = True
except ImportError:
    PROMETHEUS_AVAILABLE = False

# Toxo imports
from ..utils.logger import get_logger
from ..utils.exceptions import ToxoError, PerformanceWarning
from ..core.config import ToxoConfig


class MetricType(Enum):
    """Types of metrics to collect."""
    COUNTER = "counter"
    GAUGE = "gauge"
    HISTOGRAM = "histogram"
    SUMMARY = "summary"
    TIMER = "timer"


class AlertSeverity(Enum):
    """Alert severity levels."""
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


class AnomalyType(Enum):
    """Types of anomalies detected."""
    OUTLIER = "outlier"
    TREND_CHANGE = "trend_change"
    SEASONAL_DEVIATION = "seasonal_deviation"
    CONCEPT_DRIFT = "concept_drift"
    PERFORMANCE_DEGRADATION = "performance_degradation"


@dataclass
class MetricDefinition:
    """Definition of a metric to be collected."""
    name: str
    metric_type: MetricType
    description: str
    unit: str
    tags: Dict[str, str] = field(default_factory=dict)
    aggregation_window: int = 300  # seconds
    retention_period: int = 86400  # seconds (24 hours)
    alerting_rules: List[Dict[str, Any]] = field(default_factory=list)


@dataclass
class MetricDataPoint:
    """Individual metric data point."""
    metric_name: str
    value: float
    timestamp: datetime
    tags: Dict[str, str] = field(default_factory=dict)
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Alert:
    """System alert definition."""
    alert_id: str
    metric_name: str
    severity: AlertSeverity
    message: str
    threshold_value: float
    current_value: float
    triggered_at: datetime
    resolved_at: Optional[datetime] = None
    acknowledged: bool = False
    acknowledged_by: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class AnomalyDetection:
    """Anomaly detection result."""
    anomaly_id: str
    metric_name: str
    anomaly_type: AnomalyType
    confidence_score: float
    detected_at: datetime
    data_points: List[float]
    explanation: str
    recommended_actions: List[str] = field(default_factory=list)


@dataclass
class PerformanceAnalysis:
    """Performance analysis result."""
    analysis_id: str
    model_name: str
    analysis_type: str
    metrics: Dict[str, float]
    trends: Dict[str, str]
    recommendations: List[str]
    analyzed_at: datetime
    confidence: float


class TimeSeriesAnalyzer:
    """
    Advanced time series analysis for metrics.
    """
    
    def __init__(self):
        self.logger = get_logger(f"{__name__}.TimeSeriesAnalyzer")
        self.models: Dict[str, Any] = {}
        self.scalers: Dict[str, StandardScaler] = {}
    
    async def analyze_trend(
        self,
        data: List[float],
        timestamps: List[datetime],
        metric_name: str
    ) -> Dict[str, Any]:
        """Analyze trend in time series data."""
        try:
            if len(data) < 10:
                return {"trend": "insufficient_data", "confidence": 0.0}
            
            # Convert to numpy arrays
            y = np.array(data)
            x = np.arange(len(y))
            
            # Linear regression for trend
            slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
            
            # Determine trend direction
            if abs(slope) < std_err:
                trend = "stable"
            elif slope > 0:
                trend = "increasing"
            else:
                trend = "decreasing"
            
            # Calculate confidence
            confidence = abs(r_value)
            
            # Seasonal decomposition if enough data
            seasonal_info = {}
            if STATSMODELS_AVAILABLE and len(data) >= 20:
                try:
                    ts = pd.Series(data, index=pd.date_range(start=timestamps[0], periods=len(data), freq='T'))
                    decomposition = seasonal_decompose(ts, model='additive', period=min(len(data)//4, 10))
                    
                    seasonal_info = {
                        "seasonality_strength": float(np.var(decomposition.seasonal) / np.var(y)),
                        "trend_strength": float(np.var(decomposition.trend.dropna()) / np.var(y)),
                        "residual_variance": float(np.var(decomposition.resid.dropna()))
                    }
                except Exception as e:
                    self.logger.warning(f"Seasonal decomposition failed: {e}")
            
            return {
                "trend": trend,
                "slope": slope,
                "confidence": confidence,
                "r_squared": r_value ** 2,
                "p_value": p_value,
                "seasonal_info": seasonal_info,
                "data_points": len(data)
            }
            
        except Exception as e:
            self.logger.error(f"Trend analysis failed for {metric_name}: {e}")
            return {"trend": "error", "confidence": 0.0, "error": str(e)}
    
    async def forecast_values(
        self,
        data: List[float],
        forecast_horizon: int = 10,
        metric_name: str = "unknown"
    ) -> Dict[str, Any]:
        """Forecast future values using ARIMA model."""
        try:
            if not STATSMODELS_AVAILABLE:
                return {"error": "statsmodels not available"}
            
            if len(data) < 20:
                return {"error": "insufficient_data", "required": 20, "available": len(data)}
            
            # Prepare data
            ts = pd.Series(data)
            
            # Fit ARIMA model
            model = ARIMA(ts, order=(1, 1, 1))
            fitted_model = model.fit()
            
            # Generate forecast
            forecast = fitted_model.forecast(steps=forecast_horizon)
            forecast_ci = fitted_model.get_forecast(steps=forecast_horizon).conf_int()
            
            # Calculate prediction intervals
            lower_bound = forecast_ci.iloc[:, 0].tolist()
            upper_bound = forecast_ci.iloc[:, 1].tolist()
            
            return {
                "forecast": forecast.tolist(),
                "lower_bound": lower_bound,
                "upper_bound": upper_bound,
                "model_aic": fitted_model.aic,
                "model_params": fitted_model.params.to_dict(),
                "forecast_horizon": forecast_horizon
            }
            
        except Exception as e:
            self.logger.error(f"Forecasting failed for {metric_name}: {e}")
            return {"error": str(e)}
    
    async def detect_change_points(
        self,
        data: List[float],
        metric_name: str = "unknown"
    ) -> List[int]:
        """Detect change points in time series data."""
        try:
            if len(data) < 10:
                return []
            
            # Use CUSUM algorithm for change point detection
            data_array = np.array(data)
            
            # Calculate cumulative sum of deviations from mean
            mean_val = np.mean(data_array)
            cusum = np.cumsum(data_array - mean_val)
            
            # Find significant changes
            cusum_std = np.std(cusum)
            threshold = 2 * cusum_std
            
            change_points = []
            for i in range(1, len(cusum)):
                if abs(cusum[i] - cusum[i-1]) > threshold:
                    change_points.append(i)
            
            return change_points
            
        except Exception as e:
            self.logger.error(f"Change point detection failed for {metric_name}: {e}")
            return []


class AnomalyDetector:
    """
    Advanced anomaly detection system using multiple algorithms.
    """
    
    def __init__(self, contamination: float = 0.1):
        self.contamination = contamination
        self.logger = get_logger(f"{__name__}.AnomalyDetector")
        self.models: Dict[str, Any] = {}
        self.thresholds: Dict[str, Dict[str, float]] = {}
    
    async def detect_anomalies(
        self,
        metric_name: str,
        data: List[float],
        timestamps: List[datetime],
        method: str = "isolation_forest"
    ) -> List[AnomalyDetection]:
        """
        Detect anomalies in metric data using specified method.
        
        Args:
            metric_name: Name of the metric
            data: Time series data
            timestamps: Corresponding timestamps
            method: Detection method ('isolation_forest', 'statistical', 'ensemble')
            
        Returns:
            List of detected anomalies
        """
        try:
            if len(data) < 10:
                return []
            
            anomalies = []
            
            if method == "isolation_forest":
                anomalies.extend(await self._isolation_forest_detection(metric_name, data, timestamps))
            elif method == "statistical":
                anomalies.extend(await self._statistical_detection(metric_name, data, timestamps))
            elif method == "ensemble":
                # Combine multiple methods
                if_anomalies = await self._isolation_forest_detection(metric_name, data, timestamps)
                stat_anomalies = await self._statistical_detection(metric_name, data, timestamps)
                anomalies.extend(if_anomalies)
                anomalies.extend(stat_anomalies)
            
            return anomalies
            
        except Exception as e:
            self.logger.error(f"Anomaly detection failed for {metric_name}: {e}")
            return []
    
    async def _isolation_forest_detection(
        self,
        metric_name: str,
        data: List[float],
        timestamps: List[datetime]
    ) -> List[AnomalyDetection]:
        """Detect anomalies using Isolation Forest."""
        try:
            # Prepare data
            X = np.array(data).reshape(-1, 1)
            
            # Initialize or get existing model
            if metric_name not in self.models:
                self.models[metric_name] = IsolationForest(
                    contamination=self.contamination,
                    random_state=42
                )
            
            model = self.models[metric_name]
            
            # Fit and predict
            model.fit(X)
            predictions = model.predict(X)
            anomaly_scores = model.score_samples(X)
            
            # Find anomalies
            anomalies = []
            for i, (pred, score) in enumerate(zip(predictions, anomaly_scores)):
                if pred == -1:  # Anomaly
                    anomaly = AnomalyDetection(
                        anomaly_id=f"{metric_name}_isolation_{timestamps[i].timestamp()}",
                        metric_name=metric_name,
                        anomaly_type=AnomalyType.OUTLIER,
                        confidence_score=abs(score),
                        detected_at=timestamps[i],
                        data_points=[data[i]],
                        explanation=f"Isolation Forest detected outlier value {data[i]:.3f} with score {score:.3f}",
                        recommended_actions=[
                            "Investigate data quality",
                            "Check for external factors",
                            "Verify sensor readings"
                        ]
                    )
                    anomalies.append(anomaly)
            
            return anomalies
            
        except Exception as e:
            self.logger.error(f"Isolation Forest detection failed: {e}")
            return []
    
    async def _statistical_detection(
        self,
        metric_name: str,
        data: List[float],
        timestamps: List[datetime]
    ) -> List[AnomalyDetection]:
        """Detect anomalies using statistical methods."""
        try:
            data_array = np.array(data)
            
            # Calculate statistical thresholds
            mean_val = np.mean(data_array)
            std_val = np.std(data_array)
            
            # Z-score based detection
            z_scores = np.abs(stats.zscore(data_array))
            z_threshold = 3.0
            
            # Interquartile range based detection
            q1, q3 = np.percentile(data_array, [25, 75])
            iqr = q3 - q1
            iqr_lower = q1 - 1.5 * iqr
            iqr_upper = q3 + 1.5 * iqr
            
            anomalies = []
            
            for i, (value, z_score) in enumerate(zip(data, z_scores)):
                is_z_anomaly = z_score > z_threshold
                is_iqr_anomaly = value < iqr_lower or value > iqr_upper
                
                if is_z_anomaly or is_iqr_anomaly:
                    detection_method = []
                    if is_z_anomaly:
                        detection_method.append(f"Z-score: {z_score:.3f}")
                    if is_iqr_anomaly:
                        detection_method.append("IQR outlier")
                    
                    anomaly = AnomalyDetection(
                        anomaly_id=f"{metric_name}_statistical_{timestamps[i].timestamp()}",
                        metric_name=metric_name,
                        anomaly_type=AnomalyType.OUTLIER,
                        confidence_score=min(z_score / z_threshold, 1.0),
                        detected_at=timestamps[i],
                        data_points=[value],
                        explanation=f"Statistical outlier detected: {', '.join(detection_method)}",
                        recommended_actions=[
                            "Review data collection process",
                            "Check for measurement errors",
                            "Analyze contextual factors"
                        ]
                    )
                    anomalies.append(anomaly)
            
            return anomalies
            
        except Exception as e:
            self.logger.error(f"Statistical detection failed: {e}")
            return []


class PerformanceAnalyzer:
    """
    Advanced performance analysis for ML models and systems.
    """
    
    def __init__(self):
        self.logger = get_logger(f"{__name__}.PerformanceAnalyzer")
        self.baseline_metrics: Dict[str, Dict[str, float]] = {}
        self.performance_history: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
    
    async def analyze_model_performance(
        self,
        model_name: str,
        metrics: Dict[str, float],
        timestamp: Optional[datetime] = None
    ) -> PerformanceAnalysis:
        """
        Analyze model performance and provide recommendations.
        
        Args:
            model_name: Name of the model
            metrics: Performance metrics
            timestamp: When the metrics were recorded
            
        Returns:
            Performance analysis result
        """
        try:
            timestamp = timestamp or datetime.now()
            
            # Store current metrics
            self.performance_history[model_name].append({
                "metrics": metrics,
                "timestamp": timestamp
            })
            
            # Analyze trends
            trends = await self._analyze_performance_trends(model_name)
            
            # Generate recommendations
            recommendations = await self._generate_performance_recommendations(model_name, metrics, trends)
            
            # Calculate confidence
            confidence = await self._calculate_analysis_confidence(model_name)
            
            analysis = PerformanceAnalysis(
                analysis_id=f"{model_name}_{timestamp.timestamp()}",
                model_name=model_name,
                analysis_type="comprehensive",
                metrics=metrics,
                trends=trends,
                recommendations=recommendations,
                analyzed_at=timestamp,
                confidence=confidence
            )
            
            return analysis
            
        except Exception as e:
            self.logger.error(f"Performance analysis failed for {model_name}: {e}")
            raise ToxoError(f"Analysis failed: {e}")
    
    async def _analyze_performance_trends(self, model_name: str) -> Dict[str, str]:
        """Analyze performance trends over time."""
        trends = {}
        
        if model_name not in self.performance_history:
            return {"overall": "no_data"}
        
        history = self.performance_history[model_name]
        if len(history) < 3:
            return {"overall": "insufficient_data"}
        
        # Analyze each metric
        for metric_name in history[0]["metrics"].keys():
            values = [entry["metrics"][metric_name] for entry in history[-10:]]  # Last 10 entries
            
            if len(values) >= 3:
                # Simple trend analysis
                slope, _, r_value, _, _ = stats.linregress(range(len(values)), values)
                
                if abs(slope) < 0.001:
                    trends[metric_name] = "stable"
                elif slope > 0:
                    trends[metric_name] = "improving" if metric_name in ["accuracy", "f1_score", "precision", "recall"] else "degrading"
                else:
                    trends[metric_name] = "degrading" if metric_name in ["accuracy", "f1_score", "precision", "recall"] else "improving"
        
        return trends
    
    async def _generate_performance_recommendations(
        self,
        model_name: str,
        current_metrics: Dict[str, float],
        trends: Dict[str, str]
    ) -> List[str]:
        """Generate performance improvement recommendations."""
        recommendations = []
        
        # Check for performance degradation
        degrading_metrics = [metric for metric, trend in trends.items() if trend == "degrading"]
        
        if degrading_metrics:
            recommendations.append(f"Performance degrading in: {', '.join(degrading_metrics)}")
            recommendations.append("Consider retraining the model with recent data")
            recommendations.append("Review data quality and feature engineering")
        
        # Check specific metric thresholds
        if "accuracy" in current_metrics and current_metrics["accuracy"] < 0.8:
            recommendations.append("Accuracy below 80% - investigate model architecture")
        
        if "latency" in current_metrics and current_metrics["latency"] > 1000:
            recommendations.append("High latency detected - optimize inference pipeline")
        
        if "memory_usage" in current_metrics and current_metrics["memory_usage"] > 0.8:
            recommendations.append("High memory usage - consider model compression")
        
        # Resource optimization recommendations
        if len(self.performance_history[model_name]) > 5:
            recent_latency = [entry["metrics"].get("latency", 0) for entry in self.performance_history[model_name][-5:]]
            if recent_latency and np.std(recent_latency) > np.mean(recent_latency) * 0.3:
                recommendations.append("High latency variance - investigate load balancing")
        
        if not recommendations:
            recommendations.append("Performance looks good - continue monitoring")
        
        return recommendations
    
    async def _calculate_analysis_confidence(self, model_name: str) -> float:
        """Calculate confidence in the analysis based on data quality."""
        if model_name not in self.performance_history:
            return 0.0
        
        history_length = len(self.performance_history[model_name])
        
        # Base confidence on amount of historical data
        if history_length < 3:
            return 0.3
        elif history_length < 10:
            return 0.6
        elif history_length < 50:
            return 0.8
        else:
            return 0.95


class AlertManager:
    """
    Advanced alerting system with intelligent threshold management.
    """
    
    def __init__(self):
        self.logger = get_logger(f"{__name__}.AlertManager")
        self.active_alerts: Dict[str, Alert] = {}
        self.alert_history: List[Alert] = []
        self.alert_callbacks: Dict[str, Callable] = {}
        self.dynamic_thresholds: Dict[str, Dict[str, float]] = {}
    
    async def check_alerts(
        self,
        metric_name: str,
        current_value: float,
        timestamp: Optional[datetime] = None
    ) -> List[Alert]:
        """
        Check if current metric value triggers any alerts.
        
        Args:
            metric_name: Name of the metric
            current_value: Current metric value
            timestamp: When the metric was recorded
            
        Returns:
            List of triggered alerts
        """
        timestamp = timestamp or datetime.now()
        triggered_alerts = []
        
        # Get dynamic thresholds for this metric
        thresholds = self.dynamic_thresholds.get(metric_name, {})
        
        # Check each threshold
        for threshold_name, threshold_value in thresholds.items():
            alert_key = f"{metric_name}_{threshold_name}"
            
            # Determine if alert should be triggered
            should_trigger = False
            severity = AlertSeverity.INFO
            
            if threshold_name == "high_critical" and current_value > threshold_value:
                should_trigger = True
                severity = AlertSeverity.CRITICAL
            elif threshold_name == "high_warning" and current_value > threshold_value:
                should_trigger = True
                severity = AlertSeverity.WARNING
            elif threshold_name == "low_critical" and current_value < threshold_value:
                should_trigger = True
                severity = AlertSeverity.CRITICAL
            elif threshold_name == "low_warning" and current_value < threshold_value:
                should_trigger = True
                severity = AlertSeverity.WARNING
            
            if should_trigger and alert_key not in self.active_alerts:
                # Create new alert
                alert = Alert(
                    alert_id=f"alert_{timestamp.timestamp()}_{metric_name}",
                    metric_name=metric_name,
                    severity=severity,
                    message=f"{metric_name} {threshold_name}: {current_value:.3f} (threshold: {threshold_value:.3f})",
                    threshold_value=threshold_value,
                    current_value=current_value,
                    triggered_at=timestamp
                )
                
                self.active_alerts[alert_key] = alert
                self.alert_history.append(alert)
                triggered_alerts.append(alert)
                
                # Execute callbacks
                await self._execute_alert_callbacks(alert)
                
            elif not should_trigger and alert_key in self.active_alerts:
                # Resolve existing alert
                alert = self.active_alerts[alert_key]
                alert.resolved_at = timestamp
                del self.active_alerts[alert_key]
        
        return triggered_alerts
    
    async def _execute_alert_callbacks(self, alert: Alert):
        """Execute registered callbacks for an alert."""
        for callback_name, callback_func in self.alert_callbacks.items():
            try:
                if asyncio.iscoroutinefunction(callback_func):
                    await callback_func(alert)
                else:
                    callback_func(alert)
            except Exception as e:
                self.logger.error(f"Alert callback {callback_name} failed: {e}")
    
    def register_alert_callback(self, name: str, callback: Callable):
        """Register a callback function for alerts."""
        self.alert_callbacks[name] = callback
    
    async def update_dynamic_thresholds(
        self,
        metric_name: str,
        data_history: List[float]
    ):
        """Update dynamic thresholds based on historical data."""
        if len(data_history) < 10:
            return
        
        data_array = np.array(data_history)
        
        # Calculate statistical thresholds
        mean_val = np.mean(data_array)
        std_val = np.std(data_array)
        
        # Percentile-based thresholds
        p95 = np.percentile(data_array, 95)
        p99 = np.percentile(data_array, 99)
        p5 = np.percentile(data_array, 5)
        p1 = np.percentile(data_array, 1)
        
        self.dynamic_thresholds[metric_name] = {
            "high_warning": p95,
            "high_critical": p99,
            "low_warning": p5,
            "low_critical": p1,
            "mean": mean_val,
            "std": std_val
        }


class AdvancedAnalyticsManager:
    """
    Main manager for advanced analytics and monitoring in Toxo.
    Orchestrates all analytics components and provides unified API.
    """
    
    def __init__(self, config: Optional[ToxoConfig] = None):
        self.config = config or ToxoConfig()
        self.logger = get_logger(__name__)
        
        # Core components
        self.time_series_analyzer = TimeSeriesAnalyzer()
        self.anomaly_detector = AnomalyDetector()
        self.performance_analyzer = PerformanceAnalyzer()
        self.alert_manager = AlertManager()
        
        # Data storage
        self.metrics_data: Dict[str, deque] = defaultdict(lambda: deque(maxlen=10000))
        self.metric_definitions: Dict[str, MetricDefinition] = {}
        
        # Real-time processing
        self.processing_thread: Optional[threading.Thread] = None
        self.running = False
        
        # Prometheus integration
        if PROMETHEUS_AVAILABLE:
            self.registry = CollectorRegistry()
            self._init_prometheus_metrics()
        
        self.logger.info("Advanced Analytics Manager initialized")
    
    def _init_prometheus_metrics(self):
        """Initialize Prometheus metrics."""
        self.prom_analytics_operations = Counter(
            'toxo_analytics_operations_total',
            'Total analytics operations performed',
            ['operation_type', 'status'],
            registry=self.registry
        )
        
        self.prom_anomalies_detected = Counter(
            'toxo_anomalies_detected_total',
            'Total anomalies detected',
            ['metric_name', 'anomaly_type'],
            registry=self.registry
        )
        
        self.prom_active_alerts = Gauge(
            'toxo_active_alerts',
            'Number of active alerts',
            ['severity'],
            registry=self.registry
        )
    
    async def register_metric(self, metric_def: MetricDefinition):
        """Register a new metric for monitoring."""
        self.metric_definitions[metric_def.name] = metric_def
        self.logger.info(f"Registered metric: {metric_def.name}")
    
    async def record_metric(
        self,
        metric_name: str,
        value: float,
        timestamp: Optional[datetime] = None,
        tags: Optional[Dict[str, str]] = None
    ):
        """Record a metric value."""
        timestamp = timestamp or datetime.now()
        tags = tags or {}
        
        data_point = MetricDataPoint(
            metric_name=metric_name,
            value=value,
            timestamp=timestamp,
            tags=tags
        )
        
        self.metrics_data[metric_name].append(data_point)
        
        # Update Prometheus metrics
        if PROMETHEUS_AVAILABLE:
            self.prom_analytics_operations.labels(
                operation_type="metric_recorded",
                status="success"
            ).inc()
    
    async def analyze_metric(
        self,
        metric_name: str,
        analysis_type: str = "comprehensive"
    ) -> Dict[str, Any]:
        """
        Perform comprehensive analysis on a metric.
        
        Args:
            metric_name: Name of the metric to analyze
            analysis_type: Type of analysis to perform
            
        Returns:
            Analysis results
        """
        try:
            if metric_name not in self.metrics_data:
                return {"error": f"Metric {metric_name} not found"}
            
            data_points = list(self.metrics_data[metric_name])
            if not data_points:
                return {"error": "No data available"}
            
            values = [dp.value for dp in data_points]
            timestamps = [dp.timestamp for dp in data_points]
            
            analysis_result = {
                "metric_name": metric_name,
                "data_points": len(values),
                "time_range": {
                    "start": timestamps[0].isoformat() if timestamps else None,
                    "end": timestamps[-1].isoformat() if timestamps else None
                }
            }
            
            # Trend analysis
            if analysis_type in ["comprehensive", "trend"]:
                trend_analysis = await self.time_series_analyzer.analyze_trend(
                    values, timestamps, metric_name
                )
                analysis_result["trend_analysis"] = trend_analysis
            
            # Anomaly detection
            if analysis_type in ["comprehensive", "anomaly"]:
                anomalies = await self.anomaly_detector.detect_anomalies(
                    metric_name, values, timestamps, method="ensemble"
                )
                analysis_result["anomalies"] = [asdict(anomaly) for anomaly in anomalies]
                
                # Update Prometheus metrics
                if PROMETHEUS_AVAILABLE:
                    for anomaly in anomalies:
                        self.prom_anomalies_detected.labels(
                            metric_name=metric_name,
                            anomaly_type=anomaly.anomaly_type.value
                        ).inc()
            
            # Forecasting
            if analysis_type in ["comprehensive", "forecast"]:
                forecast_result = await self.time_series_analyzer.forecast_values(
                    values, forecast_horizon=10, metric_name=metric_name
                )
                analysis_result["forecast"] = forecast_result
            
            # Change point detection
            if analysis_type in ["comprehensive", "change_points"]:
                change_points = await self.time_series_analyzer.detect_change_points(
                    values, metric_name
                )
                analysis_result["change_points"] = change_points
            
            # Basic statistics
            analysis_result["statistics"] = {
                "mean": float(np.mean(values)),
                "std": float(np.std(values)),
                "min": float(np.min(values)),
                "max": float(np.max(values)),
                "median": float(np.median(values)),
                "p95": float(np.percentile(values, 95)),
                "p99": float(np.percentile(values, 99))
            }
            
            return analysis_result
            
        except Exception as e:
            self.logger.error(f"Metric analysis failed for {metric_name}: {e}")
            if PROMETHEUS_AVAILABLE:
                self.prom_analytics_operations.labels(
                    operation_type="analysis",
                    status="error"
                ).inc()
            raise ToxoError(f"Analysis failed: {e}")
    
    async def get_dashboard_data(self) -> Dict[str, Any]:
        """Get data for analytics dashboard."""
        dashboard_data = {
            "overview": {
                "total_metrics": len(self.metrics_data),
                "active_alerts": len(self.alert_manager.active_alerts),
                "total_data_points": sum(len(data) for data in self.metrics_data.values()),
                "timestamp": datetime.now().isoformat()
            },
            "metrics": {},
            "alerts": [asdict(alert) for alert in self.alert_manager.active_alerts.values()],
            "recent_anomalies": []
        }
        
        # Get recent data for each metric
        for metric_name, data_points in self.metrics_data.items():
            if data_points:
                recent_data = list(data_points)[-100:]  # Last 100 points
                dashboard_data["metrics"][metric_name] = {
                    "values": [dp.value for dp in recent_data],
                    "timestamps": [dp.timestamp.isoformat() for dp in recent_data],
                    "current_value": recent_data[-1].value if recent_data else None
                }
        
        return dashboard_data
    
    async def create_visualization(
        self,
        metric_name: str,
        chart_type: str = "line"
    ) -> Optional[str]:
        """Create visualization for a metric (requires plotly)."""
        if not PLOTLY_AVAILABLE:
            self.logger.warning("Plotly not available for visualization")
            return None
        
        if metric_name not in self.metrics_data:
            return None
        
        data_points = list(self.metrics_data[metric_name])
        if not data_points:
            return None
        
        values = [dp.value for dp in data_points]
        timestamps = [dp.timestamp for dp in data_points]
        
        if chart_type == "line":
            fig = go.Figure()
            fig.add_trace(go.Scatter(
                x=timestamps,
                y=values,
                mode='lines+markers',
                name=metric_name,
                line=dict(color='blue', width=2)
            ))
            
            fig.update_layout(
                title=f'{metric_name} Over Time',
                xaxis_title='Time',
                yaxis_title='Value',
                hovermode='x unified'
            )
            
            return fig.to_html()
        
        return None
    
    async def start_real_time_processing(self):
        """Start real-time analytics processing."""
        self.running = True
        self.processing_thread = threading.Thread(target=self._processing_loop)
        self.processing_thread.start()
        self.logger.info("Started real-time analytics processing")
    
    async def stop_real_time_processing(self):
        """Stop real-time analytics processing."""
        self.running = False
        if self.processing_thread:
            self.processing_thread.join(timeout=10)
        self.logger.info("Stopped real-time analytics processing")
    
    def _processing_loop(self):
        """Main processing loop for real-time analytics."""
        while self.running:
            try:
                # Process alerts
                asyncio.run(self._process_alerts())
                
                # Update dynamic thresholds
                asyncio.run(self._update_thresholds())
                
                # Clean up old data
                self._cleanup_old_data()
                
                time.sleep(30)  # Process every 30 seconds
                
            except Exception as e:
                self.logger.error(f"Error in processing loop: {e}")
                time.sleep(60)
    
    async def _process_alerts(self):
        """Process alerts for all metrics."""
        for metric_name, data_points in self.metrics_data.items():
            if data_points:
                latest_point = data_points[-1]
                await self.alert_manager.check_alerts(
                    metric_name,
                    latest_point.value,
                    latest_point.timestamp
                )
    
    async def _update_thresholds(self):
        """Update dynamic thresholds for all metrics."""
        for metric_name, data_points in self.metrics_data.items():
            if len(data_points) >= 10:
                values = [dp.value for dp in data_points]
                await self.alert_manager.update_dynamic_thresholds(
                    metric_name,
                    values
                )
    
    def _cleanup_old_data(self):
        """Clean up old data points based on retention policies."""
        current_time = datetime.now()
        
        for metric_name, data_points in self.metrics_data.items():
            metric_def = self.metric_definitions.get(metric_name)
            if metric_def:
                retention_period = timedelta(seconds=metric_def.retention_period)
                cutoff_time = current_time - retention_period
                
                # Remove old data points
                while data_points and data_points[0].timestamp < cutoff_time:
                    data_points.popleft()
    
    async def export_metrics(self, format: str = "prometheus") -> str:
        """Export metrics in specified format."""
        if format == "prometheus" and PROMETHEUS_AVAILABLE:
            return generate_latest(self.registry).decode('utf-8')
        elif format == "json":
            export_data = {}
            for metric_name, data_points in self.metrics_data.items():
                export_data[metric_name] = [
                    {
                        "value": dp.value,
                        "timestamp": dp.timestamp.isoformat(),
                        "tags": dp.tags
                    }
                    for dp in data_points
                ]
            return json.dumps(export_data, indent=2)
        else:
            return ""
    
    def get_state(self) -> Dict[str, Any]:
        """Get the current state for serialization."""
        return {
            "config_available": self.config is not None,
            "time_series_analyzer_available": getattr(self, 'time_series_analyzer', None) is not None,
            "anomaly_detector_available": getattr(self, 'anomaly_detector', None) is not None,
            "performance_analyzer_available": getattr(self, 'performance_analyzer', None) is not None,
            "alert_manager_available": getattr(self, 'alert_manager', None) is not None,
            "metrics_count": len(getattr(self, 'metrics_data', {})),
            "metric_definitions_count": len(getattr(self, 'metric_definitions', {})),
            "running": getattr(self, 'running', False),
            "system_type": "AdvancedAnalyticsManager"
        }


# Factory function
def create_advanced_analytics_manager(config: Optional[ToxoConfig] = None) -> AdvancedAnalyticsManager:
    """Create and return an Advanced Analytics Manager instance."""
    return AdvancedAnalyticsManager(config) 