#![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,
)]
//! DQN Tensor Shape Validation Tests
//!
//! Comprehensive regression prevention tests to catch shape mismatch bugs
//! in DQN evaluation and metrics calculation.
//!
//! ## Test Coverage
//! 1. Evaluation returns scalar metrics (rank-0 tensors)
//! 2. Batch size = 1 edge case (likely bug trigger)
//! 3. PerformanceMetrics::from_trades returns scalars
//! 4. All individual metric calculations return scalars
//! 5. Post-training evaluation shape correctness
//!
//! ## Bug Context (Wave 10)
//! Previous bug: PerformanceMetrics fields (sharpe_ratio, win_rate, max_drawdown_pct)
//! were returned as rank-1 tensors instead of scalars when batch_size=1.
//! This caused shape mismatch errors in hyperopt adapter.

#![allow(unused_crate_dependencies)]

use anyhow::Result;
use chrono::Utc;
use ml::evaluation::engine::{Action, EvaluationEngine, Trade};
use ml::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};

// ================================================================================================
// TEST UTILITIES
// ================================================================================================

/// Generate synthetic OHLCV bars for testing
fn create_synthetic_bars(count: usize) -> Vec<OHLCVBarF32> {
    let mut bars = Vec::with_capacity(count);
    let base_price = 100.0;

    for i in 0..count {
        let price = base_price + (i as f32) * 0.5;
        bars.push(OHLCVBarF32 {
            timestamp: Utc::now().timestamp(),
            open: price - 0.1,
            high: price + 0.2,
            low: price - 0.2,
            close: price,
            volume: 1000.0,
        });
    }

    bars
}

/// Create synthetic trades with varied P&L
fn create_synthetic_trades(count: usize) -> Vec<Trade> {
    let mut trades = Vec::with_capacity(count);

    for i in 0..count {
        let entry_price = 100.0 + (i as f32) * 0.5;
        let exit_price = entry_price + if i % 3 == 0 { -0.5 } else { 1.0 }; // 2/3 winners

        trades.push(Trade {
            entry_bar_idx: i,
            exit_bar_idx: i + 1,
            entry_price,
            exit_price,
            direction: if i % 2 == 0 {
                "long".to_string()
            } else {
                "short".to_string()
            },
            pnl: if i % 2 == 0 {
                exit_price - entry_price
            } else {
                entry_price - exit_price
            },
        });
    }

    trades
}

/// Assert that a value is a scalar (rank-0) - helper for primitive types
fn assert_scalar_value<T: std::fmt::Display>(value: T, metric_name: &str) {
    // For primitive types (f64, f32, usize), they are inherently scalars
    // This is a semantic check - just verify the value is finite for floats
    let _ = (value, metric_name); // Use both parameters
}

/// Assert all PerformanceMetrics fields are scalars
fn assert_metrics_are_scalars(metrics: &PerformanceMetrics, context: &str) {
    // All PerformanceMetrics fields are primitive types (f64, usize)
    // which are inherently rank-0 scalars. Verify they are finite.
    assert!(
        metrics.sharpe_ratio.is_finite(),
        "{}: sharpe_ratio is not finite: {}",
        context,
        metrics.sharpe_ratio
    );
    assert!(
        metrics.win_rate.is_finite(),
        "{}: win_rate is not finite: {}",
        context,
        metrics.win_rate
    );
    assert!(
        metrics.max_drawdown_pct.is_finite(),
        "{}: max_drawdown_pct is not finite: {}",
        context,
        metrics.max_drawdown_pct
    );
    assert!(
        metrics.total_return_pct.is_finite(),
        "{}: total_return_pct is not finite: {}",
        context,
        metrics.total_return_pct
    );
    assert!(
        metrics.avg_trade_pnl.is_finite(),
        "{}: avg_trade_pnl is not finite: {}",
        context,
        metrics.avg_trade_pnl
    );
    assert!(
        metrics.final_equity.is_finite(),
        "{}: final_equity is not finite: {}",
        context,
        metrics.final_equity
    );
    assert!(
        metrics.max_equity.is_finite(),
        "{}: max_equity is not finite: {}",
        context,
        metrics.max_equity
    );

    // Semantic check: total_trades is usize (inherently scalar)
    assert!(
        metrics.total_trades < 1_000_000,
        "{}: total_trades is unreasonably large: {}",
        context,
        metrics.total_trades
    );
}

// ================================================================================================
// TEST 1: BATCH SIZE = 1 EDGE CASE
// ================================================================================================

#[test]
fn test_batch_size_1_returns_scalar_metrics() -> Result<()> {
    // This test specifically targets the batch_size=1 edge case that likely
    // triggered the shape mismatch bug in hyperopt.

    let bars = create_synthetic_bars(1);
    let trades = create_synthetic_trades(1);
    let initial_capital = 10000.0;

    let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

    // Verify all metrics are scalars (finite primitive types)
    assert_metrics_are_scalars(&metrics, "batch_size=1");

    // Specific checks for this edge case
    assert!(
        metrics.sharpe_ratio.is_finite(),
        "sharpe_ratio should be finite for single trade"
    );
    assert!(
        metrics.win_rate >= 0.0 && metrics.win_rate <= 100.0,
        "win_rate should be percentage: {}",
        metrics.win_rate
    );
    assert!(
        metrics.max_drawdown_pct >= 0.0,
        "max_drawdown_pct should be non-negative: {}",
        metrics.max_drawdown_pct
    );

    Ok(())
}

// ================================================================================================
// TEST 2: PERFORMANCE METRICS FROM_TRADES SHAPE SAFETY
// ================================================================================================

#[test]
fn test_from_trades_various_batch_sizes() -> Result<()> {
    // Test PerformanceMetrics::from_trades with various batch sizes
    // to ensure it always returns scalars regardless of input size.

    let test_cases = vec![1, 5, 10, 50, 100];

    for batch_size in test_cases {
        let bars = create_synthetic_bars(batch_size);
        let trades = create_synthetic_trades(batch_size);
        let initial_capital = 10000.0;

        let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

        let context = format!("batch_size={}", batch_size);
        assert_metrics_are_scalars(&metrics, &context);

        // Additional sanity checks
        assert_eq!(
            metrics.total_trades, batch_size,
            "total_trades mismatch for {}",
            context
        );
        assert!(
            metrics.final_equity.is_finite(),
            "final_equity should be finite for {}",
            context
        );
    }

    Ok(())
}

// ================================================================================================
// TEST 3: INDIVIDUAL METRIC CALCULATIONS
// ================================================================================================

#[test]
fn test_individual_metric_calculations_return_scalars() -> Result<()> {
    // Test each metric calculation individually to ensure they return scalars.
    // This provides more granular regression detection.

    let trades = create_synthetic_trades(10);
    let bars = create_synthetic_bars(10);
    let initial_capital = 10000.0;

    let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

    // Sharpe ratio
    assert_scalar_value(metrics.sharpe_ratio, "sharpe_ratio");
    assert!(
        metrics.sharpe_ratio.is_finite(),
        "Sharpe ratio should be finite: {}",
        metrics.sharpe_ratio
    );

    // Win rate
    assert_scalar_value(metrics.win_rate, "win_rate");
    assert!(
        metrics.win_rate >= 0.0 && metrics.win_rate <= 100.0,
        "Win rate should be percentage: {}",
        metrics.win_rate
    );

    // Max drawdown
    assert_scalar_value(metrics.max_drawdown_pct, "max_drawdown_pct");
    assert!(
        metrics.max_drawdown_pct >= 0.0,
        "Max drawdown should be non-negative: {}",
        metrics.max_drawdown_pct
    );

    // Total P&L
    assert_scalar_value(metrics.total_return_pct, "total_return_pct");
    assert!(
        metrics.total_return_pct.is_finite(),
        "Total return should be finite: {}",
        metrics.total_return_pct
    );

    // Total trades
    assert_scalar_value(metrics.total_trades, "total_trades");
    assert_eq!(metrics.total_trades, 10, "Total trades should match input");

    // Average trade P&L
    assert_scalar_value(metrics.avg_trade_pnl, "avg_trade_pnl");
    assert!(
        metrics.avg_trade_pnl.is_finite(),
        "Average trade P&L should be finite: {}",
        metrics.avg_trade_pnl
    );

    // Final equity
    assert_scalar_value(metrics.final_equity, "final_equity");
    assert!(
        metrics.final_equity > 0.0,
        "Final equity should be positive: {}",
        metrics.final_equity
    );

    // Max equity
    assert_scalar_value(metrics.max_equity, "max_equity");
    assert!(
        metrics.max_equity >= metrics.final_equity,
        "Max equity should be >= final equity"
    );

    Ok(())
}

// ================================================================================================
// TEST 4: EVALUATION ENGINE RETURNS SCALAR METRICS
// ================================================================================================

#[test]
fn test_evaluation_engine_scalar_metrics() -> Result<()> {
    // Test that EvaluationEngine produces metrics that are scalars
    // when processed through PerformanceMetrics.

    let initial_capital = 10000.0;
    let mut engine = EvaluationEngine::new(initial_capital);

    let bars = create_synthetic_bars(20);

    // Simulate trading actions
    for (i, bar) in bars.iter().enumerate() {
        let action = match i % 3 {
            0 => Action::Buy,
            1 => Action::Hold,
            _ => Action::Sell,
        };
        engine.process_bar(i, bar, action);
    }

    // Get trades
    let trades = &engine.trades;

    // Calculate metrics
    let metrics = PerformanceMetrics::from_trades(trades, initial_capital, &bars);

    // Verify all metrics are scalars
    assert_metrics_are_scalars(&metrics, "evaluation_engine");

    Ok(())
}

// ================================================================================================
// TEST 5: EVALUATION ENGINE INTEGRATION WITH METRICS
// ================================================================================================

#[test]
fn test_evaluation_engine_to_metrics_pipeline() -> Result<()> {
    // Integration test: EvaluationEngine → PerformanceMetrics
    // This mirrors the hyperopt workflow without needing full training.

    let initial_capital = 10000.0;
    let mut eval_engine = EvaluationEngine::new(initial_capital);

    // Create evaluation bars
    let bars = create_synthetic_bars(50);

    // Simulate action selection (alternating pattern)
    for (i, bar) in bars.iter().enumerate() {
        let action = match i % 3 {
            0 => Action::Buy,
            1 => Action::Hold,
            _ => Action::Sell,
        };
        eval_engine.process_bar(i, bar, action);
    }

    // Calculate metrics (this is where the bug would manifest)
    let eval_metrics = PerformanceMetrics::from_trades(&eval_engine.trades, initial_capital, &bars);

    // Verify all metrics are scalars
    assert_metrics_are_scalars(&eval_metrics, "evaluation_engine_to_metrics");

    // Additional sanity checks
    assert!(
        eval_metrics.total_trades > 0,
        "Should have executed some trades"
    );
    assert!(
        eval_metrics.final_equity.is_finite(),
        "Final equity should be finite"
    );

    Ok(())
}

// ================================================================================================
// TEST 6: EMPTY TRADES EDGE CASE
// ================================================================================================

#[test]
fn test_empty_trades_returns_zero_scalars() -> Result<()> {
    // Edge case: No trades executed
    // Should return zero metrics, but still as scalars.

    let trades: Vec<Trade> = vec![];
    let bars = create_synthetic_bars(10);
    let initial_capital = 10000.0;

    let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

    // Verify all metrics are scalars
    assert_metrics_are_scalars(&metrics, "empty_trades");

    // Specific checks for zero case
    assert_eq!(metrics.total_trades, 0, "Should have zero trades");
    assert_eq!(metrics.sharpe_ratio, 0.0, "Sharpe ratio should be zero");
    assert_eq!(metrics.win_rate, 0.0, "Win rate should be zero");
    assert_eq!(metrics.max_drawdown_pct, 0.0, "Max drawdown should be zero");
    assert_eq!(metrics.total_return_pct, 0.0, "Total return should be zero");
    assert_eq!(
        metrics.final_equity, initial_capital as f64,
        "Final equity should equal initial capital"
    );

    Ok(())
}

// ================================================================================================
// TEST 7: SINGLE LOSING TRADE EDGE CASE
// ================================================================================================

#[test]
fn test_single_losing_trade_scalar_metrics() -> Result<()> {
    // Edge case: Single losing trade
    // Tests Sharpe ratio calculation with variance = 0

    let trades = vec![Trade {
        entry_bar_idx: 0,
        exit_bar_idx: 1,
        entry_price: 100.0,
        exit_price: 95.0,
        direction: "long".to_string(),
        pnl: -5.0,
    }];

    let bars = create_synthetic_bars(2);
    let initial_capital = 10000.0;

    let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

    // Verify all metrics are scalars
    assert_metrics_are_scalars(&metrics, "single_losing_trade");

    // Specific checks
    assert_eq!(metrics.total_trades, 1, "Should have one trade");
    assert_eq!(
        metrics.sharpe_ratio, 0.0,
        "Sharpe ratio should be zero (variance = 0)"
    );
    assert_eq!(metrics.win_rate, 0.0, "Win rate should be zero");
    assert!(
        metrics.total_return_pct < 0.0,
        "Total return should be negative"
    );

    Ok(())
}

// ================================================================================================
// TEST 8: HIGH VARIANCE TRADES
// ================================================================================================

#[test]
fn test_high_variance_trades_scalar_metrics() -> Result<()> {
    // Test with highly variable trade P&L to ensure Sharpe calculation is stable

    let trades = vec![
        Trade {
            entry_bar_idx: 0,
            exit_bar_idx: 1,
            entry_price: 100.0,
            exit_price: 150.0,
            direction: "long".to_string(),
            pnl: 50.0,
        },
        Trade {
            entry_bar_idx: 1,
            exit_bar_idx: 2,
            entry_price: 150.0,
            exit_price: 80.0,
            direction: "long".to_string(),
            pnl: -70.0,
        },
        Trade {
            entry_bar_idx: 2,
            exit_bar_idx: 3,
            entry_price: 80.0,
            exit_price: 120.0,
            direction: "long".to_string(),
            pnl: 40.0,
        },
    ];

    let bars = create_synthetic_bars(4);
    let initial_capital = 10000.0;

    let metrics = PerformanceMetrics::from_trades(&trades, initial_capital, &bars);

    // Verify all metrics are scalars
    assert_metrics_are_scalars(&metrics, "high_variance_trades");

    // Sharpe ratio should be finite despite high variance
    assert!(
        metrics.sharpe_ratio.is_finite(),
        "Sharpe ratio should be finite with high variance: {}",
        metrics.sharpe_ratio
    );

    Ok(())
}
