//! Hyperopt smoke tests — validates PSO search → train → evaluate loop.
//!
//! Uses tiny config (3 particles, 2 iterations) with consistent data loading:
//! fxcache with OFI features (MBP-10 + trades), same as all other smoke tests.

use super::helpers::{smoke_params, test_data_dir, feature_cache_dir};

/// PSO hyperopt produces a valid best trial with finite objective.
///
/// Runs a minimal 3-particle, 2-iteration search (6 total trials).
/// Data paths from smoketest TOML for fxcache + OFI consistency.
#[test]
#[ignore] // GPU + real data
fn test_hyperopt_pso_produces_valid_result() -> anyhow::Result<()> {
    use crate::hyperopt::adapters::dqn::DQNTrainer;
    use ml_hyperopt::optimizer::ArgminOptimizer;

    let data_dir = test_data_dir()
        .expect("FOXHUNT_TEST_DATA or test_data/ must exist");

    let p = smoke_params();
    let mut trainer = DQNTrainer::with_buffer_max(&data_dir, p.epochs, p.buffer_size.max(2048))?
        .with_training_profile("dqn-smoketest")
        .with_feature_cache(feature_cache_dir())
        .with_ofi_data_dirs(
            Some(p.mbp10_data_dir.clone()),
            Some(p.trades_data_dir.clone()),
        );

    // Preload fxcache ONCE — shared across all PSO trials via Arc
    trainer.preload_data()?;

    let optimizer = ArgminOptimizer::with_trials(6, 3);
    let result = optimizer.optimize(trainer)?;

    assert!(
        result.best_objective.is_finite(),
        "Hyperopt best_objective is not finite: {}",
        result.best_objective
    );

    assert!(
        !result.all_trials.is_empty(),
        "Hyperopt completed 0 trials"
    );

    tracing::info!(
        best_objective = result.best_objective,
        n_trials = result.all_trials.len(),
        "Hyperopt PSO smoke test PASS"
    );

    Ok(())
}

/// Single hyperopt trial produces backtest metrics (not None).
///
/// Validates the fxcache → train → GPU backtest → Sharpe objective pipeline.
/// If backtest_metrics is None, the objective falls back to 1e6 penalty.
#[test]
#[ignore] // GPU + real data
fn test_hyperopt_trial_produces_backtest_metrics() -> anyhow::Result<()> {
    use crate::hyperopt::adapters::dqn::DQNTrainer;

    let data_dir = test_data_dir()
        .expect("FOXHUNT_TEST_DATA or test_data/ must exist");

    let p = smoke_params();
    let mut trainer = DQNTrainer::with_buffer_max(&data_dir, p.epochs, p.buffer_size.max(2048))?
        .with_training_profile("dqn-smoketest")
        .with_feature_cache(feature_cache_dir())
        .with_ofi_data_dirs(
            Some(p.mbp10_data_dir.clone()),
            Some(p.trades_data_dir.clone()),
        );

    trainer.preload_data()?;

    // Run a single trial with default (midpoint) parameters
    let default_params = <crate::hyperopt::adapters::dqn::DQNParams as Default>::default();
    let metrics = ml_hyperopt::traits::HyperparameterOptimizable::train_with_params(
        &mut trainer, default_params,
    )?;

    // Backtest metrics MUST be present
    assert!(
        metrics.backtest_metrics.is_some(),
        "backtest_metrics is None — GPU backtest evaluator did not produce results. \
         val_data may be empty or backtest window calculation failed."
    );

    let bt = metrics.backtest_metrics.as_ref().unwrap();
    assert!(bt.sharpe_ratio.is_finite(), "Sharpe is not finite: {}", bt.sharpe_ratio);
    assert!(bt.total_trades > 0, "Zero trades in backtest");

    eprintln!(
        "Hyperopt backtest: Sharpe={:.2} Trades={} WinRate={:.1}% MaxDD={:.1}% \
         Loss={:.3} Epochs={} Q={:.4}",
        bt.sharpe_ratio, bt.total_trades, bt.win_rate * 100.0,
        bt.max_drawdown_pct, metrics.train_loss, metrics.epochs_completed,
        metrics.avg_q_value,
    );

    Ok(())
}
