use chrono::{DateTime, Utc};
use common::{OrderSide, OrderType, Price, Quantity, Symbol, TimeInForce};
use rand::{thread_rng, Rng};
use std::collections::HashMap;
use uuid::Uuid;

/// Test market data event structure
///
/// Note: This is a test-specific type, not the proto MarketDataEvent
#[derive(Debug, Clone)]
pub struct MarketDataEvent {
    pub id: Uuid,
    pub symbol: Symbol,
    pub timestamp: DateTime<Utc>,
    pub bid: Price,
    pub ask: Price,
    pub bid_size: Quantity,
    pub ask_size: Quantity,
    pub last_price: Option<Price>,
    pub volume: Option<Quantity>,
}

/// Test utilities for generating market data and orders
pub struct TestDataGenerator {
    symbols: Vec<Symbol>,
    prices: HashMap<Symbol, Price>,
}

impl Default for TestDataGenerator {
    fn default() -> Self {
        Self::new()
    }
}

impl TestDataGenerator {
    pub fn new() -> Self {
        let symbols = vec![
            Symbol::new("EURUSD".to_string()),
            Symbol::new("GBPUSD".to_string()),
            Symbol::new("USDJPY".to_string()),
            Symbol::new("USDCHF".to_string()),
            Symbol::new("AUDUSD".to_string()),
        ];

        let mut prices = HashMap::new();
        prices.insert(
            Symbol::new("EURUSD".to_string()),
            Price::new(1.0520).unwrap(),
        ); // 1.0520
        prices.insert(
            Symbol::new("GBPUSD".to_string()),
            Price::new(1.2845).unwrap(),
        ); // 1.2845
        prices.insert(
            Symbol::new("USDJPY".to_string()),
            Price::new(148.55).unwrap(),
        ); // 148.55
        prices.insert(
            Symbol::new("USDCHF".to_string()),
            Price::new(0.8750).unwrap(),
        ); // 0.8750
        prices.insert(
            Symbol::new("AUDUSD".to_string()),
            Price::new(0.6750).unwrap(),
        ); // 0.6750

        Self { symbols, prices }
    }

    pub fn generate_market_data(&mut self, symbol: &Symbol) -> MarketDataEvent {
        let mut rng = thread_rng();
        let current_price = *self.prices.get(symbol).unwrap();

        // Generate small random price movement
        let change_pct = rng.gen_range(-0.001..0.001); // ±0.1%
        let change = current_price.to_f64() * change_pct;
        let new_price = Price::new(current_price.to_f64() + change).unwrap();

        self.prices.insert(symbol.clone(), new_price);

        MarketDataEvent {
            id: Uuid::new_v4(),
            symbol: symbol.clone(),
            timestamp: Utc::now(),
            bid: Price::new(new_price.to_f64() - 0.0002).unwrap(), // 2 pip spread
            ask: new_price,
            bid_size: Quantity::new(rng.gen_range(100000..1000000) as f64).unwrap(),
            ask_size: Quantity::new(rng.gen_range(100000..1000000) as f64).unwrap(),
            last_price: Some(new_price),
            volume: Some(Quantity::new(rng.gen_range(50000..500000) as f64).unwrap()),
        }
    }

    pub fn generate_order_request(&self, symbol: &Symbol) -> OrderRequest {
        let mut rng = thread_rng();
        let current_price = self.prices.get(symbol).unwrap();

        OrderRequest {
            id: Uuid::new_v4(),
            symbol: symbol.clone(),
            side: if rng.gen_bool(0.5) {
                OrderSide::Buy
            } else {
                OrderSide::Sell
            },
            quantity: Quantity::new(rng.gen_range(10000..100000) as f64).unwrap(), // 10K to 100K units
            order_type: OrderType::Market,
            price: Some(*current_price),
            time_in_force: TimeInForce::ImmediateOrCancel,
            timestamp: Utc::now(),
        }
    }

    pub fn get_random_symbol(&self) -> &Symbol {
        let mut rng = thread_rng();
        &self.symbols[rng.gen_range(0..self.symbols.len())]
    }

    pub fn get_all_symbols(&self) -> &[Symbol] {
        &self.symbols
    }
}

#[derive(Debug, Clone)]
pub struct OrderRequest {
    pub id: Uuid,
    pub symbol: Symbol,
    pub side: OrderSide,
    pub quantity: Quantity,
    pub order_type: OrderType,
    pub price: Option<Price>,
    pub time_in_force: TimeInForce,
    pub timestamp: DateTime<Utc>,
}

/// Trading event for database persistence testing
#[derive(Debug, Clone)]
pub struct TradingEvent {
    pub id: Uuid,
    pub event_type: String,
    pub symbol: String,
    pub timestamp: DateTime<Utc>,
    pub data: serde_json::Value,
}

/// Test assertion helpers
pub mod assertions {
    use super::*;
    use std::time::Duration;

    pub fn assert_within_tolerance(actual: f64, expected: f64, tolerance_pct: f64) {
        let tolerance = expected * tolerance_pct;
        assert!(
            (actual - expected).abs() <= tolerance,
            "Value {} is not within {}% tolerance of expected {}",
            actual,
            expected,
            tolerance_pct * 100.0
        );
    }

    pub fn assert_latency_under(duration: Duration, max_latency: Duration) {
        assert!(
            duration <= max_latency,
            "Latency {:?} exceeds maximum allowed {:?}",
            duration,
            max_latency
        );
    }

    pub fn assert_price_reasonable(price: &Price, symbol: &Symbol) {
        let value = price.to_f64();
        match symbol.as_str() {
            "EURUSD" | "GBPUSD" | "AUDUSD" => {
                assert!(
                    value > 0.5 && value < 2.0,
                    "Price {} unreasonable for {}",
                    value,
                    symbol
                );
            },
            "USDJPY" => {
                assert!(
                    value > 100.0 && value < 200.0,
                    "Price {} unreasonable for {}",
                    value,
                    symbol
                );
            },
            "USDCHF" => {
                assert!(
                    value > 0.7 && value < 1.2,
                    "Price {} unreasonable for {}",
                    value,
                    symbol
                );
            },
            _ => {}, // Skip validation for unknown symbols
        }
    }
}

/// Performance profiling utilities for E2E tests
pub struct PerformanceProfiler {
    start_time: std::time::Instant,
    checkpoints: Vec<(String, std::time::Duration)>,
}

impl Default for PerformanceProfiler {
    fn default() -> Self {
        Self::new()
    }
}

impl PerformanceProfiler {
    pub fn new() -> Self {
        Self {
            start_time: std::time::Instant::now(),
            checkpoints: Vec::new(),
        }
    }

    pub fn checkpoint(&mut self, name: &str) {
        let elapsed = self.start_time.elapsed();
        self.checkpoints.push((name.to_string(), elapsed));
    }

    pub fn print_summary(&self) {
        println!("\n=== Performance Profile ===");
        let mut last_duration = std::time::Duration::ZERO;
        for (name, duration) in &self.checkpoints {
            let delta = *duration - last_duration;
            println!("{}: {:?} (delta: {:?})", name, duration, delta);
            last_duration = *duration;
        }
        println!("Total: {:?}\n", self.start_time.elapsed());
    }
}

/// Test utility functions
pub struct TestUtils;

impl TestUtils {
    /// Setup test logging
    pub fn setup_test_logging() {
        let _ = tracing_subscriber::fmt()
            .with_env_filter(
                tracing_subscriber::EnvFilter::try_from_default_env()
                    .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
            )
            .try_init();
    }

    /// Wait for a condition to be true with timeout
    pub async fn wait_for_condition<F, Fut>(
        condition: F,
        timeout_secs: u64,
        check_interval_ms: u64,
    ) -> anyhow::Result<()>
    where
        F: Fn() -> Fut,
        Fut: std::future::Future<Output = bool>,
    {
        use tokio::time::{sleep, Duration, Instant};

        let start = Instant::now();
        let timeout = Duration::from_secs(timeout_secs);
        let interval = Duration::from_millis(check_interval_ms);

        while start.elapsed() < timeout {
            if condition().await {
                return Ok(());
            }
            sleep(interval).await;
        }

        Err(anyhow::anyhow!(
            "Condition not met within {} seconds",
            timeout_secs
        ))
    }

    /// Check if a service is healthy via HTTP
    pub async fn check_service_health(endpoint: &str) -> anyhow::Result<bool> {
        let client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(5))
            .build()?;

        match client.get(endpoint).send().await {
            Ok(response) => Ok(response.status().is_success()),
            Err(_) => Ok(false),
        }
    }
}

/// Stub types for E2E testing - These are lightweight test doubles
/// for performance-critical components that are tested separately

/// Trading operations tracker for latency testing
pub struct TradingOperations {
    operations: std::sync::Arc<std::sync::Mutex<Vec<(String, String, std::time::Instant)>>>,
}

impl Default for TradingOperations {
    fn default() -> Self {
        Self::new()
    }
}

impl TradingOperations {
    pub fn new() -> Self {
        Self {
            operations: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
        }
    }

    pub fn record_order_submission(&self, order_id: String, symbol: String) {
        let mut ops = self.operations.lock().unwrap();
        ops.push((order_id, symbol, std::time::Instant::now()));
    }
}

/// SIMD price operations stub for testing (actual implementation in trading_engine)
pub struct SimdPriceOps;

impl SimdPriceOps {
    pub fn new() -> anyhow::Result<Self> {
        Ok(Self)
    }

    pub fn vectorized_mean(&self, prices: &[f64]) -> anyhow::Result<f64> {
        if prices.is_empty() {
            return Err(anyhow::anyhow!("Empty price array"));
        }
        Ok(prices.iter().sum::<f64>() / prices.len() as f64)
    }
}

/// Lock-free ring buffer stub for testing
pub struct LockFreeRingBuffer<T> {
    buffer: std::sync::Arc<std::sync::Mutex<Vec<T>>>,
    capacity: usize,
}

impl<T: Clone> LockFreeRingBuffer<T> {
    pub fn new(capacity: usize) -> Self {
        Self {
            buffer: std::sync::Arc::new(std::sync::Mutex::new(Vec::with_capacity(capacity))),
            capacity,
        }
    }

    pub fn try_push(&self, item: T) -> bool {
        let mut buffer = self.buffer.lock().unwrap();
        if buffer.len() < self.capacity {
            buffer.push(item);
            true
        } else {
            false
        }
    }
}

/// Small batch processor for testing
pub struct SmallBatchProcessor;

impl Default for SmallBatchProcessor {
    fn default() -> Self {
        Self::new()
    }
}

impl SmallBatchProcessor {
    pub fn new() -> Self {
        Self
    }

    pub fn process_batch<T>(&self, orders: Vec<T>) -> anyhow::Result<usize> {
        // Simulate minimal processing - generic to accept any order type
        Ok(orders.len())
    }
}

/// Environment setup utilities
pub mod env {
    use std::env;

    pub fn setup_test_environment() {
        // Set up test-specific environment variables
        env::set_var("RUST_LOG", "debug");
        env::set_var("DATABASE_URL", get_test_database_url());
        env::set_var("TRADING_SERVICE_PORT", "50051");
        env::set_var("BACKTESTING_SERVICE_PORT", "50052");
        env::set_var("CONFIG_SERVICE_PORT", "50053");
    }

    pub fn get_test_database_url() -> String {
        env::var("TEST_DATABASE_URL")
            .unwrap_or_else(|_| "postgresql://localhost/foxhunt_test".to_string())
    }

    pub fn is_ci_environment() -> bool {
        env::var("CI").is_ok() || env::var("GITHUB_ACTIONS").is_ok()
    }
}

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

    #[test]
    fn test_data_generator_creates_valid_market_data() {
        let mut generator = TestDataGenerator::new();
        let symbol = Symbol::new("EURUSD".to_string());
        let market_data = generator.generate_market_data(&symbol);

        assert_eq!(market_data.symbol, symbol);
        assert!(market_data.bid < market_data.ask);
        assertions::assert_price_reasonable(&market_data.bid, &symbol);
        assertions::assert_price_reasonable(&market_data.ask, &symbol);
    }

    #[test]
    fn test_order_request_generation() {
        let generator = TestDataGenerator::new();
        let symbol = Symbol::new("GBPUSD".to_string());
        let order = generator.generate_order_request(&symbol);

        assert_eq!(order.symbol, symbol);
        assert!(order.quantity.to_f64() > 0.0);
        if let Some(price) = &order.price {
            assertions::assert_price_reasonable(price, &symbol);
        }
    }

    #[test]
    fn test_assertion_helpers() {
        assertions::assert_within_tolerance(100.0, 99.0, 0.02); // 2% tolerance
        assertions::assert_latency_under(Duration::from_millis(5), Duration::from_millis(10));
    }
}
