//! Unit tests for Controller A signal computation (CPU only — no CUDA needed).
//!
//! Per spec §3.1 and plan Task 7 Step 3. Validates:
//!   - First-observation bootstrap sets noise_floor directly (per
//!     `pearl_first_observation_bootstrap`).
//!   - Trigger fires when EMA decays below `max(noise_floor/4, noise_floor/16)`.
//!   - ISV-derived hard cap fires under a stable (non-decaying) signal where
//!     dispersion is zero (MAD=0, dispersion_factor=1 → cap=half_life=100).

use ml_alpha::cfc::bucket_routing::ControllerA;

#[test]
fn controller_a_first_obs_bootstrap() {
    let mut ctrl = ControllerA::new();
    let triggered = ctrl.update(0.5, None);
    assert!(!triggered, "first observation must not trigger");
    assert_eq!(
        ctrl.noise_floor, 0.5,
        "first observation sets noise_floor directly"
    );
    assert_eq!(
        ctrl.tau_change_ema, 0.5,
        "first observation seeds EMA directly"
    );
    assert_eq!(ctrl.step_count, 1, "step counter advanced");
}

#[test]
fn controller_a_triggers_when_signal_below_threshold() {
    let mut ctrl = ControllerA::new();
    ctrl.update(1.0, None); // noise_floor = 1.0, threshold = 1.0/4 = 0.25
                            // Simulate signal decay well below noise_floor/4.
    let mut triggered_at: Option<u64> = None;
    for _ in 0..200 {
        if ctrl.update(0.01, None) {
            triggered_at = Some(ctrl.step_count);
            break;
        }
    }
    assert!(
        ctrl.transitioned,
        "should trigger when signal decays well below noise_floor/4"
    );
    assert!(
        triggered_at.is_some(),
        "trigger should fire within the 200-step window"
    );
}

#[test]
fn controller_a_hard_cap_triggers_after_isv_derived_window() {
    let mut ctrl = ControllerA::new();
    ctrl.update(1.0, None);
    // Inject stable signal (no decay) — only hard cap can trigger.
    // With constant signal: median(first100)=1.0, MAD=0 → dispersion_factor=1,
    // half_life=100 (no crossing below noise_floor/2), so cap=100.
    for _ in 0..200 {
        ctrl.update(1.0, None);
    }
    assert!(
        ctrl.transitioned,
        "hard cap must fire by step 200 when signal is stable"
    );
    assert!(
        ctrl.hard_cap_steps.is_some(),
        "hard cap should have been computed at step 100"
    );
}
