#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-eclipse -- Shadow Traffic Testing
//! Mirror production traffic to test instances without affecting users

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub enum EclipseError {
    ShadowFailed(String),
    ComparisonFailed(String),
    ConfigError(String),
}

impl std::fmt::Display for EclipseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ShadowFailed(e) => write!(f, "Shadow failed: {e}"),
            Self::ComparisonFailed(e) => write!(f, "Comparison failed: {e}"),
            Self::ConfigError(e) => write!(f, "Config: {e}"),
        }
    }
}
impl std::error::Error for EclipseError {}

#[derive(Debug, Clone)]
pub struct ShadowRequest {
    pub request_id: String,
    pub method: String,
    pub path: String,
    pub body_hash: u64,
    pub timestamp: u64,
}

#[derive(Debug, Clone)]
pub struct ShadowResult {
    pub request_id: String,
    pub production_status: u16,
    pub shadow_status: u16,
    pub production_latency_ms: u64,
    pub shadow_latency_ms: u64,
    pub response_match: bool,
    pub diff_fields: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct ShadowStats {
    pub total_requests: u64,
    pub matched: u64,
    pub mismatched: u64,
    pub shadow_errors: u64,
    pub avg_latency_diff_ms: f64,
    pub match_rate: f64,
}

pub struct Eclipse {
    shadow_percent: f64,
    results: Arc<Mutex<Vec<ShadowResult>>>,
    entropy_state: Mutex<u64>,
}

impl Eclipse {
    pub fn new(shadow_percent: f64) -> Result<Self, EclipseError> {
        if shadow_percent < 0.0 || shadow_percent > 100.0 {
            return Err(EclipseError::ConfigError(format!("Invalid percent: {shadow_percent}")));
        }
        Ok(Self {
            shadow_percent,
            results: Arc::new(Mutex::new(Vec::new())),
            entropy_state: Mutex::new(0x517cc1b727220a95),
        })
    }

    /// Determine if request should be shadowed (deterministic sampling)
    pub fn should_shadow(&self, request_id: &str) -> bool {
        let mut hash: u64 = 0xcbf29ce484222325;
        for b in request_id.bytes() {
            hash ^= b as u64;
            hash = hash.wrapping_mul(0x100000001b3);
        }
        let sample = (hash % 10000) as f64 / 100.0;
        sample < self.shadow_percent
    }

    /// Record comparison result
    pub fn record_result(&self, result: ShadowResult) -> Result<(), EclipseError> {
        if let Ok(mut results) = self.results.lock() {
            results.push(result);
            Ok(())
        } else {
            Err(EclipseError::ShadowFailed("Lock poisoned".into()))
        }
    }

    /// Compare two JSON-like response bodies (simplified field comparison)
    pub fn compare_responses(
        &self,
        prod_fields: &HashMap<String, String>,
        shadow_fields: &HashMap<String, String>,
    ) -> (bool, Vec<String>) {
        let mut diffs = Vec::new();

        for (key, prod_val) in prod_fields {
            match shadow_fields.get(key) {
                Some(shadow_val) if shadow_val != prod_val => {
                    diffs.push(format!("{key}: '{prod_val}' vs '{shadow_val}'"));
                }
                None => {
                    diffs.push(format!("{key}: missing in shadow"));
                }
                _ => {}
            }
        }

        for key in shadow_fields.keys() {
            if !prod_fields.contains_key(key) {
                diffs.push(format!("{key}: extra in shadow"));
            }
        }

        (diffs.is_empty(), diffs)
    }

    /// Calculate shadow testing statistics
    pub fn stats(&self) -> Result<ShadowStats, EclipseError> {
        let results = self.results.lock()
            .map_err(|_| EclipseError::ComparisonFailed("Lock".into()))?;

        if results.is_empty() {
            return Ok(ShadowStats {
                total_requests: 0, matched: 0, mismatched: 0,
                shadow_errors: 0, avg_latency_diff_ms: 0.0, match_rate: 0.0,
            });
        }

        let total = results.len() as u64;
        let matched = results.iter().filter(|r| r.response_match).count() as u64;
        let errors = results.iter().filter(|r| r.shadow_status >= 500).count() as u64;
        let latency_diff_sum: f64 = results.iter()
            .map(|r| (r.shadow_latency_ms as f64 - r.production_latency_ms as f64).abs())
            .sum();

        Ok(ShadowStats {
            total_requests: total,
            matched,
            mismatched: total - matched,
            shadow_errors: errors,
            avg_latency_diff_ms: latency_diff_sum / total as f64,
            match_rate: matched as f64 / total as f64 * 100.0,
        })
    }
}

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

    #[test]
    fn test_shadow_sampling() {
        let e = Eclipse::new(50.0).unwrap();
        let mut shadowed = 0;
        for i in 0..1000 {
            if e.should_shadow(&format!("req-{i}")) { shadowed += 1; }
        }
        assert!(shadowed > 300 && shadowed < 700);
    }

    #[test]
    fn test_compare_match() {
        let e = Eclipse::new(100.0).unwrap();
        let mut a = HashMap::new();
        a.insert("status".into(), "ok".into());
        let b = a.clone();
        let (matched, diffs) = e.compare_responses(&a, &b);
        assert!(matched);
        assert!(diffs.is_empty());
    }

    #[test]
    fn test_compare_mismatch() {
        let e = Eclipse::new(100.0).unwrap();
        let mut a = HashMap::new();
        a.insert("status".into(), "ok".into());
        let mut b = HashMap::new();
        b.insert("status".into(), "error".into());
        let (matched, diffs) = e.compare_responses(&a, &b);
        assert!(!matched);
        assert_eq!(diffs.len(), 1);
    }

    #[test]
    fn test_stats() {
        let e = Eclipse::new(100.0).unwrap();
        e.record_result(ShadowResult {
            request_id: "1".into(), production_status: 200, shadow_status: 200,
            production_latency_ms: 10, shadow_latency_ms: 12, response_match: true,
            diff_fields: vec![],
        }).unwrap();
        e.record_result(ShadowResult {
            request_id: "2".into(), production_status: 200, shadow_status: 500,
            production_latency_ms: 10, shadow_latency_ms: 100, response_match: false,
            diff_fields: vec!["body".into()],
        }).unwrap();
        let stats = e.stats().unwrap();
        assert_eq!(stats.total_requests, 2);
        assert_eq!(stats.matched, 1);
        assert_eq!(stats.shadow_errors, 1);
    }
}
