#![allow(
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_line_after_doc_comments,
    clippy::field_reassign_with_default,
    clippy::get_unwrap,
    clippy::identity_op,
    clippy::inconsistent_digit_grouping,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_underscore_must_use,
    clippy::manual_div_ceil,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::non_ascii_literal,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::too_many_lines,
    clippy::unnecessary_wraps,
    clippy::unseparated_literal_suffix,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    clippy::else_if_without_else,
    clippy::expect_used,
    clippy::missing_const_for_fn,
    clippy::similar_names,
    clippy::type_complexity,
    clippy::collapsible_else_if,
    clippy::doc_lazy_continuation,
    clippy::items_after_test_module,
    clippy::map_clone,
    clippy::multiple_unsafe_ops_per_block,
    clippy::unwrap_or_default,
    clippy::assign_op_pattern,
    clippy::needless_borrow,
    clippy::println_empty_string,
    clippy::unnecessary_cast,
    clippy::used_underscore_binding,
    clippy::create_dir,
    clippy::implicit_saturating_sub,
    clippy::exit,
    clippy::expect_fun_call,
    clippy::too_many_arguments,
    clippy::unnecessary_map_or,
    clippy::unwrap_used,
    dead_code,
    unused_imports,
    unused_variables,
    clippy::cloned_ref_to_slice_refs,
    clippy::neg_multiply,
    clippy::while_let_loop,
    clippy::bool_assert_comparison,
    clippy::excessive_precision,
    clippy::trivially_copy_pass_by_ref,
    clippy::op_ref,
    clippy::redundant_closure,
    clippy::unnecessary_lazy_evaluations,
    clippy::if_then_some_else_none,
    clippy::unnecessary_to_owned,
    clippy::single_component_path_imports,
)]
// ml/tests/barrier_optimization_test.rs
//
// TDD Test Suite for Barrier Optimization Engine
// Tests MUST be written BEFORE implementation

use approx::assert_relative_eq;
use std::time::Instant;

// Import types that will be implemented
use ml::features::barrier_optimization::{BarrierOptimizer, BarrierParams, OptimizationResult};

#[test]
fn test_barrier_params_validation() {
    // Test valid parameters
    let params = BarrierParams::new(2.0, 1.0, 10);
    assert_relative_eq!(params.profit_factor, 2.0, epsilon = 1e-6);
    assert_relative_eq!(params.stop_factor, 1.0, epsilon = 1e-6);
    assert_eq!(params.time_horizon, 10);
}

#[test]
#[should_panic(expected = "profit_factor must be positive")]
fn test_barrier_params_negative_profit() {
    BarrierParams::new(-1.0, 1.0, 10);
}

#[test]
#[should_panic(expected = "stop_factor must be positive")]
fn test_barrier_params_negative_stop() {
    BarrierParams::new(2.0, -1.0, 10);
}

#[test]
#[should_panic(expected = "time_horizon must be at least 1")]
fn test_barrier_params_zero_horizon() {
    BarrierParams::new(2.0, 1.0, 0);
}

#[test]
fn test_optimizer_creation_default() {
    let optimizer = BarrierOptimizer::new();

    // Default ranges
    assert_eq!(optimizer.profit_range().len(), 5); // [1.0, 1.5, 2.0, 2.5, 3.0]
    assert_eq!(optimizer.stop_range().len(), 4); // [0.5, 1.0, 1.5, 2.0]
    assert_eq!(optimizer.horizon_range().len(), 4); // [5, 10, 20, 30]

    // Total combinations: 5 * 4 * 4 = 80
    assert_eq!(optimizer.total_combinations(), 80);
}

#[test]
fn test_optimizer_creation_custom() {
    let profit_range = vec![1.5, 2.0, 2.5];
    let stop_range = vec![0.5, 1.0];
    let horizon_range = vec![10, 20];

    let optimizer = BarrierOptimizer::with_ranges(
        profit_range.clone(),
        stop_range.clone(),
        horizon_range.clone(),
    );

    assert_eq!(optimizer.profit_range(), &profit_range);
    assert_eq!(optimizer.stop_range(), &stop_range);
    assert_eq!(optimizer.horizon_range(), &horizon_range);
    assert_eq!(optimizer.total_combinations(), 3 * 2 * 2); // 12
}

#[test]
fn test_sharpe_ratio_calculation_positive_returns() {
    let optimizer = BarrierOptimizer::new();

    // Positive returns with some volatility
    let returns = vec![0.01, 0.02, -0.005, 0.015, 0.008];
    let sharpe = optimizer.calculate_sharpe(&returns);

    // Should be positive (profitable strategy)
    assert!(sharpe > 0.0);
    assert!(sharpe.is_finite());
}

#[test]
fn test_sharpe_ratio_calculation_negative_returns() {
    let optimizer = BarrierOptimizer::new();

    // Negative returns (losing strategy)
    let returns = vec![-0.01, -0.02, 0.005, -0.015, -0.008];
    let sharpe = optimizer.calculate_sharpe(&returns);

    // Should be negative
    assert!(sharpe < 0.0);
    assert!(sharpe.is_finite());
}

#[test]
fn test_sharpe_ratio_zero_volatility() {
    let optimizer = BarrierOptimizer::new();

    // All returns are identical (zero volatility)
    let returns = vec![0.01, 0.01, 0.01, 0.01, 0.01];
    let sharpe = optimizer.calculate_sharpe(&returns);

    // Should handle gracefully (return 0.0 or large value)
    assert!(sharpe.is_finite());
}

#[test]
fn test_sharpe_ratio_empty_returns() {
    let optimizer = BarrierOptimizer::new();

    let returns = vec![];
    let sharpe = optimizer.calculate_sharpe(&returns);

    // Should return 0.0 for empty data
    assert_relative_eq!(sharpe, 0.0, epsilon = 1e-6);
}

#[test]
fn test_backtest_params_simple_uptrend() {
    let optimizer = BarrierOptimizer::new();

    // Simple uptrend: prices increase steadily
    let prices = vec![100.0, 101.0, 102.0, 103.0, 104.0, 105.0];
    let params = BarrierParams::new(2.0, 1.0, 3);

    let sharpe = optimizer.backtest_params(&params, &prices);

    // Uptrend should produce non-negative Sharpe (may be 0.0 with very few data points)
    assert!(sharpe >= 0.0, "uptrend sharpe should be >= 0.0, got {}", sharpe);
    assert!(sharpe.is_finite());
}

#[test]
fn test_backtest_params_simple_downtrend() {
    let optimizer = BarrierOptimizer::new();

    // Simple downtrend: prices decrease steadily
    let prices = vec![105.0, 104.0, 103.0, 102.0, 101.0, 100.0];
    let params = BarrierParams::new(2.0, 1.0, 3);

    let sharpe = optimizer.backtest_params(&params, &prices);

    // Downtrend should produce negative or low Sharpe
    assert!(sharpe.is_finite());
}

#[test]
fn test_backtest_params_volatile_market() {
    let optimizer = BarrierOptimizer::new();

    // Volatile market: prices oscillate
    let prices = vec![100.0, 105.0, 98.0, 107.0, 95.0, 110.0];
    let params = BarrierParams::new(2.0, 1.0, 3);

    let sharpe = optimizer.backtest_params(&params, &prices);

    // Should handle volatility without crashing
    assert!(sharpe.is_finite());
}

#[test]
fn test_backtest_params_insufficient_data() {
    let optimizer = BarrierOptimizer::new();

    // Too few prices for meaningful backtest
    let prices = vec![100.0, 101.0];
    let params = BarrierParams::new(2.0, 1.0, 10); // horizon longer than data

    let sharpe = optimizer.backtest_params(&params, &prices);

    // Should return 0.0 or handle gracefully
    assert!(sharpe.is_finite());
}

#[test]
fn test_optimize_simple_data() {
    let optimizer = BarrierOptimizer::new();

    // Simple uptrend data
    let prices = vec![
        100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0,
    ];

    let result = optimizer.optimize(&prices);

    // Should find optimal parameters
    assert!(result.best_params.profit_factor > 0.0);
    assert!(result.best_params.stop_factor > 0.0);
    assert!(result.best_params.time_horizon > 0);
    assert!(result.best_sharpe.is_finite());
    assert!(result.evaluations > 0);
    // duration_ms may be 0 on fast machines with small data — just check finite
    let _ = result.duration_ms;
}

#[test]
fn test_optimize_returns_best_sharpe() {
    let optimizer = BarrierOptimizer::new();

    // Generate synthetic data with known pattern
    let prices: Vec<f64> = (0..50).map(|i| 100.0 + (i as f64) * 0.5).collect();

    let result = optimizer.optimize(&prices);

    // Best Sharpe should be better than worst case
    assert!(result.best_sharpe > -10.0); // Sanity check

    // Verify the selected parameters are within search space
    let profit_range = optimizer.profit_range();
    let stop_range = optimizer.stop_range();
    let horizon_range = optimizer.horizon_range();

    assert!(profit_range.contains(&result.best_params.profit_factor));
    assert!(stop_range.contains(&result.best_params.stop_factor));
    assert!(horizon_range.contains(&result.best_params.time_horizon));
}

#[test]
fn test_optimize_evaluates_all_combinations() {
    let optimizer = BarrierOptimizer::new();

    // Small dataset
    let prices: Vec<f64> = (0..20).map(|i| 100.0 + i as f64).collect();

    let result = optimizer.optimize(&prices);

    // Should evaluate all combinations (5 * 4 * 4 = 80)
    assert_eq!(result.evaluations, 80);
}

#[test]
fn test_optimize_consistent_results() {
    let optimizer = BarrierOptimizer::new();

    // Same data should produce same results
    let prices: Vec<f64> = (0..30).map(|i| 100.0 + (i as f64) * 0.3).collect();

    let result1 = optimizer.optimize(&prices);
    let result2 = optimizer.optimize(&prices);

    assert_relative_eq!(result1.best_sharpe, result2.best_sharpe, epsilon = 1e-6);
    assert_eq!(
        result1.best_params.profit_factor,
        result2.best_params.profit_factor
    );
    assert_eq!(
        result1.best_params.stop_factor,
        result2.best_params.stop_factor
    );
    assert_eq!(
        result1.best_params.time_horizon,
        result2.best_params.time_horizon
    );
}

#[test]
fn test_optimize_performance_100_combinations() {
    // Custom optimizer with fewer combinations for performance test
    let profit_range = vec![1.0, 1.5, 2.0, 2.5, 3.0]; // 5
    let stop_range = vec![0.5, 1.0, 1.5, 2.0]; // 4
    let horizon_range = vec![5, 10, 20, 30, 40]; // 5
                                                 // Total: 5 * 4 * 5 = 100 combinations

    let optimizer = BarrierOptimizer::with_ranges(profit_range, stop_range, horizon_range);

    // Generate sufficient data
    let prices: Vec<f64> = (0..100).map(|i| 100.0 + (i as f64) * 0.2).collect();

    let start = Instant::now();
    let result = optimizer.optimize(&prices);
    let duration = start.elapsed();

    // Must complete in under 10 seconds
    assert!(
        duration.as_secs() < 10,
        "Optimization took {:?}, expected < 10s",
        duration
    );
    assert_eq!(result.evaluations, 100);
    // duration_ms may be 0 on fast machines with small data — just check finite
    let _ = result.duration_ms;
}

#[test]
fn test_optimize_cross_validation_walk_forward() {
    let optimizer = BarrierOptimizer::new();

    // Generate data with trend reversal
    let mut prices = Vec::new();
    // First half: uptrend
    for i in 0..25 {
        prices.push(100.0 + i as f64);
    }
    // Second half: downtrend
    for i in 0..25 {
        prices.push(125.0 - i as f64);
    }

    // Split into train/test
    let split_idx = prices.len() / 2;
    let train_prices = &prices[..split_idx];
    let test_prices = &prices[split_idx..];

    // Optimize on training data
    let train_result = optimizer.optimize(train_prices);

    // Backtest on test data with optimal params
    let test_sharpe = optimizer.backtest_params(&train_result.best_params, test_prices);

    // Test Sharpe should be finite (may be negative due to reversal)
    assert!(test_sharpe.is_finite());
}

#[test]
fn test_optimization_result_display() {
    let params = BarrierParams::new(2.0, 1.0, 10);
    let result = OptimizationResult {
        best_params: params,
        best_sharpe: 1.5,
        evaluations: 80,
        duration_ms: 1234,
    };

    // Should implement Display trait
    let display_str = format!("{}", result);
    assert!(display_str.contains("2.0"));
    assert!(display_str.contains("1.0"));
    assert!(display_str.contains("10"));
    assert!(display_str.contains("1.5"));
}

#[test]
fn test_barrier_params_clone() {
    let params = BarrierParams::new(2.0, 1.0, 10);
    let cloned = params.clone();

    assert_relative_eq!(params.profit_factor, cloned.profit_factor, epsilon = 1e-6);
    assert_relative_eq!(params.stop_factor, cloned.stop_factor, epsilon = 1e-6);
    assert_eq!(params.time_horizon, cloned.time_horizon);
}

#[test]
fn test_optimization_with_nan_prices() {
    let optimizer = BarrierOptimizer::new();

    // Prices with NaN values
    let prices = vec![100.0, f64::NAN, 102.0, 103.0];

    let result = optimizer.optimize(&prices);

    // Should handle NaN gracefully (skip or filter)
    assert!(result.best_sharpe.is_finite());
}

#[test]
fn test_optimization_with_infinite_prices() {
    let optimizer = BarrierOptimizer::new();

    // Prices with infinity
    let prices = vec![100.0, f64::INFINITY, 102.0, 103.0];

    let result = optimizer.optimize(&prices);

    // Should handle infinity gracefully
    assert!(result.best_sharpe.is_finite());
}

#[test]
fn test_optimize_parallel_consistency() {
    // Test that optimization is deterministic (no race conditions)
    let optimizer = BarrierOptimizer::new();
    let prices: Vec<f64> = (0..50).map(|i| 100.0 + (i as f64) * 0.5).collect();

    let results: Vec<_> = (0..5).map(|_| optimizer.optimize(&prices)).collect();

    // All results should be identical
    let first_sharpe = results[0].best_sharpe;
    for result in &results {
        assert_relative_eq!(result.best_sharpe, first_sharpe, epsilon = 1e-6);
    }
}

#[test]
fn test_backtest_params_respects_time_horizon() {
    let optimizer = BarrierOptimizer::new();

    let prices: Vec<f64> = (0..100).map(|i| 100.0 + (i as f64) * 0.1).collect();

    // Short horizon vs long horizon should produce different results
    let short_params = BarrierParams::new(2.0, 1.0, 5);
    let long_params = BarrierParams::new(2.0, 1.0, 30);

    let short_sharpe = optimizer.backtest_params(&short_params, &prices);
    let long_sharpe = optimizer.backtest_params(&long_params, &prices);

    // Results should differ (unless market is perfectly linear)
    assert!(short_sharpe.is_finite());
    assert!(long_sharpe.is_finite());
}

#[test]
fn test_optimize_empty_prices() {
    let optimizer = BarrierOptimizer::new();

    let prices = vec![];
    let result = optimizer.optimize(&prices);

    // Should handle gracefully, return default or zero Sharpe
    assert!(result.best_sharpe.is_finite());
    assert_eq!(result.evaluations, 80); // Still evaluates all combinations
}

#[test]
fn test_optimize_single_price() {
    let optimizer = BarrierOptimizer::new();

    let prices = vec![100.0];
    let result = optimizer.optimize(&prices);

    // Should handle gracefully
    assert!(result.best_sharpe.is_finite());
}

#[test]
fn test_barrier_params_default() {
    let params = BarrierParams::default();

    // Default should be reasonable
    assert!(params.profit_factor > 0.0);
    assert!(params.stop_factor > 0.0);
    assert!(params.time_horizon > 0);
}
