#![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,
)]
//! TDD Tests for Ensemble Intensity in the 14D Hyperopt Search Space
//!
//! Validates that ensemble_intensity is properly included in the 14D search space
//! and that from_continuous / to_continuous round-trip correctly.
//! Individual ensemble params (ensemble_size, beta_variance, etc.) are now fixed
//! in DQNHyperparameters, not in DQNParams.

use ml::hyperopt::adapters::dqn::DQNParams;
use ml::hyperopt::ParameterSpace;
use ml::trainers::dqn::DQNHyperparameters;

#[test]
fn test_dqn_hyperparameters_has_ensemble_fields() {
    // Verify DQNHyperparameters struct has ensemble fields (these live here, not in DQNParams)
    let hyperparams = DQNHyperparameters::conservative();

    let _use_ensemble = hyperparams.use_ensemble_uncertainty;
    let _ensemble_size = hyperparams.ensemble_size;
    let _beta_variance = hyperparams.beta_variance;
    let _beta_disagreement = hyperparams.beta_disagreement;
    let _beta_entropy = hyperparams.beta_entropy;
    let _variance_cap = hyperparams.variance_cap;

    assert!(!hyperparams.use_ensemble_uncertainty, "Default should be disabled for conservative config");
    assert_eq!(hyperparams.ensemble_size, 5, "Default ensemble size should be 5");
    assert!(hyperparams.beta_variance >= 0.0 && hyperparams.beta_variance <= 1.0, "Beta variance should be in [0, 1]");
    assert!(hyperparams.beta_disagreement >= 0.0 && hyperparams.beta_disagreement <= 1.0, "Beta disagreement should be in [0, 1]");
    assert!(hyperparams.beta_entropy >= 0.0 && hyperparams.beta_entropy <= 1.0, "Beta entropy should be in [0, 1]");
    assert!(hyperparams.variance_cap > 0.0, "Variance cap should be positive");
}

#[test]
fn test_dqn_params_has_ensemble_intensity() {
    // DQNParams now has ensemble_intensity (a single scalar) instead of individual ensemble fields
    let params = DQNParams::default();
    assert!(
        params.ensemble_intensity >= 0.0 && params.ensemble_intensity <= 2.0,
        "ensemble_intensity should be in [0.0, 2.0], got {}",
        params.ensemble_intensity
    );
    assert!(
        (params.ensemble_intensity - 1.0).abs() < 1e-6,
        "Default ensemble_intensity should be 1.0 (neutral)"
    );
}

#[test]
fn test_ensemble_search_space_bounds() {
    // 14D search space: 3 breakouts + 5 core families + 6 gen families
    let bounds = DQNParams::continuous_bounds();
    assert_eq!(bounds.len(), 14, "Search space should have 14 continuous parameters");

    // ensemble_intensity is at index 12
    let names = DQNParams::param_names();
    let ens_idx = names.iter().position(|&n| n == "ensemble_intensity")
        .expect("ensemble_intensity should be in param_names");
    assert_eq!(ens_idx, 12, "ensemble_intensity should be at index 12");

    let (lo, hi) = bounds[ens_idx];
    assert_eq!(lo, 0.0, "ensemble_intensity lower bound should be 0.0");
    assert_eq!(hi, 2.0, "ensemble_intensity upper bound should be 2.0");
}

#[test]
fn test_from_continuous_sets_ensemble_intensity() {
    // from_continuous with midpoint values
    let bounds = DQNParams::continuous_bounds();
    let x: Vec<f64> = bounds.iter().map(|(lo, hi)| (lo + hi) / 2.0).collect();
    assert_eq!(x.len(), 14, "Should produce 14D midpoint vector");

    let params = DQNParams::from_continuous(&x).expect("Should convert valid parameters");

    // ensemble_intensity should be the midpoint of [0.0, 2.0] = 1.0
    assert!(
        (params.ensemble_intensity - 1.0).abs() < 1e-6,
        "ensemble_intensity should be 1.0 at midpoint, got {}",
        params.ensemble_intensity
    );
}

#[test]
fn test_from_continuous_rejects_wrong_length() {
    // from_continuous rejects wrong-length input
    let x = vec![0.0; 15]; // Wrong length (should be 14)
    let result = DQNParams::from_continuous(&x);
    assert!(result.is_err(), "Should reject wrong-length (15) input");

    let x2 = vec![0.0; 13]; // Also wrong length
    let result2 = DQNParams::from_continuous(&x2);
    assert!(result2.is_err(), "Should reject wrong-length (13) input");

    // Correct length (14) with valid midpoints should succeed
    let bounds = DQNParams::continuous_bounds();
    let x14: Vec<f64> = bounds.iter().map(|(lo, hi)| (lo + hi) / 2.0).collect();
    let params = DQNParams::from_continuous(&x14).expect("Should accept 14D input");
    assert!(
        (params.ensemble_intensity - 1.0).abs() < 1e-6,
        "ensemble_intensity should be 1.0 at midpoint"
    );
}

#[test]
fn test_to_continuous_roundtrip() {
    // Verify to_continuous includes all 14 fields and round-trips correctly
    let mut params = DQNParams::default();
    params.ensemble_intensity = 1.5;
    params.causal_intensity = 0.8;

    let continuous = params.to_continuous();
    assert_eq!(continuous.len(), 14, "Continuous representation should have 14 values");

    let recovered = DQNParams::from_continuous(&continuous).expect("Should recover from continuous");
    assert!(
        (recovered.ensemble_intensity - 1.5).abs() < 1e-6,
        "ensemble_intensity should survive roundtrip: expected 1.5, got {}",
        recovered.ensemble_intensity
    );
    assert!(
        (recovered.causal_intensity - 0.8).abs() < 1e-6,
        "causal_intensity should survive roundtrip: expected 0.8, got {}",
        recovered.causal_intensity
    );
}

#[test]
fn test_ensemble_intensity_in_param_names() {
    // Verify ensemble_intensity is listed in param_names at position 12
    let names = DQNParams::param_names();
    assert_eq!(names.len(), 14, "param_names should have 14 entries");
    assert_eq!(names[12], "ensemble_intensity", "Index 12 should be ensemble_intensity");
    assert_eq!(names[13], "causal_intensity", "Index 13 should be causal_intensity");
}
