//! Drift Learning System
//!
//! This module extends the DataDriftEngine with learning capabilities that allow
//! mocks to gradually learn from recorded traffic and adapt their behavior.
//!
//! Features:
//! - Traffic pattern learning from recorded requests
//! - Persona behavior adaptation based on request patterns
//! - Configurable learning rate and sensitivity
//! - Opt-in per persona/endpoint learning

use crate::drift::{DataDriftConfig, DataDriftEngine};
use crate::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;

/// Learning configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningConfig {
    /// Enable drift learning
    #[serde(default)]
    pub enabled: bool,

    /// Learning mode
    #[serde(default)]
    pub mode: LearningMode,

    /// Learning rate (0.0 to 1.0) - how quickly mocks learn from patterns
    #[serde(default = "default_learning_rate")]
    pub sensitivity: f64,

    /// Decay rate (0.0 to 1.0) - drift resets if upstream patterns reverse
    #[serde(default = "default_decay_rate")]
    pub decay: f64,

    /// Minimum number of samples before learning starts
    #[serde(default = "default_min_samples")]
    pub min_samples: usize,

    /// Update interval for learning
    #[serde(default = "default_update_interval")]
    pub update_interval: Duration,

    /// Enable persona adaptation
    #[serde(default = "default_true")]
    pub persona_adaptation: bool,

    /// Enable traffic pattern mirroring
    #[serde(default = "default_true")]
    pub traffic_mirroring: bool,

    /// Per-endpoint opt-in learning (endpoint pattern -> enabled)
    #[serde(default)]
    pub endpoint_learning: HashMap<String, bool>,

    /// Per-persona opt-in learning (persona_id -> enabled)
    #[serde(default)]
    pub persona_learning: HashMap<String, bool>,
}

fn default_learning_rate() -> f64 {
    0.2 // 20% learning rate - conservative default
}

fn default_decay_rate() -> f64 {
    0.05 // 5% decay rate
}

fn default_min_samples() -> usize {
    10 // Need at least 10 samples before learning
}

fn default_update_interval() -> Duration {
    Duration::from_secs(60) // Update every minute
}

fn default_true() -> bool {
    true
}

impl Default for LearningConfig {
    fn default() -> Self {
        Self {
            enabled: false, // Opt-in by default
            mode: LearningMode::Behavioral,
            sensitivity: default_learning_rate(),
            decay: default_decay_rate(),
            min_samples: default_min_samples(),
            update_interval: default_update_interval(),
            persona_adaptation: true,
            traffic_mirroring: true,
            endpoint_learning: HashMap::new(),
            persona_learning: HashMap::new(),
        }
    }
}

/// Learning mode
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum LearningMode {
    /// Behavioral learning - adapts to behavior patterns
    #[default]
    Behavioral,
    /// Statistical learning - adapts to statistical patterns
    Statistical,
    /// Hybrid - combines behavioral and statistical
    Hybrid,
}

/// Drift Learning Engine
///
/// Extends DataDriftEngine with learning capabilities from recorded traffic.
pub struct DriftLearningEngine {
    /// Base drift engine
    drift_engine: DataDriftEngine,
    /// Learning configuration
    learning_config: LearningConfig,
    /// Traffic pattern learner (constructed for config validation; wiring pending)
    _traffic_learner: Option<Arc<RwLock<TrafficPatternLearner>>>,
    /// Persona behavior learner (constructed for config validation; wiring pending)
    _persona_learner: Option<Arc<RwLock<PersonaBehaviorLearner>>>,
    /// Learned patterns cache
    learned_patterns: Arc<RwLock<HashMap<String, LearnedPattern>>>,
}

/// Learned pattern from traffic analysis
#[derive(Debug, Clone)]
pub struct LearnedPattern {
    /// Pattern identifier
    pub pattern_id: String,
    /// Pattern type
    pub pattern_type: PatternType,
    /// Learned parameters
    pub parameters: HashMap<String, Value>,
    /// Confidence score (0.0 to 1.0)
    pub confidence: f64,
    /// Sample count used for learning
    pub sample_count: usize,
    /// Last updated timestamp
    pub last_updated: chrono::DateTime<chrono::Utc>,
}

/// Pattern type
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatternType {
    /// Latency pattern
    Latency,
    /// Error rate pattern
    ErrorRate,
    /// Request sequence pattern
    RequestSequence,
    /// Persona behavior pattern
    PersonaBehavior,
}

impl DriftLearningEngine {
    /// Create a new drift learning engine
    pub fn new(drift_config: DataDriftConfig, learning_config: LearningConfig) -> Result<Self> {
        let drift_engine = DataDriftEngine::new(drift_config)?;

        let traffic_learner = if learning_config.traffic_mirroring {
            Some(Arc::new(RwLock::new(TrafficPatternLearner::new(learning_config.clone())?)))
        } else {
            None
        };

        let persona_learner = if learning_config.persona_adaptation {
            Some(Arc::new(RwLock::new(PersonaBehaviorLearner::new(learning_config.clone())?)))
        } else {
            None
        };

        Ok(Self {
            drift_engine,
            learning_config,
            _traffic_learner: traffic_learner,
            _persona_learner: persona_learner,
            learned_patterns: Arc::new(RwLock::new(HashMap::new())),
        })
    }

    /// Get the base drift engine
    pub fn drift_engine(&self) -> &DataDriftEngine {
        &self.drift_engine
    }

    /// Get learning configuration
    pub fn learning_config(&self) -> &LearningConfig {
        &self.learning_config
    }

    /// Update learning configuration
    pub fn update_learning_config(&mut self, config: LearningConfig) -> Result<()> {
        self.learning_config = config;
        Ok(())
    }

    /// Get learned patterns
    pub async fn get_learned_patterns(&self) -> HashMap<String, LearnedPattern> {
        self.learned_patterns.read().await.clone()
    }

    /// Apply drift with learning
    pub async fn apply_drift_with_learning(&self, data: Value) -> Result<Value> {
        // First apply base drift
        let mut data = self.drift_engine.apply_drift(data).await?;

        // Then apply learned patterns if learning is enabled
        if !self.learning_config.enabled {
            return Ok(data);
        }

        // Apply learned patterns
        let patterns = self.learned_patterns.read().await;
        for (_pattern_id, pattern) in patterns.iter() {
            // Check if pattern should be applied based on confidence and decay
            if pattern.confidence < 0.5 {
                continue; // Low confidence, skip
            }

            // Apply pattern based on type
            match pattern.pattern_type {
                PatternType::Latency => {
                    // Latency patterns are handled separately
                }
                PatternType::ErrorRate => {
                    // Error rate patterns are handled separately
                }
                PatternType::RequestSequence => {
                    // Request sequence patterns affect persona behavior
                }
                PatternType::PersonaBehavior => {
                    // Persona behavior patterns affect data generation
                    if let Some(obj) = data.as_object_mut() {
                        for (key, value) in &pattern.parameters {
                            if let Some(existing) = obj.get(key) {
                                // Blend learned value with existing value
                                let blended =
                                    self.blend_values(existing, value, pattern.confidence)?;
                                obj.insert(key.clone(), blended);
                            }
                        }
                    }
                }
            }
        }

        Ok(data)
    }

    /// Blend two values based on confidence
    fn blend_values(&self, existing: &Value, learned: &Value, confidence: f64) -> Result<Value> {
        // Simple blending: existing * (1 - confidence * sensitivity) + learned * (confidence * sensitivity)
        let weight = confidence * self.learning_config.sensitivity;

        match (existing, learned) {
            (Value::Number(existing_num), Value::Number(learned_num)) => {
                if let (Some(existing_f64), Some(learned_f64)) =
                    (existing_num.as_f64(), learned_num.as_f64())
                {
                    let blended = existing_f64 * (1.0 - weight) + learned_f64 * weight;
                    Ok(Value::from(blended))
                } else {
                    Ok(existing.clone())
                }
            }
            _ => Ok(existing.clone()), // For non-numeric, keep existing
        }
    }
}

/// Traffic Pattern Learner
///
/// Analyzes recorded traffic to detect patterns and trends.
pub struct TrafficPatternLearner {
    /// Learning configuration (reserved for future per-learner config checks)
    _config: LearningConfig,
}

impl TrafficPatternLearner {
    /// Create a new traffic pattern learner
    pub fn new(config: LearningConfig) -> Result<Self> {
        Ok(Self { _config: config })
    }

    /// Analyze traffic patterns from recorded requests.
    ///
    /// Accepts JSON values with fields: `method`, `path`, `duration_ms`, `status_code`, `trace_id`.
    /// Returns detected latency, error rate, and sequence patterns.
    pub async fn analyze_traffic_patterns(
        &mut self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        let mut patterns = Vec::new();
        patterns.extend(self.detect_latency_patterns_from_requests(requests).await?);
        patterns.extend(self.detect_error_patterns_from_requests(requests).await?);
        patterns.extend(self.detect_sequence_patterns_from_requests(requests).await?);
        Ok(patterns)
    }

    /// Detect latency patterns from recorded request data.
    ///
    /// Each request value should contain `method` (string), `path` (string),
    /// and `duration_ms` (integer) fields.
    pub async fn detect_latency_patterns_from_requests(
        &self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        use chrono::Utc;

        let mut endpoint_latencies: HashMap<String, Vec<i64>> = HashMap::new();

        for request in requests {
            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
            let duration = request.get("duration_ms").and_then(|v| v.as_i64());

            if let Some(duration_ms) = duration {
                let key = format!("{} {}", method, path);
                endpoint_latencies.entry(key).or_default().push(duration_ms);
            }
        }

        let mut patterns = Vec::new();

        for (endpoint_key, latencies) in endpoint_latencies {
            if latencies.len() < 10 {
                continue;
            }

            let sum: i64 = latencies.iter().sum();
            let count = latencies.len();
            let avg_latency = sum as f64 / count as f64;

            let mut sorted = latencies.clone();
            sorted.sort();
            let p50 = sorted[count / 2];
            let p95 = sorted[(count * 95) / 100];
            let p99 = sorted[(count * 99) / 100];

            let recent_avg = if latencies.len() >= 20 {
                let recent: Vec<i64> = latencies.iter().rev().take(10).copied().collect();
                let recent_sum: i64 = recent.iter().sum();
                recent_sum as f64 / recent.len() as f64
            } else {
                avg_latency
            };

            let latency_trend = if recent_avg > avg_latency * 1.2 {
                "increasing"
            } else if recent_avg < avg_latency * 0.8 {
                "decreasing"
            } else {
                "stable"
            };

            if p99 > p50 * 2 || latency_trend != "stable" {
                let mut parameters = HashMap::new();
                parameters.insert("endpoint".to_string(), serde_json::json!(endpoint_key));
                parameters.insert("avg_latency_ms".to_string(), serde_json::json!(avg_latency));
                parameters.insert("p50_ms".to_string(), serde_json::json!(p50));
                parameters.insert("p95_ms".to_string(), serde_json::json!(p95));
                parameters.insert("p99_ms".to_string(), serde_json::json!(p99));
                parameters.insert("sample_count".to_string(), serde_json::json!(count));
                parameters.insert("trend".to_string(), serde_json::json!(latency_trend));

                let confidence = (count as f64 / 100.0).min(1.0);

                patterns.push(LearnedPattern {
                    pattern_id: format!("latency_{}", endpoint_key.replace(['/', ' '], "_")),
                    pattern_type: PatternType::Latency,
                    parameters,
                    confidence,
                    sample_count: count,
                    last_updated: Utc::now(),
                });
            }
        }

        Ok(patterns)
    }

    /// Detect error rate patterns from recorded request data.
    ///
    /// Each request value should contain `method` (string), `path` (string),
    /// and `status_code` (integer) fields.
    async fn detect_error_patterns_from_requests(
        &self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        use chrono::Utc;

        let mut endpoint_errors: HashMap<String, (usize, usize)> = HashMap::new();

        for request in requests {
            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
            let status_code = request.get("status_code").and_then(|v| v.as_u64());

            let key = format!("{} {}", method, path);
            let entry = endpoint_errors.entry(key).or_insert((0, 0));
            entry.0 += 1;

            if let Some(status) = status_code {
                if status >= 400 {
                    entry.1 += 1;
                }
            }
        }

        let mut patterns = Vec::new();

        for (endpoint_key, (total, errors)) in endpoint_errors {
            if total < 20 {
                continue;
            }

            let error_rate = errors as f64 / total as f64;

            if error_rate > 0.05 {
                let mut parameters = HashMap::new();
                parameters.insert("endpoint".to_string(), serde_json::json!(endpoint_key));
                parameters.insert("error_rate".to_string(), serde_json::json!(error_rate));
                parameters.insert("total_requests".to_string(), serde_json::json!(total));
                parameters.insert("error_count".to_string(), serde_json::json!(errors));

                let confidence = ((total as f64 / 100.0).min(1.0) * error_rate * 10.0).min(1.0);

                patterns.push(LearnedPattern {
                    pattern_id: format!("error_rate_{}", endpoint_key.replace(['/', ' '], "_")),
                    pattern_type: PatternType::ErrorRate,
                    parameters,
                    confidence,
                    sample_count: total,
                    last_updated: Utc::now(),
                });
            }
        }

        Ok(patterns)
    }

    /// Detect request sequence patterns from recorded request data.
    ///
    /// Each request value should contain `method` (string), `path` (string),
    /// and optionally `trace_id` (string) fields.
    async fn detect_sequence_patterns_from_requests(
        &self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        use chrono::Utc;

        if requests.len() < 50 {
            return Ok(Vec::new());
        }

        let mut trace_sequences: HashMap<Option<String>, Vec<String>> = HashMap::new();

        for request in requests {
            let trace_id = request.get("trace_id").and_then(|v| v.as_str()).map(String::from);
            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
            let endpoint_key = format!("{} {}", method, path);
            trace_sequences.entry(trace_id).or_default().push(endpoint_key);
        }

        let mut sequence_counts: HashMap<String, usize> = HashMap::new();

        for sequence in trace_sequences.values() {
            if sequence.len() >= 2 {
                let signature: Vec<String> = sequence.iter().take(3).cloned().collect();
                let signature_str = signature.join(" -> ");
                *sequence_counts.entry(signature_str).or_insert(0) += 1;
            }
        }

        let mut patterns = Vec::new();

        for (sequence_str, count) in sequence_counts {
            if count >= 5 {
                let mut parameters = HashMap::new();
                parameters.insert("sequence".to_string(), serde_json::json!(sequence_str));
                parameters.insert("occurrence_count".to_string(), serde_json::json!(count));

                let confidence = (count as f64 / 20.0).min(1.0);

                patterns.push(LearnedPattern {
                    pattern_id: format!(
                        "sequence_{}",
                        sequence_str.replace(['/', ' '], "_").replace("->", "_")
                    ),
                    pattern_type: PatternType::RequestSequence,
                    parameters,
                    confidence,
                    sample_count: count,
                    last_updated: Utc::now(),
                });
            }
        }

        Ok(patterns)
    }

    /// Detect latency patterns from recorded requests.
    ///
    /// Convenience wrapper — pass request data as JSON values.
    pub async fn detect_latency_patterns(
        &mut self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        self.detect_latency_patterns_from_requests(requests).await
    }

    /// Detect error rate patterns from recorded requests.
    ///
    /// Convenience wrapper — pass request data as JSON values.
    pub async fn detect_error_patterns(
        &mut self,
        requests: &[Value],
    ) -> Result<Vec<LearnedPattern>> {
        self.detect_error_patterns_from_requests(requests).await
    }
}

/// Persona Behavior Learner
///
/// Adapts persona profiles based on request patterns.
pub struct PersonaBehaviorLearner {
    /// Learning configuration
    config: LearningConfig,
    /// Behavior history (persona_id -> behavior events)
    behavior_history: HashMap<String, Vec<BehaviorEvent>>,
}

/// Behavior event for a persona
#[derive(Debug, Clone)]
pub struct BehaviorEvent {
    /// Event timestamp
    pub timestamp: chrono::DateTime<chrono::Utc>,
    /// Event type
    pub event_type: BehaviorEventType,
    /// Event data
    pub data: HashMap<String, Value>,
}

/// Behavior event type
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BehaviorEventType {
    /// Request made to an endpoint
    Request {
        /// Endpoint path
        endpoint: String,
        /// HTTP method
        method: String,
    },
    /// Request failed
    RequestFailed {
        /// Endpoint path
        endpoint: String,
        /// HTTP status code
        status_code: u16,
    },
    /// Request succeeded after failure
    RequestSucceededAfterFailure {
        /// Endpoint path
        endpoint: String,
    },
    /// Pattern detected
    PatternDetected {
        /// Pattern identifier
        pattern: String,
    },
}

impl PersonaBehaviorLearner {
    /// Create a new persona behavior learner
    pub fn new(config: LearningConfig) -> Result<Self> {
        Ok(Self {
            config,
            behavior_history: HashMap::new(),
        })
    }

    /// Record a behavior event for a persona
    pub fn record_event(&mut self, persona_id: String, event: BehaviorEvent) {
        if !self.config.enabled {
            return;
        }

        // Check if persona learning is enabled for this persona
        if let Some(&enabled) = self.config.persona_learning.get(&persona_id) {
            if !enabled {
                return; // Learning disabled for this persona
            }
        }

        let events = self.behavior_history.entry(persona_id).or_default();
        events.push(event);

        // Keep only recent events (last 1000)
        if events.len() > 1000 {
            events.remove(0);
        }
    }

    /// Analyze behavior patterns for a persona
    pub async fn analyze_persona_behavior(
        &self,
        persona_id: &str,
    ) -> Result<Option<LearnedPattern>> {
        if !self.config.enabled {
            return Ok(None);
        }

        let events = match self.behavior_history.get(persona_id) {
            Some(events) => events,
            None => return Ok(None),
        };

        if events.len() < self.config.min_samples {
            return Ok(None); // Not enough samples
        }

        // Analyze patterns
        // Example: If persona repeatedly requests /checkout after failure, learn this pattern
        let mut checkout_after_failure_count = 0;
        let mut total_failures = 0;

        for i in 1..events.len() {
            if let BehaviorEventType::RequestFailed { .. } = events[i - 1].event_type {
                total_failures += 1;
                if let BehaviorEventType::Request { endpoint, .. } = &events[i].event_type {
                    if endpoint.contains("/checkout") {
                        checkout_after_failure_count += 1;
                    }
                }
            }
        }

        if total_failures > 0 && checkout_after_failure_count as f64 / total_failures as f64 > 0.5 {
            // Pattern detected: persona requests /checkout after failure > 50% of the time
            let mut parameters = HashMap::new();
            parameters.insert("retry_checkout_after_failure".to_string(), Value::from(true));
            parameters.insert(
                "retry_probability".to_string(),
                Value::from(checkout_after_failure_count as f64 / total_failures as f64),
            );

            return Ok(Some(LearnedPattern {
                pattern_id: format!("persona_{}_checkout_retry", persona_id),
                pattern_type: PatternType::PersonaBehavior,
                parameters,
                confidence: (checkout_after_failure_count as f64 / total_failures as f64).min(1.0),
                sample_count: total_failures,
                last_updated: chrono::Utc::now(),
            }));
        }

        Ok(None)
    }

    /// Get behavior history for a persona
    pub fn get_behavior_history(&self, persona_id: &str) -> Option<&Vec<BehaviorEvent>> {
        self.behavior_history.get(persona_id)
    }

    /// Apply learned patterns to a persona in PersonaRegistry
    ///
    /// This method should be called periodically to update persona profiles
    /// based on learned behavior patterns.
    pub async fn apply_learned_patterns_to_persona(
        &self,
        persona_id: &str,
        persona_registry: &crate::PersonaRegistry,
    ) -> Result<()> {
        if !self.config.enabled {
            return Ok(());
        }

        // Analyze behavior for this persona
        if let Some(pattern) = self.analyze_persona_behavior(persona_id).await? {
            // Convert learned pattern parameters to traits
            let mut learned_traits = HashMap::new();
            for (key, value) in &pattern.parameters {
                let trait_key = format!("learned_{}", key);
                let trait_value = if let Some(s) = value.as_str() {
                    s.to_string()
                } else if let Some(n) = value.as_f64() {
                    n.to_string()
                } else if let Some(b) = value.as_bool() {
                    b.to_string()
                } else {
                    value.to_string()
                };
                learned_traits.insert(trait_key, trait_value);
            }

            // Update persona traits in registry
            if !learned_traits.is_empty() {
                persona_registry.update_persona(persona_id, learned_traits)?;
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // =========================================================================
    // LearningConfig tests
    // =========================================================================

    #[test]
    fn test_learning_config_default() {
        let config = LearningConfig::default();
        assert!(!config.enabled); // Opt-in by default
        assert_eq!(config.sensitivity, 0.2);
        assert_eq!(config.min_samples, 10);
        assert_eq!(config.decay, 0.05);
        assert!(config.persona_adaptation);
        assert!(config.traffic_mirroring);
    }

    #[test]
    fn test_learning_config_serialize() {
        let config = LearningConfig {
            enabled: true,
            mode: LearningMode::Statistical,
            ..Default::default()
        };
        let json = serde_json::to_string(&config).unwrap();
        assert!(json.contains("true"));
        assert!(json.contains("statistical"));
    }

    #[test]
    fn test_learning_config_deserialize() {
        let json = r#"{"enabled": true, "mode": "hybrid", "sensitivity": 0.5}"#;
        let config: LearningConfig = serde_json::from_str(json).unwrap();
        assert!(config.enabled);
        assert_eq!(config.mode, LearningMode::Hybrid);
        assert!((config.sensitivity - 0.5).abs() < f64::EPSILON);
    }

    #[test]
    fn test_learning_config_clone() {
        let config = LearningConfig {
            enabled: true,
            sensitivity: 0.3,
            ..Default::default()
        };
        let cloned = config.clone();
        assert!(cloned.enabled);
        assert!((cloned.sensitivity - 0.3).abs() < f64::EPSILON);
    }

    #[test]
    fn test_learning_config_debug() {
        let config = LearningConfig::default();
        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("sensitivity"));
        assert!(debug_str.contains("enabled"));
    }

    #[test]
    fn test_learning_config_endpoint_learning() {
        let mut config = LearningConfig::default();
        config.endpoint_learning.insert("/api/users".to_string(), true);
        config.endpoint_learning.insert("/api/orders".to_string(), false);

        assert_eq!(config.endpoint_learning.get("/api/users"), Some(&true));
        assert_eq!(config.endpoint_learning.get("/api/orders"), Some(&false));
    }

    #[test]
    fn test_learning_config_persona_learning() {
        let mut config = LearningConfig::default();
        config.persona_learning.insert("persona-1".to_string(), true);
        config.persona_learning.insert("persona-2".to_string(), false);

        assert_eq!(config.persona_learning.get("persona-1"), Some(&true));
        assert_eq!(config.persona_learning.get("persona-2"), Some(&false));
    }

    // =========================================================================
    // LearningMode tests
    // =========================================================================

    #[test]
    fn test_learning_mode_default() {
        let mode = LearningMode::default();
        assert_eq!(mode, LearningMode::Behavioral);
    }

    #[test]
    fn test_learning_mode_eq() {
        assert_eq!(LearningMode::Statistical, LearningMode::Statistical);
        assert_ne!(LearningMode::Behavioral, LearningMode::Hybrid);
    }

    #[test]
    fn test_learning_mode_serialize() {
        let mode = LearningMode::Hybrid;
        let json = serde_json::to_string(&mode).unwrap();
        assert_eq!(json, "\"hybrid\"");
    }

    #[test]
    fn test_learning_mode_deserialize() {
        let json = "\"statistical\"";
        let mode: LearningMode = serde_json::from_str(json).unwrap();
        assert_eq!(mode, LearningMode::Statistical);
    }

    #[test]
    fn test_learning_mode_clone() {
        let mode = LearningMode::Hybrid;
        let cloned = mode.clone();
        assert_eq!(cloned, LearningMode::Hybrid);
    }

    #[test]
    fn test_learning_mode_debug() {
        let debug_str = format!("{:?}", LearningMode::Behavioral);
        assert!(debug_str.contains("Behavioral"));
    }

    // =========================================================================
    // DriftLearningEngine tests
    // =========================================================================

    #[test]
    fn test_drift_learning_engine_creation() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig::default();
        let engine = DriftLearningEngine::new(drift_config, learning_config);
        assert!(engine.is_ok());
    }

    #[test]
    fn test_drift_learning_engine_with_traffic_mirroring_disabled() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            traffic_mirroring: false,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
        assert!(engine._traffic_learner.is_none());
    }

    #[test]
    fn test_drift_learning_engine_with_persona_adaptation_disabled() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            persona_adaptation: false,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
        assert!(engine._persona_learner.is_none());
    }

    #[test]
    fn test_drift_learning_engine_get_drift_engine() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig::default();
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
        let _ = engine.drift_engine();
    }

    #[test]
    fn test_drift_learning_engine_get_learning_config() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            enabled: true,
            sensitivity: 0.5,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
        assert!(engine.learning_config().enabled);
        assert!((engine.learning_config().sensitivity - 0.5).abs() < f64::EPSILON);
    }

    #[test]
    fn test_drift_learning_engine_update_learning_config() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig::default();
        let mut engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let new_config = LearningConfig {
            enabled: true,
            sensitivity: 0.8,
            ..Default::default()
        };
        engine.update_learning_config(new_config).unwrap();

        assert!(engine.learning_config().enabled);
        assert!((engine.learning_config().sensitivity - 0.8).abs() < f64::EPSILON);
    }

    #[tokio::test]
    async fn test_drift_learning_engine_get_learned_patterns_empty() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig::default();
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let patterns = engine.get_learned_patterns().await;
        assert!(patterns.is_empty());
    }

    #[tokio::test]
    async fn test_drift_learning_engine_apply_drift_with_learning_disabled() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            enabled: false,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let data = serde_json::json!({"value": 100});
        let result = engine.apply_drift_with_learning(data.clone()).await.unwrap();
        // Should return data without learning modifications
        assert!(result.is_object());
    }

    #[tokio::test]
    async fn test_drift_learning_engine_apply_drift_with_learning_enabled() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            enabled: true,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let data = serde_json::json!({"value": 100});
        let result = engine.apply_drift_with_learning(data).await.unwrap();
        assert!(result.is_object());
    }

    #[test]
    fn test_drift_learning_engine_blend_values_numeric() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            sensitivity: 0.5,
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let existing = serde_json::json!(100.0);
        let learned = serde_json::json!(200.0);
        let result = engine.blend_values(&existing, &learned, 0.5).unwrap();

        // With sensitivity 0.5 and confidence 0.5, weight = 0.25
        // blended = 100 * 0.75 + 200 * 0.25 = 75 + 50 = 125
        if let Some(n) = result.as_f64() {
            assert!((n - 125.0).abs() < 1.0);
        }
    }

    #[test]
    fn test_drift_learning_engine_blend_values_non_numeric() {
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig::default();
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        let existing = serde_json::json!("original");
        let learned = serde_json::json!("learned");
        let result = engine.blend_values(&existing, &learned, 0.5).unwrap();

        // Non-numeric values keep existing
        assert_eq!(result, serde_json::json!("original"));
    }

    // =========================================================================
    // LearnedPattern tests
    // =========================================================================

    #[test]
    fn test_learned_pattern_creation() {
        let pattern = LearnedPattern {
            pattern_id: "test-pattern".to_string(),
            pattern_type: PatternType::Latency,
            parameters: HashMap::new(),
            confidence: 0.9,
            sample_count: 100,
            last_updated: chrono::Utc::now(),
        };
        assert_eq!(pattern.pattern_id, "test-pattern");
        assert!((pattern.confidence - 0.9).abs() < f64::EPSILON);
    }

    #[test]
    fn test_learned_pattern_clone() {
        let pattern = LearnedPattern {
            pattern_id: "cloneable".to_string(),
            pattern_type: PatternType::ErrorRate,
            parameters: HashMap::new(),
            confidence: 0.75,
            sample_count: 50,
            last_updated: chrono::Utc::now(),
        };
        let cloned = pattern.clone();
        assert_eq!(cloned.pattern_id, "cloneable");
    }

    #[test]
    fn test_learned_pattern_debug() {
        let pattern = LearnedPattern {
            pattern_id: "debug-pattern".to_string(),
            pattern_type: PatternType::PersonaBehavior,
            parameters: HashMap::new(),
            confidence: 0.5,
            sample_count: 25,
            last_updated: chrono::Utc::now(),
        };
        let debug_str = format!("{:?}", pattern);
        assert!(debug_str.contains("debug-pattern"));
    }

    // =========================================================================
    // PatternType tests
    // =========================================================================

    #[test]
    fn test_pattern_type_eq() {
        assert_eq!(PatternType::Latency, PatternType::Latency);
        assert_ne!(PatternType::Latency, PatternType::ErrorRate);
    }

    #[test]
    fn test_pattern_type_clone() {
        let pt = PatternType::RequestSequence;
        let cloned = pt.clone();
        assert_eq!(cloned, PatternType::RequestSequence);
    }

    #[test]
    fn test_pattern_type_debug() {
        let debug_str = format!("{:?}", PatternType::PersonaBehavior);
        assert!(debug_str.contains("PersonaBehavior"));
    }

    // =========================================================================
    // TrafficPatternLearner tests
    // =========================================================================

    #[test]
    fn test_traffic_pattern_learner_new() {
        let config = LearningConfig::default();
        let learner = TrafficPatternLearner::new(config);
        assert!(learner.is_ok());
    }

    #[tokio::test]
    async fn test_traffic_pattern_learner_analyze_empty() {
        let config = LearningConfig::default();
        let mut learner = TrafficPatternLearner::new(config).unwrap();
        let patterns = learner.analyze_traffic_patterns(&[]).await.unwrap();
        assert!(patterns.is_empty());
    }

    #[tokio::test]
    async fn test_traffic_pattern_learner_detect_latency_patterns() {
        let config = LearningConfig::default();
        let mut learner = TrafficPatternLearner::new(config).unwrap();
        // Not enough samples (< 10) should return empty
        let requests: Vec<Value> = (0..5)
            .map(|i| {
                serde_json::json!({
                    "method": "GET",
                    "path": "/api/test",
                    "duration_ms": 100 + i * 10,
                })
            })
            .collect();
        let patterns = learner.detect_latency_patterns(&requests).await.unwrap();
        assert!(patterns.is_empty());
    }

    #[tokio::test]
    async fn test_traffic_pattern_learner_detect_error_patterns() {
        let config = LearningConfig::default();
        let mut learner = TrafficPatternLearner::new(config).unwrap();
        // Not enough samples (< 20) should return empty
        let requests: Vec<Value> = (0..10)
            .map(|_| {
                serde_json::json!({
                    "method": "GET",
                    "path": "/api/test",
                    "status_code": 500,
                })
            })
            .collect();
        let patterns = learner.detect_error_patterns(&requests).await.unwrap();
        assert!(patterns.is_empty());
    }

    // =========================================================================
    // PersonaBehaviorLearner tests
    // =========================================================================

    #[test]
    fn test_persona_behavior_learner_new() {
        let config = LearningConfig::default();
        let learner = PersonaBehaviorLearner::new(config);
        assert!(learner.is_ok());
    }

    #[test]
    fn test_persona_behavior_learner_record_event_disabled() {
        let config = LearningConfig {
            enabled: false,
            ..Default::default()
        };
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        learner.record_event(
            "persona-1".to_string(),
            BehaviorEvent {
                timestamp: chrono::Utc::now(),
                event_type: BehaviorEventType::Request {
                    endpoint: "/api/test".to_string(),
                    method: "GET".to_string(),
                },
                data: HashMap::new(),
            },
        );

        // Should not record when disabled
        assert!(learner.get_behavior_history("persona-1").is_none());
    }

    #[test]
    fn test_persona_behavior_learner_record_event_persona_disabled() {
        let mut config = LearningConfig {
            enabled: true,
            ..Default::default()
        };
        config.persona_learning.insert("persona-1".to_string(), false);
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        learner.record_event(
            "persona-1".to_string(),
            BehaviorEvent {
                timestamp: chrono::Utc::now(),
                event_type: BehaviorEventType::Request {
                    endpoint: "/api/test".to_string(),
                    method: "GET".to_string(),
                },
                data: HashMap::new(),
            },
        );

        // Should not record when persona learning is disabled
        assert!(learner.get_behavior_history("persona-1").is_none());
    }

    #[tokio::test]
    async fn test_persona_behavior_learner() {
        let config = LearningConfig {
            enabled: true,
            persona_adaptation: true,
            ..Default::default()
        };
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        // Record failure
        learner.record_event(
            "persona-1".to_string(),
            BehaviorEvent {
                timestamp: chrono::Utc::now(),
                event_type: BehaviorEventType::RequestFailed {
                    endpoint: "/api/checkout".to_string(),
                    status_code: 500,
                },
                data: HashMap::new(),
            },
        );

        // Record checkout request after failure
        learner.record_event(
            "persona-1".to_string(),
            BehaviorEvent {
                timestamp: chrono::Utc::now(),
                event_type: BehaviorEventType::Request {
                    endpoint: "/api/checkout".to_string(),
                    method: "POST".to_string(),
                },
                data: HashMap::new(),
            },
        );

        // Analyze (won't find pattern with only 2 samples, need min_samples)
        let pattern = learner.analyze_persona_behavior("persona-1").await.unwrap();
        assert!(pattern.is_none()); // Not enough samples
    }

    #[tokio::test]
    async fn test_persona_behavior_learner_get_behavior_history() {
        let config = LearningConfig {
            enabled: true,
            ..Default::default()
        };
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        learner.record_event(
            "persona-test".to_string(),
            BehaviorEvent {
                timestamp: chrono::Utc::now(),
                event_type: BehaviorEventType::Request {
                    endpoint: "/api/users".to_string(),
                    method: "GET".to_string(),
                },
                data: HashMap::new(),
            },
        );

        let history = learner.get_behavior_history("persona-test");
        assert!(history.is_some());
        assert_eq!(history.unwrap().len(), 1);
    }

    #[tokio::test]
    async fn test_persona_behavior_learner_analyze_nonexistent_persona() {
        let config = LearningConfig {
            enabled: true,
            ..Default::default()
        };
        let learner = PersonaBehaviorLearner::new(config).unwrap();

        let pattern = learner.analyze_persona_behavior("nonexistent").await.unwrap();
        assert!(pattern.is_none());
    }

    #[tokio::test]
    async fn test_persona_behavior_learner_analyze_disabled() {
        let config = LearningConfig {
            enabled: false,
            ..Default::default()
        };
        let learner = PersonaBehaviorLearner::new(config).unwrap();

        let pattern = learner.analyze_persona_behavior("any").await.unwrap();
        assert!(pattern.is_none());
    }

    #[test]
    fn test_persona_behavior_learner_event_limit() {
        let config = LearningConfig {
            enabled: true,
            ..Default::default()
        };
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        // Record more than 1000 events
        for i in 0..1050 {
            learner.record_event(
                "persona-limit".to_string(),
                BehaviorEvent {
                    timestamp: chrono::Utc::now(),
                    event_type: BehaviorEventType::Request {
                        endpoint: format!("/api/test/{}", i),
                        method: "GET".to_string(),
                    },
                    data: HashMap::new(),
                },
            );
        }

        let history = learner.get_behavior_history("persona-limit").unwrap();
        assert_eq!(history.len(), 1000); // Should be capped at 1000
    }

    // =========================================================================
    // BehaviorEvent tests
    // =========================================================================

    #[test]
    fn test_behavior_event_creation() {
        let event = BehaviorEvent {
            timestamp: chrono::Utc::now(),
            event_type: BehaviorEventType::Request {
                endpoint: "/api/test".to_string(),
                method: "POST".to_string(),
            },
            data: HashMap::new(),
        };
        assert!(event.data.is_empty());
    }

    #[test]
    fn test_behavior_event_clone() {
        let event = BehaviorEvent {
            timestamp: chrono::Utc::now(),
            event_type: BehaviorEventType::PatternDetected {
                pattern: "test-pattern".to_string(),
            },
            data: HashMap::new(),
        };
        let cloned = event.clone();
        if let BehaviorEventType::PatternDetected { pattern } = cloned.event_type {
            assert_eq!(pattern, "test-pattern");
        } else {
            panic!("Wrong event type after clone");
        }
    }

    #[test]
    fn test_behavior_event_debug() {
        let event = BehaviorEvent {
            timestamp: chrono::Utc::now(),
            event_type: BehaviorEventType::RequestSucceededAfterFailure {
                endpoint: "/api/retry".to_string(),
            },
            data: HashMap::new(),
        };
        let debug_str = format!("{:?}", event);
        assert!(debug_str.contains("RequestSucceededAfterFailure"));
    }

    // =========================================================================
    // BehaviorEventType tests
    // =========================================================================

    #[test]
    fn test_behavior_event_type_request() {
        let event_type = BehaviorEventType::Request {
            endpoint: "/api/users".to_string(),
            method: "GET".to_string(),
        };
        if let BehaviorEventType::Request { endpoint, method } = event_type {
            assert_eq!(endpoint, "/api/users");
            assert_eq!(method, "GET");
        }
    }

    #[test]
    fn test_behavior_event_type_request_failed() {
        let event_type = BehaviorEventType::RequestFailed {
            endpoint: "/api/orders".to_string(),
            status_code: 500,
        };
        if let BehaviorEventType::RequestFailed {
            endpoint,
            status_code,
        } = event_type
        {
            assert_eq!(endpoint, "/api/orders");
            assert_eq!(status_code, 500);
        }
    }

    #[test]
    fn test_behavior_event_type_eq() {
        let a = BehaviorEventType::PatternDetected {
            pattern: "a".to_string(),
        };
        let b = BehaviorEventType::PatternDetected {
            pattern: "a".to_string(),
        };
        assert_eq!(a, b);
    }

    #[test]
    fn test_behavior_event_type_clone() {
        let event_type = BehaviorEventType::RequestFailed {
            endpoint: "/test".to_string(),
            status_code: 404,
        };
        let cloned = event_type.clone();
        assert_eq!(cloned, event_type);
    }

    #[test]
    fn test_behavior_event_type_debug() {
        let event_type = BehaviorEventType::Request {
            endpoint: "/debug".to_string(),
            method: "DELETE".to_string(),
        };
        let debug_str = format!("{:?}", event_type);
        assert!(debug_str.contains("Request"));
        assert!(debug_str.contains("/debug"));
    }

    // =========================================================================
    // Integration tests
    // =========================================================================

    #[tokio::test]
    async fn test_full_learning_workflow() {
        // Create learning engine
        let drift_config = DataDriftConfig::new();
        let learning_config = LearningConfig {
            enabled: true,
            min_samples: 2, // Lower threshold for testing
            ..Default::default()
        };
        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();

        // Apply drift with learning
        let data = serde_json::json!({
            "user_id": "123",
            "amount": 100.0,
            "status": "pending"
        });
        let result = engine.apply_drift_with_learning(data).await.unwrap();
        assert!(result.is_object());
    }

    #[tokio::test]
    async fn test_persona_behavior_pattern_detection() {
        let config = LearningConfig {
            enabled: true,
            min_samples: 5,
            ..Default::default()
        };
        let mut learner = PersonaBehaviorLearner::new(config).unwrap();

        // Simulate pattern: failure followed by checkout retry
        for _ in 0..10 {
            learner.record_event(
                "retry-persona".to_string(),
                BehaviorEvent {
                    timestamp: chrono::Utc::now(),
                    event_type: BehaviorEventType::RequestFailed {
                        endpoint: "/api/payment".to_string(),
                        status_code: 503,
                    },
                    data: HashMap::new(),
                },
            );
            learner.record_event(
                "retry-persona".to_string(),
                BehaviorEvent {
                    timestamp: chrono::Utc::now(),
                    event_type: BehaviorEventType::Request {
                        endpoint: "/api/checkout".to_string(),
                        method: "POST".to_string(),
                    },
                    data: HashMap::new(),
                },
            );
        }

        // Now analyze - should detect pattern
        let pattern = learner.analyze_persona_behavior("retry-persona").await.unwrap();
        assert!(pattern.is_some());
        let p = pattern.unwrap();
        assert_eq!(p.pattern_type, PatternType::PersonaBehavior);
        assert!(p.confidence > 0.0);
    }
}
