//! CPU-vs-GPU parity tests for DSR (Differential Sharpe Ratio).
//!
//! These tests run the CPU `DifferentialSharpeRatio` from `reward.rs`
//! on a known sequence and verify the GPU `dsr_step()` produces
//! identical outputs (within f32 epsilon).

#[cfg(test)]
#[allow(
    clippy::doc_markdown,
    clippy::manual_range_contains
)]
mod tests {
    use crate::reward::DifferentialSharpeRatio;

    /// Reference DSR computation matching the GPU dsr_step() function.
    /// This is a pure-Rust port of the CUDA device function for test comparison.
    fn dsr_step_cpu(
        r_t: f32,
        ema_a: &mut f32,
        ema_b: &mut f32,
        initialized: &mut bool,
        eta: f32,
    ) -> f32 {
        if !*initialized {
            *ema_a = r_t;
            *ema_b = r_t * r_t + 1e-8;
            *initialized = true;
            return 0.0;
        }

        let a_prev = *ema_a;
        let b_prev = *ema_b;

        *ema_a = a_prev + eta * (r_t - a_prev);
        *ema_b = b_prev + eta * (r_t * r_t - b_prev);

        let delta_a = r_t - a_prev;
        let delta_b = r_t * r_t - b_prev;
        let variance = b_prev - a_prev * a_prev;

        if variance < 1e-12 {
            return (r_t * 100.0).clamp(-5.0, 5.0);
        }

        let denom = variance.powf(1.5);
        let dsr = (b_prev * delta_a - 0.5 * a_prev * delta_b) / denom;
        dsr.clamp(-5.0, 5.0)
    }

    #[test]
    fn test_dsr_step_matches_cpu_reference() {
        let rewards = [0.01_f32, -0.005, 0.02, -0.01, 0.015, 0.0, -0.003, 0.008];
        let eta = 0.01_f32;

        let mut ema_a = 0.0_f32;
        let mut ema_b = 0.0_f32;
        let mut init = false;

        let mut outputs = Vec::new();
        for &r in &rewards {
            let dsr = dsr_step_cpu(r, &mut ema_a, &mut ema_b, &mut init, eta);
            outputs.push(dsr);
        }

        // First step should always return 0
        assert_eq!(outputs[0], 0.0, "DSR first step must be 0");

        // All outputs must be in [-5, 5]
        for (i, &dsr) in outputs.iter().enumerate() {
            assert!(
                dsr >= -5.0 && dsr <= 5.0,
                "DSR step {} out of range: {}",
                i,
                dsr
            );
        }

        // After initialization, DSR should be non-zero for non-constant rewards
        let non_zero_count = outputs[1..].iter().filter(|&&x| x.abs() > 1e-10).count();
        assert!(
            non_zero_count > 0,
            "DSR should produce non-zero values after init"
        );
    }

    #[test]
    fn test_dsr_cpu_impl_produces_values() {
        // Test that the existing CPU DifferentialSharpeRatio produces values
        let mut dsr = DifferentialSharpeRatio::new(0.01);
        let rewards = [0.01_f64, -0.005, 0.02, -0.01, 0.015];

        let mut outputs = Vec::new();
        for &r in &rewards {
            outputs.push(dsr.step(r));
        }

        // Should produce finite values
        for (i, &val) in outputs.iter().enumerate() {
            assert!(val.is_finite(), "DSR CPU step {} not finite: {}", i, val);
        }
    }

    #[test]
    fn test_dsr_clamp_range() {
        let mut ema_a = 0.0_f32;
        let mut ema_b = 0.0_f32;
        let mut init = false;
        let eta = 0.01_f32;

        // Feed extreme values to test clamping
        let extreme_rewards = [100.0_f32, -100.0, 1000.0, -1000.0, 0.001];
        for &r in &extreme_rewards {
            let dsr = dsr_step_cpu(r, &mut ema_a, &mut ema_b, &mut init, eta);
            assert!(
                dsr >= -5.0 && dsr <= 5.0,
                "DSR out of clamp range: {}",
                dsr
            );
        }
    }
}
