#![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 Diversity Penalty Unit Tests
//!
//! Comprehensive tests for Fix #4: Entropy-based diversity penalty in hyperopt objective function.
//!
//! This test suite validates:
//! 1. Shannon entropy calculation accuracy (uniform, extreme, moderate bias)
//! 2. Diversity penalty application (threshold at entropy < 0.5)
//! 3. Edge cases (empty distribution, boundary conditions)
//!
//! ## Context
//!
//! Fix #4 addresses the critical issue of 99.4% SELL bias discovered in DQN training.
//! The diversity penalty penalizes configurations where action entropy falls below 0.5
//! (indicating extreme action homogeneity).
//!
//! ## Penalty Formula
//!
//! ```text
//! if entropy < 0.5:
//!     penalty = -10.0  (catastrophic)
//! else:
//!     penalty = 0.0    (acceptable diversity)
//! ```
//!
//! ## Shannon Entropy Formula
//!
//! ```text
//! H(X) = -Σ p(x) * log2(p(x))
//! ```
//!
//! Where:
//! - p(x) = probability of action x (count / total)
//! - log2(p) = logarithm base 2
//! - H(X) = entropy in bits
//!
//! Expected ranges:
//! - Uniform (33%, 33%, 33%): H ≈ 1.585 bits (max for 3 actions)
//! - Extreme bias (100%, 0%, 0%): H = 0.0 bits (min)
//! - Moderate bias (70%, 20%, 10%): H ≈ 1.16 bits

#[cfg(test)]
mod dqn_diversity_penalty_tests {
    use tracing::info;

    /// Helper function to calculate Shannon entropy
    ///
    /// This mirrors the entropy calculation used in the hyperopt objective function.
    /// Calculates entropy in bits using base-2 logarithm.
    ///
    /// # Arguments
    ///
    /// * `action_counts` - Array of action counts [BUY, SELL, HOLD]
    ///
    /// # Returns
    ///
    /// Entropy in bits (0.0 = all same action, 1.585 ≈ uniform distribution)
    fn calculate_action_entropy(action_counts: &[usize]) -> f64 {
        let total: usize = action_counts.iter().sum();
        if total == 0 {
            return 0.0;
        }

        let mut entropy = 0.0;
        for &count in action_counts {
            if count > 0 {
                let p = count as f64 / total as f64;
                entropy -= p * p.log2();
            }
        }
        entropy
    }

    /// Helper to calculate diversity penalty
    ///
    /// # Arguments
    ///
    /// * `entropy` - Shannon entropy of action distribution
    ///
    /// # Returns
    ///
    /// Penalty value (-10.0 if entropy < 0.5, else 0.0)
    fn calculate_diversity_penalty(entropy: f64) -> f64 {
        if entropy < 0.5 {
            -10.0
        } else {
            0.0
        }
    }

    // ========================================================================
    // Entropy Calculation Tests
    // ========================================================================

    #[test]
    fn test_entropy_uniform_distribution() {
        // BUY=100, SELL=100, HOLD=100 (33.3% each)
        let action_counts = [100, 100, 100];
        let entropy = calculate_action_entropy(&action_counts);

        // Uniform distribution: entropy = log2(3) ≈ 1.585
        assert!(
            (entropy - 1.585).abs() < 0.001,
            "Expected entropy ≈ 1.585 for uniform distribution, got {:.4}",
            entropy
        );
    }

    #[test]
    fn test_entropy_extreme_bias() {
        // SELL=1000, BUY=0, HOLD=0 (100% SELL)
        let action_counts = [0, 1000, 0];
        let entropy = calculate_action_entropy(&action_counts);

        // All actions the same: entropy = 0.0
        assert_eq!(
            entropy, 0.0,
            "Expected entropy = 0.0 for 100% bias, got {:.4}",
            entropy
        );
    }

    #[test]
    fn test_entropy_moderate_bias() {
        // SELL=70, BUY=20, HOLD=10 (70% SELL dominance)
        let action_counts = [20, 70, 10];
        let entropy = calculate_action_entropy(&action_counts);

        // Expected: entropy ≈ 1.157 (calculated manually)
        // H = -(0.2*log2(0.2) + 0.7*log2(0.7) + 0.1*log2(0.1))
        // H = -(0.2*(-2.322) + 0.7*(-0.515) + 0.1*(-3.322))
        // H = -(-0.464 - 0.361 - 0.332) = 1.157
        assert!(
            (entropy - 1.157).abs() < 0.02,
            "Expected entropy ≈ 1.16 for 70/20/10 distribution, got {:.4}",
            entropy
        );
    }

    #[test]
    fn test_entropy_two_action_distribution() {
        // BUY=50, SELL=50, HOLD=0 (50/50 split)
        let action_counts = [50, 50, 0];
        let entropy = calculate_action_entropy(&action_counts);

        // Two equal actions: entropy = log2(2) = 1.0
        assert!(
            (entropy - 1.0).abs() < 0.001,
            "Expected entropy = 1.0 for 50/50 split, got {:.4}",
            entropy
        );
    }

    // ========================================================================
    // Penalty Application Tests
    // ========================================================================

    #[test]
    fn test_penalty_balanced_trial() {
        // Balanced distribution (BUY=30%, SELL=30%, HOLD=40%)
        let action_counts = [30, 30, 40];
        let entropy = calculate_action_entropy(&action_counts);
        let penalty = calculate_diversity_penalty(entropy);

        assert!(
            entropy >= 0.5,
            "Balanced trial should have entropy ≥ 0.5, got {:.4}",
            entropy
        );
        assert_eq!(
            penalty, 0.0,
            "Balanced trial should have NO penalty (entropy={:.4})",
            entropy
        );
    }

    #[test]
    fn test_penalty_moderately_biased_trial() {
        // Moderate bias (SELL=60%, BUY=25%, HOLD=15%)
        let action_counts = [25, 60, 15];
        let entropy = calculate_action_entropy(&action_counts);
        let penalty = calculate_diversity_penalty(entropy);

        assert!(
            entropy >= 0.5,
            "Moderate bias (60%) should have entropy ≥ 0.5, got {:.4}",
            entropy
        );
        assert_eq!(
            penalty, 0.0,
            "Moderate bias should have NO penalty (entropy={:.4})",
            entropy
        );
    }

    #[test]
    fn test_penalty_highly_biased_trial() {
        // High bias (SELL=95%, BUY=3%, HOLD=2%)
        // This produces entropy ≈ 0.33, well below 0.5 threshold
        let action_counts = [3, 95, 2];
        let entropy = calculate_action_entropy(&action_counts);
        let penalty = calculate_diversity_penalty(entropy);

        assert!(
            entropy < 0.5,
            "High bias (95%) should have entropy < 0.5, got {:.4}",
            entropy
        );
        assert_eq!(
            penalty, -10.0,
            "High bias should receive -10.0 penalty (entropy={:.4})",
            entropy
        );
    }

    #[test]
    fn test_penalty_extreme_bias_trial() {
        // Extreme bias (SELL=99.4%, BUY=0.6%, HOLD=0%)
        // This mirrors the actual bug discovered in Wave 3-A1
        let action_counts = [6, 994, 0];
        let entropy = calculate_action_entropy(&action_counts);
        let penalty = calculate_diversity_penalty(entropy);

        assert!(
            entropy < 0.5,
            "Extreme bias (99.4%) should have entropy < 0.5, got {:.4}",
            entropy
        );
        assert_eq!(
            penalty, -10.0,
            "Extreme bias should receive -10.0 penalty (entropy={:.4})",
            entropy
        );
    }

    // ========================================================================
    // Edge Case Tests
    // ========================================================================

    #[test]
    fn test_entropy_empty_distribution() {
        // Edge case: no actions recorded
        let action_counts = [0, 0, 0];
        let entropy = calculate_action_entropy(&action_counts);
        let penalty = calculate_diversity_penalty(entropy);

        assert_eq!(entropy, 0.0, "Empty distribution should have entropy = 0.0");
        assert_eq!(
            penalty, -10.0,
            "Empty distribution should receive -10.0 penalty"
        );
    }

    #[test]
    fn test_penalty_boundary_case() {
        // Find action counts that produce entropy ≈ 0.5 (boundary)
        // Through trial and error:
        // SELL=92%, BUY=6%, HOLD=2% gives entropy ≈ 0.47 (below threshold)
        // SELL=85%, BUY=10%, HOLD=5% gives entropy ≈ 0.71 (above threshold)

        // Test below threshold
        let action_counts_below = [6, 92, 2];
        let entropy_below = calculate_action_entropy(&action_counts_below);
        let penalty_below = calculate_diversity_penalty(entropy_below);

        info!(entropy = entropy_below, penalty = penalty_below, "Boundary case (below)");

        assert!(
            entropy_below < 0.5,
            "92/6/2 distribution should have entropy < 0.5, got {:.4}",
            entropy_below
        );
        assert_eq!(
            penalty_below, -10.0,
            "Below threshold: should penalize (entropy={:.4})",
            entropy_below
        );

        // Test above threshold
        let action_counts_above = [10, 85, 5];
        let entropy_above = calculate_action_entropy(&action_counts_above);
        let penalty_above = calculate_diversity_penalty(entropy_above);

        info!(entropy = entropy_above, penalty = penalty_above, "Boundary case (above)");

        assert!(
            entropy_above >= 0.5,
            "85/10/5 distribution should have entropy ≥ 0.5, got {:.4}",
            entropy_above
        );
        assert_eq!(
            penalty_above, 0.0,
            "At/above threshold: should not penalize (entropy={:.4})",
            entropy_above
        );
    }

    #[test]
    fn test_entropy_exact_threshold() {
        // Test with entropy exactly at 0.5 threshold
        // We need to find action counts that produce entropy ≈ 0.5
        // Through calculation: SELL=93%, BUY=5%, HOLD=2% gives entropy ≈ 0.48

        // Test just below threshold (should penalize)
        let action_counts_below = [5, 93, 2];
        let entropy_below = calculate_action_entropy(&action_counts_below);

        info!(entropy = entropy_below, "Below threshold test");

        // Verify entropy is close to but below 0.5
        assert!(
            entropy_below < 0.5,
            "Expected entropy < 0.5 for 93/5/2 distribution, got {:.4}",
            entropy_below
        );

        let penalty_below = calculate_diversity_penalty(entropy_below);
        assert_eq!(
            penalty_below, -10.0,
            "Just below threshold (entropy={:.4}): should penalize",
            entropy_below
        );

        // Test just above threshold (should not penalize)
        // SELL=87%, BUY=9%, HOLD=4% gives entropy ≈ 0.61
        let action_counts_above = [9, 87, 4];
        let entropy_above = calculate_action_entropy(&action_counts_above);

        info!(entropy = entropy_above, "Above threshold test");

        assert!(
            entropy_above >= 0.5,
            "Expected entropy >= 0.5 for 87/9/4 distribution, got {:.4}",
            entropy_above
        );

        let penalty_above = calculate_diversity_penalty(entropy_above);
        assert_eq!(
            penalty_above, 0.0,
            "Just above threshold (entropy={:.4}): should not penalize",
            entropy_above
        );
    }

    // ========================================================================
    // Additional Validation Tests
    // ========================================================================

    #[test]
    fn test_entropy_mathematical_properties() {
        // Test 1: Entropy is symmetric (order doesn't matter)
        let counts_1 = [30, 40, 30];
        let counts_2 = [40, 30, 30];
        let counts_3 = [30, 30, 40];

        let entropy_1 = calculate_action_entropy(&counts_1);
        let entropy_2 = calculate_action_entropy(&counts_2);
        let entropy_3 = calculate_action_entropy(&counts_3);

        assert!(
            (entropy_1 - entropy_2).abs() < 1e-10,
            "Entropy should be symmetric: {:.4} != {:.4}",
            entropy_1,
            entropy_2
        );
        assert!(
            (entropy_1 - entropy_3).abs() < 1e-10,
            "Entropy should be symmetric: {:.4} != {:.4}",
            entropy_1,
            entropy_3
        );

        // Test 2: Maximum entropy for 3 actions is log2(3) ≈ 1.585
        let uniform = [100, 100, 100];
        let max_entropy = calculate_action_entropy(&uniform);

        assert!(
            max_entropy <= 1.585 + 0.001,
            "Maximum entropy for 3 actions should be ≤ log2(3) ≈ 1.585, got {:.4}",
            max_entropy
        );

        // Test 3: Minimum entropy is 0.0 (all same action)
        let extreme = [0, 1000, 0];
        let min_entropy = calculate_action_entropy(&extreme);

        assert_eq!(
            min_entropy, 0.0,
            "Minimum entropy should be 0.0 for uniform action, got {:.4}",
            min_entropy
        );
    }

    #[test]
    fn test_penalty_consistency() {
        // Test that penalty is consistent across different action counts with same proportions

        // 90/7/3 proportions
        let small_scale = [7, 90, 3];
        let large_scale = [70, 900, 30];

        let entropy_small = calculate_action_entropy(&small_scale);
        let entropy_large = calculate_action_entropy(&large_scale);

        assert!(
            (entropy_small - entropy_large).abs() < 1e-10,
            "Entropy should be scale-invariant: {:.4} != {:.4}",
            entropy_small,
            entropy_large
        );

        let penalty_small = calculate_diversity_penalty(entropy_small);
        let penalty_large = calculate_diversity_penalty(entropy_large);

        assert_eq!(
            penalty_small, penalty_large,
            "Penalty should be consistent across scales"
        );
    }
}
