//! Integration tests for Sequential Substitution (Picard) solver (Story 4.3).
//!
//! Tests cover:
//! - AC #1: Reliable convergence when Newton diverges
//! - AC #2: Sequential variable update
//! - AC #3: Configurable relaxation factors
//! - AC #4: Timeout enforcement
//! - AC #5: Divergence detection
//! - AC #6: Pre-allocated buffers

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

// ─────────────────────────────────────────────────────────────────────────────
// AC #1: Solver Trait and Configuration
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_picard_config_default() {
    let cfg = PicardConfig::default();

    assert_eq!(cfg.max_iterations, 100);
    assert_relative_eq!(cfg.tolerance, 1e-6);
    assert_relative_eq!(cfg.relaxation_factor, 0.5);
    assert!(cfg.timeout.is_none());
    assert_relative_eq!(cfg.divergence_threshold, 1e10);
    assert_eq!(cfg.divergence_patience, 5);
}

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

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

#[test]
fn test_picard_config_custom_values() {
    let cfg = PicardConfig {
        max_iterations: 200,
        tolerance: 1e-8,
        relaxation_factor: 0.3,
        timeout: Some(Duration::from_millis(1000)),
        divergence_threshold: 1e8,
        divergence_patience: 7,
        ..Default::default()
    };

    assert_eq!(cfg.max_iterations, 200);
    assert_relative_eq!(cfg.tolerance, 1e-8);
    assert_relative_eq!(cfg.relaxation_factor, 0.3);
    assert_eq!(cfg.timeout, Some(Duration::from_millis(1000)));
    assert_relative_eq!(cfg.divergence_threshold, 1e8);
    assert_eq!(cfg.divergence_patience, 7);
}

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

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

    let mut solver = PicardConfig::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"),
                "Expected empty system message, got: {}",
                message
            );
        }
        other => panic!("Expected InvalidSystem, got {:?}", other),
    }
}

#[test]
#[should_panic(expected = "finalize")]
fn test_picard_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 = PicardConfig::default();
    let _ = solver.solve(&mut sys);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #3: Relaxation Factor Configuration
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_relaxation_factor_default() {
    let cfg = PicardConfig::default();
    assert_relative_eq!(cfg.relaxation_factor, 0.5);
}

#[test]
fn test_relaxation_factor_full_update() {
    // omega = 1.0: Full update (fastest, may oscillate)
    let cfg = PicardConfig {
        relaxation_factor: 1.0,
        ..Default::default()
    };
    assert_relative_eq!(cfg.relaxation_factor, 1.0);
}

#[test]
fn test_relaxation_factor_heavy_damping() {
    // omega = 0.1: Heavy damping (slow but very stable)
    let cfg = PicardConfig {
        relaxation_factor: 0.1,
        ..Default::default()
    };
    assert_relative_eq!(cfg.relaxation_factor, 0.1);
}

#[test]
fn test_relaxation_factor_moderate() {
    // omega = 0.5: Moderate damping (default, good balance)
    let cfg = PicardConfig {
        relaxation_factor: 0.5,
        ..Default::default()
    };
    assert_relative_eq!(cfg.relaxation_factor, 0.5);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #4: Timeout Enforcement
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_timeout_value_stored() {
    let timeout = Duration::from_millis(250);
    let cfg = PicardConfig::default().with_timeout(timeout);

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

#[test]
fn test_timeout_preserves_other_fields() {
    let cfg = PicardConfig {
        max_iterations: 150,
        tolerance: 1e-7,
        relaxation_factor: 0.25,
        timeout: None,
        divergence_threshold: 1e9,
        divergence_patience: 8,
        ..Default::default()
    }
    .with_timeout(Duration::from_millis(300));

    assert_eq!(cfg.max_iterations, 150);
    assert_relative_eq!(cfg.tolerance, 1e-7);
    assert_relative_eq!(cfg.relaxation_factor, 0.25);
    assert_eq!(cfg.timeout, Some(Duration::from_millis(300)));
    assert_relative_eq!(cfg.divergence_threshold, 1e9);
    assert_eq!(cfg.divergence_patience, 8);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #5: Divergence Detection Configuration
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_divergence_threshold_default() {
    let cfg = PicardConfig::default();
    assert_relative_eq!(cfg.divergence_threshold, 1e10);
}

#[test]
fn test_divergence_patience_default() {
    let cfg = PicardConfig::default();
    assert_eq!(cfg.divergence_patience, 5);
}

#[test]
fn test_divergence_patience_higher_than_newton() {
    // Newton uses hardcoded patience of 3
    // Picard should be more tolerant (5 by default)
    let cfg = PicardConfig::default();
    assert!(
        cfg.divergence_patience >= 5,
        "Picard divergence_patience ({}) should be >= 5 (more tolerant than Newton's 3)",
        cfg.divergence_patience
    );
}

#[test]
fn test_divergence_threshold_custom() {
    let cfg = PicardConfig {
        divergence_threshold: 1e6,
        ..Default::default()
    };
    assert_relative_eq!(cfg.divergence_threshold, 1e6);
}

#[test]
fn test_divergence_patience_custom() {
    let cfg = PicardConfig {
        divergence_patience: 10,
        ..Default::default()
    };
    assert_eq!(cfg.divergence_patience, 10);
}

// ─────────────────────────────────────────────────────────────────────────────
// AC #6: 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 = PicardConfig::default();

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

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

    let mut solver = PicardConfig {
        relaxation_factor: 0.1,
        ..Default::default()
    };

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

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

    let mut solver = PicardConfig {
        relaxation_factor: 1.0,
        ..Default::default()
    };

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

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

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

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

// ─────────────────────────────────────────────────────────────────────────────
// Error Types
// ─────────────────────────────────────────────────────────────────────────────

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

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

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

#[test]
fn test_error_display_invalid_system() {
    let err = SolverError::InvalidSystem {
        message: "State dimension does not match equation count".to_string(),
    };
    let msg = err.to_string();
    assert!(msg.contains("State dimension"));
}

// ─────────────────────────────────────────────────────────────────────────────
// ConvergedState
// ─────────────────────────────────────────────────────────────────────────────

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

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

    assert!(state.is_converged());
    assert_eq!(state.iterations, 25);
    assert_eq!(state.state, vec![1.0, 2.0, 3.0]);
    assert_relative_eq!(state.final_residual, 1e-7);
}

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

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

    assert!(!state.is_converged());
    assert_eq!(state.status, ConvergenceStatus::TimedOutWithBestState);
}

// ─────────────────────────────────────────────────────────────────────────────
// SolverStrategy Integration
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_solver_strategy_picard_dispatch() {
    use entropyk_solver::SolverStrategy;

    let mut strategy = SolverStrategy::SequentialSubstitution(PicardConfig::default());
    let mut system = System::new();
    system.finalize().unwrap();

    let result = strategy.solve(&mut system);
    assert!(result.is_err());
}

#[test]
fn test_solver_strategy_picard_with_timeout() {
    use entropyk_solver::SolverStrategy;

    let strategy = SolverStrategy::SequentialSubstitution(PicardConfig::default())
        .with_timeout(Duration::from_millis(100));

    match strategy {
        SolverStrategy::SequentialSubstitution(cfg) => {
            assert_eq!(cfg.timeout, Some(Duration::from_millis(100)));
        }
        other => panic!("Expected SequentialSubstitution, got {:?}", other),
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Dimension Mismatch Handling
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn test_picard_dimension_mismatch_returns_error() {
    // Picard requires state dimension == equation count
    // This is validated in solve() before iteration begins
    let mut sys = System::new();
    sys.finalize().unwrap();

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

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