#![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 GELU and Mish activation functions in DQN networks

// candle eliminated — test uses native APIs

use ml::dqn::rainbow_network::ActivationType;
use ml::dqn::network::{QNetwork, QNetworkConfig};
use ml::MLError;

#[test]
fn test_activation_type_default() {
    let activation = ActivationType::default();
    assert!(matches!(activation, ActivationType::ReLU));
}

#[test]
fn test_gelu_activation_qnetwork() -> Result<(), MLError> {
    let config = QNetworkConfig {
        state_dim: 4,
        num_actions: 3,
        hidden_dims: vec![8, 4],
        use_gpu: true,
        ..QNetworkConfig::default()
    };

    let network = QNetwork::new(config)?;

    // Verify network can be created (QNetworkConfig uses LeakyReLU, not GELU)
    let state = vec![1.0, 0.0, -1.0, 0.5];
    let q_values = network.forward(&state)?;

    // Verify output shape
    assert_eq!(q_values.len(), 3);

    // Verify all Q-values are finite (no NaN or Inf)
    for &q in &q_values {
        assert!(q.is_finite(), "Q-value should be finite, got: {}", q);
    }

    Ok(())
}

#[test]
fn test_mish_activation_qnetwork() -> Result<(), MLError> {
    let config = QNetworkConfig {
        state_dim: 4,
        num_actions: 3,
        hidden_dims: vec![8, 4],
        use_gpu: true,
        ..QNetworkConfig::default()
    };

    let network = QNetwork::new(config)?;

    // Verify network can be created (QNetworkConfig uses LeakyReLU, not Mish)
    let state = vec![1.0, 0.0, -1.0, 0.5];
    let q_values = network.forward(&state)?;

    // Verify output shape
    assert_eq!(q_values.len(), 3);

    // Verify all Q-values are finite (no NaN or Inf)
    for &q in &q_values {
        assert!(q.is_finite(), "Q-value should be finite, got: {}", q);
    }

    Ok(())
}

#[test]
fn test_gelu_mathematical_properties() -> Result<(), MLError> {
    // Host-side f32 math — no GPU tensor needed for scalar activation checks.
    // GELU(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))

    let gelu = |x: f32| -> f32 {
        let sqrt_2_over_pi = (2.0_f32 / std::f32::consts::PI).sqrt();
        let inner = sqrt_2_over_pi * (x + 0.044715 * x * x * x);
        0.5 * x * (1.0 + inner.tanh())
    };

    // Test GELU(0) ~ 0
    let result = gelu(0.0);
    assert!(result.abs() < 0.01, "GELU(0) should be close to 0, got: {}", result);

    // Test GELU is approximately identity for large positive values
    let result_large = gelu(5.0);
    assert!(result_large > 4.9, "GELU(5.0) should be close to 5.0, got: {}", result_large);

    // Test GELU is approximately 0 for large negative values
    let result_neg = gelu(-5.0);
    assert!(result_neg.abs() < 0.1, "GELU(-5.0) should be close to 0, got: {}", result_neg);

    Ok(())
}

#[test]
fn test_mish_mathematical_properties() -> Result<(), MLError> {
    // Host-side f32 math — no GPU tensor needed for scalar activation checks.
    // Mish(x) = x * tanh(softplus(x)) = x * tanh(ln(1 + exp(x)))

    let mish = |x: f32| -> f32 {
        let softplus = (1.0_f32 + x.exp()).ln();
        x * softplus.tanh()
    };

    // Test Mish(0) ~ 0 (small positive value due to softplus)
    let result = mish(0.0);
    assert!(result.abs() < 0.1, "Mish(0) should be close to 0, got: {}", result);

    // Test Mish preserves sign for large positive values
    let result_large = mish(5.0);
    assert!(result_large > 4.0, "Mish(5.0) should be close to 5.0, got: {}", result_large);

    Ok(())
}

#[test]
fn test_all_activations_qnetwork() -> Result<(), MLError> {
    // QNetworkConfig only supports LeakyReLU activation (hardcoded in network.rs)
    // Testing that the network works correctly with its fixed activation
    let config = QNetworkConfig {
        state_dim: 4,
        num_actions: 3,
        hidden_dims: vec![8],
        use_gpu: true,
        ..QNetworkConfig::default()
    };

    let network = QNetwork::new(config)?;
    let state = vec![1.0, 0.0, -1.0, 0.5];
    let q_values = network.forward(&state)?;

    assert_eq!(q_values.len(), 3);
    for &q in &q_values {
        assert!(q.is_finite(), "Non-finite Q-value");
    }

    Ok(())
}

#[test]
fn test_batch_forward_with_leaky_relu() -> Result<(), MLError> {
    let config = QNetworkConfig {
        state_dim: 4,
        num_actions: 3,
        hidden_dims: vec![8],
        use_gpu: true,
        ..QNetworkConfig::default()
    };

    let network = QNetwork::new(config)?;

    let states = vec![
        vec![1.0, 0.0, -1.0, 0.5],
        vec![0.5, 1.0, -0.5, 0.0],
    ];

    let q_values_batch = network.forward_batch(&states)?;

    assert_eq!(q_values_batch.len(), 2);
    assert_eq!(q_values_batch[0].len(), 3);
    assert_eq!(q_values_batch[1].len(), 3);

    for batch in &q_values_batch {
        for &q in batch {
            assert!(q.is_finite());
        }
    }

    Ok(())
}
