//! Integration tests for Newton-Raphson solver (Story 4.2).
//!
//! Tests cover:
//! - AC #1: Solver trait and strategy dispatch
//! - AC #2: Configuration options
//! - AC #3: Timeout enforcement
//! - AC #4: Error handling for empty/invalid systems
//! - AC #5: Pre-allocated buffers (no panic)

use approx::assert_relative_eq;
use entropyk_solver::{NewtonConfig, Solver, SolverError, System};
use std::time::Duration;

// ─────────────────────────────────────────────────────────────────────────────
// AC #1: Solver Trait and Strategy Dispatch
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_newton_config_default() {
    let cfg = NewtonConfig::default();

    assert_eq!(cfg.max_iterations, 100);
    assert_relative_eq!(cfg.tolerance, 1e-6);
    assert!(!cfg.line_search);
    assert!(cfg.timeout.is_none());
    assert!(!cfg.use_numerical_jacobian);
    assert_relative_eq!(cfg.line_search_armijo_c, 1e-4);
    assert_eq!(cfg.line_search_max_backtracks, 20);
    assert_relative_eq!(cfg.divergence_threshold, 1e10);
}

#[test]
fn test_newton_config_with_timeout() {
    let timeout = Duration::from_millis(500);
    let cfg = NewtonConfig::default().with_timeout(timeout);

    assert_eq!(cfg.timeout, Some(timeout));
}

#[test]
fn test_newton_config_custom_values() {
    let cfg = NewtonConfig {
        max_iterations: 50,
        tolerance: 1e-8,
        line_search: true,
        timeout: Some(Duration::from_millis(500)),
        use_numerical_jacobian: true,
        line_search_armijo_c: 1e-3,
        line_search_max_backtracks: 10,
        divergence_threshold: 1e8,
        ..Default::default()
    };

    assert_eq!(cfg.max_iterations, 50);
    assert_relative_eq!(cfg.tolerance, 1e-8);
    assert!(cfg.line_search);
    assert_eq!(cfg.timeout, Some(Duration::from_millis(500)));
    assert!(cfg.use_numerical_jacobian);
    assert_relative_eq!(cfg.line_search_armijo_c, 1e-3);
    assert_eq!(cfg.line_search_max_backtracks, 10);
    assert_relative_eq!(cfg.divergence_threshold, 1e8);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #2: Empty System Handling
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_empty_system_returns_invalid() {
    let mut sys = System::new();
    sys.finalize().unwrap();

    let mut solver = NewtonConfig::default();
    let result = solver.solve(&mut sys);

    assert!(result.is_err());
    match result {
        Err(SolverError::InvalidSystem { message }) => {
            assert!(message.contains("Empty") || message.contains("no state"));
        }
        other => panic!("Expected InvalidSystem, got {:?}", other),
    }
}

#[test]
#[should_panic(expected = "finalize")]
fn test_empty_system_without_finalize_panics() {
    // System panics if solve() is called without finalize()
    // This is expected behavior - the solver requires a finalized system
    let mut sys = System::new();
    // Don't call finalize

    let mut solver = NewtonConfig::default();
    let _ = solver.solve(&mut sys);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #3: Timeout Enforcement
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_timeout_value_in_error() {
    let mut sys = System::new();
    sys.finalize().unwrap();

    let timeout_ms = 10u64;
    let mut solver = NewtonConfig {
        timeout: Some(Duration::from_millis(timeout_ms)),
        ..Default::default()
    };

    let result = solver.solve(&mut sys);

    // Empty system returns InvalidSystem immediately (before timeout check)
    assert!(result.is_err());
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #4: Error Types
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_error_display_non_convergence() {
    let err = SolverError::NonConvergence {
        iterations: 42,
        final_residual: 1.23e-3,
    };
    let msg = err.to_string();
    assert!(msg.contains("42"));
    assert!(msg.contains("1.23"));
}

#[test]
fn test_error_display_timeout() {
    let err = SolverError::Timeout { timeout_ms: 500 };
    let msg = err.to_string();
    assert!(msg.contains("500"));
}

#[test]
fn test_error_display_divergence() {
    let err = SolverError::Divergence {
        reason: "test reason".to_string(),
    };
    let msg = err.to_string();
    assert!(msg.contains("test reason"));
}

#[test]
fn test_error_display_invalid_system() {
    let err = SolverError::InvalidSystem {
        message: "test message".to_string(),
    };
    let msg = err.to_string();
    assert!(msg.contains("test message"));
}

#[test]
fn test_error_equality() {
    let e1 = SolverError::NonConvergence {
        iterations: 10,
        final_residual: 1e-3,
    };
    let e2 = SolverError::NonConvergence {
        iterations: 10,
        final_residual: 1e-3,
    };
    assert_eq!(e1, e2);

    let e3 = SolverError::Timeout { timeout_ms: 100 };
    assert_ne!(e1, e3);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #5: Pre-Allocated Buffers (No Panic)
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_solver_does_not_panic_on_empty_system() {
    let mut sys = System::new();
    sys.finalize().unwrap();

    let mut solver = NewtonConfig::default();

    // Should complete without panic
    let result = solver.solve(&mut sys);
    assert!(result.is_err());
}

#[test]
fn test_solver_does_not_panic_with_line_search() {
    let mut sys = System::new();
    sys.finalize().unwrap();

    let mut solver = NewtonConfig {
        line_search: true,
        ..Default::default()
    };

    // Should complete without panic
    let result = solver.solve(&mut sys);
    assert!(result.is_err());
}

#[test]
fn test_solver_does_not_panic_with_numerical_jacobian() {
    let mut sys = System::new();
    sys.finalize().unwrap();

    let mut solver = NewtonConfig {
        use_numerical_jacobian: true,
        ..Default::default()
    };

    // Should complete without panic
    let result = solver.solve(&mut sys);
    assert!(result.is_err());
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #6: ConvergedState
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_converged_state_is_converged() {
    use entropyk_solver::ConvergedState;
    use entropyk_solver::ConvergenceStatus;

    let state = ConvergedState::new(
        vec![1.0, 2.0, 3.0],
        10,
        1e-8,
        ConvergenceStatus::Converged,
        entropyk_solver::SimulationMetadata::new("".to_string()),
    );

    assert!(state.is_converged());
    assert_eq!(state.iterations, 10);
    assert_eq!(state.state, vec![1.0, 2.0, 3.0]);
}

#[test]
fn test_converged_state_timed_out() {
    use entropyk_solver::ConvergedState;
    use entropyk_solver::ConvergenceStatus;

    let state = ConvergedState::new(
        vec![1.0],
        50,
        1e-3,
        ConvergenceStatus::TimedOutWithBestState,
        entropyk_solver::SimulationMetadata::new("".to_string()),
    );

    assert!(!state.is_converged());
}
