//! Quick test: EXV OperationalState Off path.
//! Validates that the EXV with `Off` produces ṁ = 0 in its residual and keeps
//! the Jacobian non-singular. Run with:
//!   cargo test --release -p entropyk-components --test test_exv_off -- --nocapture
use entropyk_components::isenthalpic_expansion_valve::IsenthalpicExpansionValve;
use entropyk_components::state_machine::{OperationalState, StateManageable};
use entropyk_components::{Component, JacobianBuilder};

fn make_exv() -> IsenthalpicExpansionValve {
    let mut exv = IsenthalpicExpansionValve::new(278.15)
        .with_refrigerant("R134a")
        .with_emergent_pressure()
        .with_orifice_fixed(2.0e-6, 1.0);
    // Layout: [m_inlet=0, m_outlet=1, p_inlet=2, h_inlet=3, p_outlet=4, h_outlet=5]
    exv.set_system_context(0, &[(0, 2, 3), (1, 4, 5)]);
    exv
}

#[test]
fn exv_off_produces_zero_flow_residual() {
    let mut exv = make_exv();

    // Turn the valve OFF.
    exv.set_operational_state_unchecked(OperationalState::Off);
    assert!(exv.state().is_off());

    // State where m_outlet = 0.05 (non-zero flow) — the Off residual must drive it to 0.
    let state: Vec<f64> = vec![0.05, 0.05, 1.5e6, 250_000.0, 0.4e6, 250_000.0];
    let mut r = vec![0.0_f64; exv.n_equations()];
    exv.compute_residuals(&state, &mut r).expect("residuals");

    // The orifice equation is the last one (after isenthalpic + mass conservation).
    // r_orifice should equal m_outlet - 0 = 0.05 (non-zero → Newton will push it to 0).
    let orifice_residual = *r.last().unwrap();
    assert!(
        (orifice_residual - 0.05).abs() < 1e-12,
        "Off residual should be ṁ = 0.05 (forcing flow to 0), got {}",
        orifice_residual
    );

    // Jacobian: ∂r_orifice/∂m_outlet = 1 (keeps Newton coupled on the mass flow).
    let mut jb = JacobianBuilder::new();
    exv.jacobian_entries(&state, &mut jb).expect("jacobian");
    let entries = jb.entries();
    let m_out_idx = 1;
    let orifice_row = exv.n_equations() - 1;
    let dm_out: f64 = entries
        .iter()
        .filter(|(row, col, _)| *row == orifice_row && *col == m_out_idx)
        .map(|(_, _, v)| *v)
        .sum();
    assert!(
        (dm_out - 1.0).abs() < 1e-12,
        "Off Jacobian ∂r/∂m_outlet should be 1.0, got {}",
        dm_out
    );

    println!(
        "EXV Off: residual={:.4e} (target 0.05), ∂r/∂m_out={:.3} (target 1.0) — OK",
        orifice_residual, dm_out
    );
}

#[test]
fn exv_on_still_uses_orifice_equation() {
    let mut exv = make_exv();
    // Default state is On.
    assert!(exv.state().is_on());

    // Use a backend so ρ_in can be evaluated.
    let backend: std::sync::Arc<dyn entropyk_fluids::FluidBackend> =
        std::sync::Arc::new(entropyk_fluids::TestBackend::new());
    exv.set_fluid_backend_from_builder(backend);

    // State with a physical ΔP across the valve: P_in = 13 bar, P_out = 3.5 bar.
    let state: Vec<f64> = vec![0.05, 0.05, 1.3e6, 250_000.0, 0.35e6, 250_000.0];
    let mut r = vec![0.0_f64; exv.n_equations()];
    exv.compute_residuals(&state, &mut r).expect("residuals");

    let orifice_residual = *r.last().unwrap();
    // Orifice equation is active: residual is non-trivial (not just ṁ).
    assert!(
        orifice_residual.abs() > 1e-6,
        "On residual should be non-trivial (orifice equation active), got {}",
        orifice_residual
    );
    println!(
        "EXV On: residual={:.4e} (orifice equation active) — OK",
        orifice_residual
    );
}

#[test]
fn exv_state_transitions_are_validated() {
    let mut exv = IsenthalpicExpansionValve::new(278.15);
    assert!(exv.state().is_on());

    // On → Off is a legal transition.
    assert!(exv.can_transition_to(OperationalState::Off));
    exv.set_state(OperationalState::Off).expect("On → Off");
    assert!(exv.state().is_off());

    // Off → On is legal.
    assert!(exv.can_transition_to(OperationalState::On));
    exv.set_state(OperationalState::On).expect("Off → On");
    assert!(exv.state().is_on());
}
