//! Story 1.5 AC#2/#3: the faer dense backend solves the reference cycles with
//! results matching the nalgebra baseline within tolerance (not bitwise —
//! different factorization engines), via the strangler dispatch.

mod common;

use entropyk_solver::linear::{make_linear_backend, set_linear_backend_override, DenseLu};
use entropyk_solver::{LinearSolver, Solver};

/// Per-value tolerance matching the golden-snapshot comparison policy.
const STATE_RTOL: f64 = 1e-6;
const STATE_ATOL: f64 = 1.0; // Pa / J/kg absolute floor

fn solve_all_cycles() -> Vec<Vec<f64>> {
    [
        common::build_reference_cycle_a(),
        common::build_reference_cycle_b(),
        common::build_reference_cycle_c(),
    ]
    .into_iter()
    .map(|mut system| {
        common::solve_reference_system_with_state(&mut system)
            .state
            .clone()
    })
    .collect()
}

#[test]
fn faer_path_dispatch_and_reference_cycle_parity() {
    // 1. Unknown override must fail the solve with the dispatch error (the
    //    dispatch is on the strategy path, not dead code).
    set_linear_backend_override(Some("definitely-not-a-backend"));
    let mut system = common::build_reference_cycle_a();
    let result = entropyk_solver::FallbackSolver::default_solver().solve(&mut system);
    assert!(result.is_err(), "unknown backend must fail the solve");

    // 2. Baseline (nalgebra) on the three reference cycles.
    set_linear_backend_override(Some("nalgebra"));
    let baseline = solve_all_cycles();

    // 3. Same cycles on faer; converged states must match within tolerance.
    set_linear_backend_override(Some("faer"));
    let faer = solve_all_cycles();

    set_linear_backend_override(None);

    assert_eq!(baseline.len(), faer.len());
    for (cycle_idx, (base, alt)) in baseline.iter().zip(faer.iter()).enumerate() {
        assert_eq!(
            base.len(),
            alt.len(),
            "cycle {cycle_idx}: state length differs"
        );
        for (i, (b, a)) in base.iter().zip(alt.iter()).enumerate() {
            let scale = b.abs().max(a.abs()).max(STATE_ATOL);
            assert!(
                (b - a).abs() <= STATE_RTOL * scale,
                "cycle {cycle_idx} state[{i}]: nalgebra={b} faer={a} (rel diff {})",
                (b - a).abs() / scale
            );
        }
    }
}

#[test]
fn dense_lu_enum_dispatches_both_engines() {
    for name in ["nalgebra", "faer"] {
        let mut backend = make_linear_backend(Some(name)).unwrap();
        backend.set_problem(2, 2).unwrap();
        backend
            .set_linearisation(&[(0, 0, 2.0), (1, 1, 1.0)])
            .unwrap();
        let mut rhs = vec![4.0, 3.0];
        backend.solve_in_place(&mut rhs).unwrap();
        assert!((rhs[0] - 2.0).abs() < 1e-9, "{name}: {}", rhs[0]);
        assert!((rhs[1] - 3.0).abs() < 1e-9, "{name}: {}", rhs[1]);
    }
    assert!(matches!(
        make_linear_backend(Some("faer")).unwrap(),
        DenseLu::Faer(_)
    ));
}
