//! Tests for verbose mode diagnostics (Story 7.4).
//!
//! Covers:
//! - VerboseConfig default behavior
//! - IterationDiagnostics collection
//! - Jacobian condition number estimation
//! - ConvergenceDiagnostics summary

use entropyk_solver::jacobian::JacobianMatrix;
use entropyk_solver::{
    ConvergenceDiagnostics, IterationDiagnostics, SolverSwitchEvent, SolverType, SwitchReason,
    VerboseConfig, VerboseOutputFormat,
};

// =============================================================================
// Task 1: VerboseConfig Tests
// =============================================================================

#[test]
fn test_verbose_config_default_is_disabled() {
    let config = VerboseConfig::default();

    // All features should be disabled by default for backward compatibility
    assert!(!config.enabled, "enabled should be false by default");
    assert!(
        !config.log_residuals,
        "log_residuals should be false by default"
    );
    assert!(
        !config.log_jacobian_condition,
        "log_jacobian_condition should be false by default"
    );
    assert!(
        !config.log_solver_switches,
        "log_solver_switches should be false by default"
    );
    assert!(
        !config.dump_final_state,
        "dump_final_state should be false by default"
    );
    assert_eq!(
        config.output_format,
        VerboseOutputFormat::Both,
        "output_format should default to Both"
    );
}

#[test]
fn test_verbose_config_all_enabled() {
    let config = VerboseConfig::all_enabled();

    assert!(config.enabled, "enabled should be true");
    assert!(config.log_residuals, "log_residuals should be true");
    assert!(
        config.log_jacobian_condition,
        "log_jacobian_condition should be true"
    );
    assert!(
        config.log_solver_switches,
        "log_solver_switches should be true"
    );
    assert!(config.dump_final_state, "dump_final_state should be true");
}

#[test]
fn test_verbose_config_is_any_enabled() {
    // All disabled
    let config = VerboseConfig::default();
    assert!(!config.is_any_enabled(), "no features should be enabled");

    // Master switch off but features on
    let config = VerboseConfig {
        enabled: false,
        log_residuals: true,
        ..Default::default()
    };
    assert!(
        !config.is_any_enabled(),
        "should be false when master switch is off"
    );

    // Master switch on but all features off
    let config = VerboseConfig {
        enabled: true,
        ..Default::default()
    };
    assert!(
        !config.is_any_enabled(),
        "should be false when no features are enabled"
    );

    // Master switch on and one feature on
    let config = VerboseConfig {
        enabled: true,
        log_residuals: true,
        ..Default::default()
    };
    assert!(
        config.is_any_enabled(),
        "should be true when one feature is enabled"
    );
}

// =============================================================================
// Task 2: IterationDiagnostics Tests
// =============================================================================

#[test]
fn test_iteration_diagnostics_creation() {
    let diag = IterationDiagnostics {
        iteration: 5,
        residual_norm: 1e-4,
        delta_norm: 1e-5,
        alpha: Some(0.5),
        jacobian_frozen: true,
        jacobian_condition: Some(1e3),
        ..Default::default()
    };

    assert_eq!(diag.iteration, 5);
    assert!((diag.residual_norm - 1e-4).abs() < 1e-15);
    assert!((diag.delta_norm - 1e-5).abs() < 1e-15);
    assert_eq!(diag.alpha, Some(0.5));
    assert!(diag.jacobian_frozen);
    assert_eq!(diag.jacobian_condition, Some(1e3));
}

#[test]
fn test_iteration_diagnostics_without_alpha() {
    // Sequential Substitution doesn't use line search
    let diag = IterationDiagnostics {
        iteration: 3,
        residual_norm: 1e-3,
        delta_norm: 1e-4,
        alpha: None,
        jacobian_frozen: false,
        jacobian_condition: None,
        ..Default::default()
    };

    assert_eq!(diag.alpha, None);
    assert!(!diag.jacobian_frozen);
    assert_eq!(diag.jacobian_condition, None);
}

// =============================================================================
// Task 3: Jacobian Condition Number Tests
// =============================================================================

#[test]
fn test_jacobian_condition_number_well_conditioned() {
    // Identity-like matrix (well-conditioned)
    let entries = vec![(0, 0, 2.0), (1, 1, 1.0)];
    let j = JacobianMatrix::from_builder(&entries, 2, 2);

    let cond = j
        .estimate_condition_number()
        .expect("should compute condition number");

    // Condition number of diagonal matrix is max/min diagonal entry
    assert!(
        cond < 10.0,
        "Expected low condition number for well-conditioned matrix, got {}",
        cond
    );
}

#[test]
fn test_jacobian_condition_number_ill_conditioned() {
    // Nearly singular matrix
    let entries = vec![(0, 0, 1.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 1.0000001)];
    let j = JacobianMatrix::from_builder(&entries, 2, 2);

    let cond = j
        .estimate_condition_number()
        .expect("should compute condition number");

    assert!(
        cond > 1e6,
        "Expected high condition number for ill-conditioned matrix, got {}",
        cond
    );
}

#[test]
fn test_jacobian_condition_number_identity() {
    // Identity matrix has condition number 1
    let entries = vec![(0, 0, 1.0), (1, 1, 1.0), (2, 2, 1.0)];
    let j = JacobianMatrix::from_builder(&entries, 3, 3);

    let cond = j
        .estimate_condition_number()
        .expect("should compute condition number");

    assert!(
        (cond - 1.0).abs() < 1e-10,
        "Expected condition number 1 for identity matrix, got {}",
        cond
    );
}

#[test]
fn test_jacobian_condition_number_empty_matrix() {
    // Empty matrix (0x0)
    let j = JacobianMatrix::zeros(0, 0);

    let cond = j.estimate_condition_number();

    assert!(cond.is_none(), "Expected None for empty matrix");
}

// =============================================================================
// Task 4: SolverSwitchEvent Tests
// =============================================================================

#[test]
fn test_solver_switch_event_creation() {
    let event = SolverSwitchEvent {
        from_solver: SolverType::NewtonRaphson,
        to_solver: SolverType::SequentialSubstitution,
        reason: SwitchReason::Divergence,
        iteration: 10,
        residual_at_switch: 1e6,
    };

    assert_eq!(event.from_solver, SolverType::NewtonRaphson);
    assert_eq!(event.to_solver, SolverType::SequentialSubstitution);
    assert_eq!(event.reason, SwitchReason::Divergence);
    assert_eq!(event.iteration, 10);
    assert!((event.residual_at_switch - 1e6).abs() < 1e-6);
}

#[test]
fn test_solver_type_display() {
    assert_eq!(format!("{}", SolverType::NewtonRaphson), "Newton-Raphson");
    assert_eq!(
        format!("{}", SolverType::SequentialSubstitution),
        "Sequential Substitution"
    );
}

#[test]
fn test_switch_reason_display() {
    assert_eq!(
        format!("{}", SwitchReason::Divergence),
        "divergence detected"
    );
    assert_eq!(
        format!("{}", SwitchReason::SlowConvergence),
        "slow convergence"
    );
    assert_eq!(format!("{}", SwitchReason::UserRequested), "user requested");
    assert_eq!(
        format!("{}", SwitchReason::ReturnToNewton),
        "returning to Newton after stabilization"
    );
}

// =============================================================================
// Task 5: ConvergenceDiagnostics Tests
// =============================================================================

#[test]
fn test_convergence_diagnostics_default() {
    let diag = ConvergenceDiagnostics::default();

    assert_eq!(diag.iterations, 0);
    assert!((diag.final_residual - 0.0).abs() < 1e-15);
    assert!(!diag.converged);
    assert!(diag.iteration_history.is_empty());
    assert!(diag.solver_switches.is_empty());
    assert!(diag.final_state.is_none());
    assert!(diag.jacobian_condition_final.is_none());
    assert_eq!(diag.timing_ms, 0);
    assert!(diag.final_solver.is_none());
}

#[test]
fn test_convergence_diagnostics_with_capacity() {
    let diag = ConvergenceDiagnostics::with_capacity(100);

    // Capacity should be pre-allocated
    assert!(diag.iteration_history.capacity() >= 100);
    assert!(diag.iteration_history.is_empty());
}

#[test]
fn test_convergence_diagnostics_push_iteration() {
    let mut diag = ConvergenceDiagnostics::new();

    diag.push_iteration(IterationDiagnostics {
        iteration: 0,
        residual_norm: 1.0,
        delta_norm: 0.0,
        alpha: None,
        jacobian_frozen: false,
        jacobian_condition: None,
        ..Default::default()
    });

    diag.push_iteration(IterationDiagnostics {
        iteration: 1,
        residual_norm: 0.5,
        delta_norm: 0.5,
        alpha: Some(1.0),
        jacobian_frozen: false,
        jacobian_condition: Some(100.0),
        ..Default::default()
    });

    assert_eq!(diag.iteration_history.len(), 2);
    assert_eq!(diag.iteration_history[0].iteration, 0);
    assert_eq!(diag.iteration_history[1].iteration, 1);
}

#[test]
fn test_convergence_diagnostics_push_switch() {
    let mut diag = ConvergenceDiagnostics::new();

    diag.push_switch(SolverSwitchEvent {
        from_solver: SolverType::NewtonRaphson,
        to_solver: SolverType::SequentialSubstitution,
        reason: SwitchReason::Divergence,
        iteration: 5,
        residual_at_switch: 1e10,
    });

    assert_eq!(diag.solver_switches.len(), 1);
    assert_eq!(diag.solver_switches[0].iteration, 5);
}

#[test]
fn test_convergence_diagnostics_summary_converged() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 25;
    diag.final_residual = 1e-8;
    diag.best_residual = 1e-8;
    diag.converged = true;
    diag.timing_ms = 150;
    diag.final_solver = Some(SolverType::NewtonRaphson);
    diag.jacobian_condition_final = Some(1e4);

    let summary = diag.summary();

    assert!(summary.contains("Converged: YES"));
    assert!(summary.contains("Iterations: 25"));
    // The format uses {:.3e} which produces like "1.000e-08"
    assert!(summary.contains("Final Residual:"));
    assert!(summary.contains("Solver Switches: 0"));
    assert!(summary.contains("Timing: 150 ms"));
    assert!(summary.contains("Jacobian Condition:"));
    assert!(summary.contains("Final Solver: Newton-Raphson"));
    // Should NOT contain ill-conditioned warning
    assert!(!summary.contains("WARNING"));
}

#[test]
fn test_convergence_diagnostics_summary_ill_conditioned() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 100;
    diag.final_residual = 1e-2;
    diag.best_residual = 1e-3;
    diag.converged = false;
    diag.timing_ms = 500;
    diag.jacobian_condition_final = Some(1e12);

    let summary = diag.summary();

    assert!(summary.contains("Converged: NO"));
    assert!(summary.contains("WARNING: ill-conditioned"));
}

#[test]
fn test_convergence_diagnostics_summary_with_switches() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 50;
    diag.final_residual = 1e-6;
    diag.best_residual = 1e-6;
    diag.converged = true;
    diag.timing_ms = 200;

    diag.push_switch(SolverSwitchEvent {
        from_solver: SolverType::NewtonRaphson,
        to_solver: SolverType::SequentialSubstitution,
        reason: SwitchReason::Divergence,
        iteration: 10,
        residual_at_switch: 1e10,
    });

    let summary = diag.summary();

    assert!(summary.contains("Solver Switches: 1"));
}

// =============================================================================
// VerboseOutputFormat Tests
// =============================================================================

#[test]
fn test_verbose_output_format_default() {
    let format = VerboseOutputFormat::default();
    assert_eq!(format, VerboseOutputFormat::Both);
}

// =============================================================================
// JSON Serialization Tests (Story 7.4 - AC4)
// =============================================================================

#[test]
fn test_convergence_diagnostics_json_serialization() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 50;
    diag.final_residual = 1e-6;
    diag.best_residual = 1e-7;
    diag.converged = true;
    diag.timing_ms = 250;
    diag.final_solver = Some(SolverType::NewtonRaphson);
    diag.jacobian_condition_final = Some(1e5);

    diag.push_iteration(IterationDiagnostics {
        iteration: 1,
        residual_norm: 1.0,
        delta_norm: 0.5,
        alpha: Some(1.0),
        jacobian_frozen: false,
        jacobian_condition: Some(100.0),
        ..Default::default()
    });

    diag.push_switch(SolverSwitchEvent {
        from_solver: SolverType::NewtonRaphson,
        to_solver: SolverType::SequentialSubstitution,
        reason: SwitchReason::Divergence,
        iteration: 10,
        residual_at_switch: 1e6,
    });

    // Test JSON serialization
    let json = serde_json::to_string(&diag).expect("Should serialize to JSON");
    assert!(json.contains("\"iterations\":50"));
    assert!(json.contains("\"converged\":true"));
    assert!(json.contains("\"NewtonRaphson\""));
    assert!(json.contains("\"Divergence\""));
}

#[test]
fn test_convergence_diagnostics_round_trip() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 25;
    diag.final_residual = 1e-8;
    diag.converged = true;
    diag.timing_ms = 100;
    diag.final_solver = Some(SolverType::SequentialSubstitution);

    // Serialize to JSON
    let json = serde_json::to_string(&diag).expect("Should serialize");

    // Deserialize back
    let restored: ConvergenceDiagnostics = serde_json::from_str(&json).expect("Should deserialize");

    assert_eq!(restored.iterations, 25);
    assert!((restored.final_residual - 1e-8).abs() < 1e-20);
    assert!(restored.converged);
    assert_eq!(restored.timing_ms, 100);
    assert_eq!(
        restored.final_solver,
        Some(SolverType::SequentialSubstitution)
    );
}

#[test]
fn test_dump_diagnostics_json_format() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 10;
    diag.final_residual = 1e-4;
    diag.converged = false;

    let json_output = diag.dump_diagnostics(VerboseOutputFormat::Json);
    assert!(json_output.starts_with('{'));
    // to_string_pretty adds spaces after colons
    assert!(json_output.contains("\"iterations\"") && json_output.contains("10"));
    assert!(json_output.contains("\"converged\"") && json_output.contains("false"));
}

#[test]
fn test_dump_diagnostics_log_format() {
    let mut diag = ConvergenceDiagnostics::new();
    diag.iterations = 10;
    diag.final_residual = 1e-4;
    diag.converged = false;

    let log_output = diag.dump_diagnostics(VerboseOutputFormat::Log);
    assert!(log_output.contains("Convergence Diagnostics Summary"));
    assert!(log_output.contains("Converged: NO"));
    assert!(log_output.contains("Iterations: 10"));
}
