use runmat_analysis_core::AnalysisStepKind;
use runmat_analysis_fea::{
    ComputeBackend, FEA_FIELD_STRUCTURAL_DISPLACEMENT, FEA_FIELD_STRUCTURAL_VON_MISES,
};
use runmat_runtime::analysis::{
    analysis_create_model_op, analysis_run_nonlinear_with_options_op,
    AnalysisCreateModelIntentSpec, AnalysisCreateModelProfile, AnalysisNonlinearRunOptions,
};
use runmat_runtime::geometry::{
    geometry_load_op, geometry_prep_for_analysis_op, GeometryPrepForAnalysisSpec,
};
use runmat_runtime::operations::OperationContext;

const TRIANGLE_STL: &str = "solid tri\n  facet normal 0 0 1\n    outer loop\n      vertex 0 0 0\n      vertex 1 0 0\n      vertex 0 1 0\n    endloop\n  endfacet\nendsolid tri\n";

#[test]
fn prep_artifact_reference_changes_nonlinear_solve_profile_with_bounded_quality() {
    let geometry = geometry_load_op(
        "/fixtures/prep_solve_tri.stl",
        TRIANGLE_STL.as_bytes(),
        OperationContext::new(Some("trace-prep-solve-load".to_string()), None),
    )
    .expect("geometry load should succeed");
    let prep = geometry_prep_for_analysis_op(
        &geometry.data,
        GeometryPrepForAnalysisSpec::default(),
        OperationContext::new(Some("trace-prep-solve-prep".to_string()), None),
    )
    .expect("geometry prep should succeed");

    let created = analysis_create_model_op(
        &geometry.data,
        AnalysisCreateModelIntentSpec {
            model_id: "prep_solve_model".to_string(),
            profile: AnalysisCreateModelProfile::NonlinearStructural,
            prep_context: None,
        },
        OperationContext::new(Some("trace-prep-solve-create".to_string()), None),
    )
    .expect("create model should succeed");
    let mut model = created.data;
    model.steps[0].kind = AnalysisStepKind::Nonlinear;

    let baseline = analysis_run_nonlinear_with_options_op(
        &model,
        ComputeBackend::Cpu,
        AnalysisNonlinearRunOptions::production_recommended(),
        OperationContext::new(Some("trace-prep-solve-base".to_string()), None),
    )
    .expect("baseline nonlinear run should succeed");

    let prep_enhanced = analysis_run_nonlinear_with_options_op(
        &model,
        ComputeBackend::Cpu,
        AnalysisNonlinearRunOptions {
            prep_artifact_id: Some(prep.data.prep_artifact_id.clone()),
            ..AnalysisNonlinearRunOptions::production_recommended()
        },
        OperationContext::new(Some("trace-prep-solve-enhanced".to_string()), None),
    )
    .expect("prep-enhanced nonlinear run should succeed");
    let prep_enhanced_replay = analysis_run_nonlinear_with_options_op(
        &model,
        ComputeBackend::Cpu,
        AnalysisNonlinearRunOptions {
            prep_artifact_id: Some(prep.data.prep_artifact_id.clone()),
            ..AnalysisNonlinearRunOptions::production_recommended()
        },
        OperationContext::new(Some("trace-prep-solve-enhanced-replay".to_string()), None),
    )
    .expect("prep-enhanced replay nonlinear run should succeed");

    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_TOPOLOGY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_OPERATOR_TOPOLOGY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_REGION_TOPOLOGY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_ELEMENT_ASSEMBLY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_ELEMENT_CONNECTIVITY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_GRAPH_ASSEMBLY"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_GRAPH_SOLVER"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_CALIBRATION"));
    assert!(baseline
        .data
        .run
        .diagnostics
        .iter()
        .all(|diag| diag.code != "FEA_PREP_ACCEPTANCE"));

    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_CONTEXT"
            && diag.message.contains("element_geometry_node_count=")
            && diag.message.contains("element_geometry_edge_count=")
            && diag.message.contains("mean_element_edge_length_m=")
            && diag.message.contains("mean_element_area_m2=")
            && diag.message.contains("element_geometry_coverage_ratio=")));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_ASSEMBLY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_TOPOLOGY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_OPERATOR_TOPOLOGY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_REGION_TOPOLOGY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_ELEMENT_ASSEMBLY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_ELEMENT_CONNECTIVITY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_GRAPH_ASSEMBLY"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_GRAPH_SOLVER"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_CALIBRATION"));
    assert!(prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .any(|diag| diag.code == "FEA_PREP_ACCEPTANCE"));

    let base_nonlinear = baseline
        .data
        .nonlinear_results
        .as_ref()
        .expect("baseline nonlinear payload present");
    let prep_nonlinear = prep_enhanced
        .data
        .nonlinear_results
        .as_ref()
        .expect("prep nonlinear payload present");

    let base_max_iters = base_nonlinear
        .iteration_counts
        .iter()
        .copied()
        .max()
        .unwrap_or(0);
    let prep_max_iters = prep_nonlinear
        .iteration_counts
        .iter()
        .copied()
        .max()
        .unwrap_or(0);
    assert!(base_max_iters.abs_diff(prep_max_iters) <= 16);
    assert!(
        base_max_iters != prep_max_iters
            || base_nonlinear.failed_increments != prep_nonlinear.failed_increments
    );

    let prep_topology_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_TOPOLOGY")
        .expect("prep topology diagnostic should be present");
    let replay_topology_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_TOPOLOGY")
        .expect("prep topology diagnostic should be present in replay");
    assert_eq!(prep_topology_diag.message, replay_topology_diag.message);

    let prep_operator_topology_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_OPERATOR_TOPOLOGY")
        .expect("prep operator topology diagnostic should be present");
    let replay_operator_topology_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_OPERATOR_TOPOLOGY")
        .expect("prep operator topology diagnostic should be present in replay");
    assert_eq!(
        prep_operator_topology_diag.message,
        replay_operator_topology_diag.message
    );

    let prep_region_topology_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_REGION_TOPOLOGY")
        .expect("prep region topology diagnostic should be present");
    let replay_region_topology_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_REGION_TOPOLOGY")
        .expect("prep region topology diagnostic should be present in replay");
    assert_eq!(
        prep_region_topology_diag.message,
        replay_region_topology_diag.message
    );

    let prep_element_assembly_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ELEMENT_ASSEMBLY")
        .expect("prep element assembly diagnostic should be present");
    let replay_element_assembly_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ELEMENT_ASSEMBLY")
        .expect("prep element assembly diagnostic should be present in replay");
    assert_eq!(
        prep_element_assembly_diag.message,
        replay_element_assembly_diag.message
    );

    let prep_element_connectivity_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ELEMENT_CONNECTIVITY")
        .expect("prep element connectivity diagnostic should be present");
    let replay_element_connectivity_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ELEMENT_CONNECTIVITY")
        .expect("prep element connectivity diagnostic should be present in replay");
    assert_eq!(
        prep_element_connectivity_diag.message,
        replay_element_connectivity_diag.message
    );

    let prep_graph_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_GRAPH_ASSEMBLY")
        .expect("prep graph assembly diagnostic should be present");
    let replay_graph_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_GRAPH_ASSEMBLY")
        .expect("prep graph assembly diagnostic should be present in replay");
    assert_eq!(prep_graph_diag.message, replay_graph_diag.message);

    let edge_count = metric_usize(&prep_graph_diag.message, "edge_count");
    let node_count = metric_usize(&prep_graph_diag.message, "node_count");
    assert!(edge_count > 0);
    assert!(edge_count <= node_count.saturating_mul(node_count.saturating_sub(1)) / 2);

    let prep_graph_solver_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_GRAPH_SOLVER")
        .expect("prep graph solver diagnostic should be present");
    let replay_graph_solver_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_GRAPH_SOLVER")
        .expect("prep graph solver diagnostic should be present in replay");
    assert_eq!(
        prep_graph_solver_diag.message,
        replay_graph_solver_diag.message
    );

    let prep_calibration_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_CALIBRATION")
        .expect("prep calibration diagnostic should be present");
    let replay_calibration_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_CALIBRATION")
        .expect("prep calibration diagnostic should be present in replay");
    assert_eq!(
        prep_calibration_diag.message,
        replay_calibration_diag.message
    );

    let prep_acceptance_diag = prep_enhanced
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ACCEPTANCE")
        .expect("prep acceptance diagnostic should be present");
    let replay_acceptance_diag = prep_enhanced_replay
        .data
        .run
        .diagnostics
        .iter()
        .find(|diag| diag.code == "FEA_PREP_ACCEPTANCE")
        .expect("prep acceptance diagnostic should be present in replay");
    assert_eq!(prep_acceptance_diag.message, replay_acceptance_diag.message);
    let acceptance_score = metric_f64(&prep_acceptance_diag.message, "acceptance_score");
    assert!(acceptance_score > 0.0);

    let base_peak_displacement = baseline
        .data
        .run
        .field(FEA_FIELD_STRUCTURAL_DISPLACEMENT)
        .expect("baseline displacement field should be present")
        .as_host_f64()
        .expect("baseline displacement should be host-backed")
        .iter()
        .map(|value| value.abs())
        .fold(0.0_f64, f64::max);
    let prep_peak_displacement = prep_enhanced
        .data
        .run
        .field(FEA_FIELD_STRUCTURAL_DISPLACEMENT)
        .expect("prep displacement field should be present")
        .as_host_f64()
        .expect("prep displacement should be host-backed")
        .iter()
        .map(|value| value.abs())
        .fold(0.0_f64, f64::max);
    let displacement_delta_ratio = (prep_peak_displacement - base_peak_displacement).abs()
        / base_peak_displacement
            .max(prep_peak_displacement)
            .max(1.0e-9);
    assert!(displacement_delta_ratio > 1.0e-4);
    assert!(displacement_delta_ratio < 0.95);

    let base_peak_stress = baseline
        .data
        .run
        .field(FEA_FIELD_STRUCTURAL_VON_MISES)
        .expect("baseline von Mises field should be present")
        .as_host_f64()
        .expect("baseline stress should be host-backed")
        .iter()
        .map(|value| value.abs())
        .fold(0.0_f64, f64::max);
    let prep_peak_stress = prep_enhanced
        .data
        .run
        .field(FEA_FIELD_STRUCTURAL_VON_MISES)
        .expect("prep von Mises field should be present")
        .as_host_f64()
        .expect("prep stress should be host-backed")
        .iter()
        .map(|value| value.abs())
        .fold(0.0_f64, f64::max);
    let stress_delta_ratio = (prep_peak_stress - base_peak_stress).abs()
        / base_peak_stress.max(prep_peak_stress).max(1.0e-9);
    assert!(stress_delta_ratio > 1.0e-4);
    assert!(stress_delta_ratio < 0.95);
}

fn metric_usize(message: &str, key: &str) -> usize {
    message
        .split_whitespace()
        .find_map(|token| token.strip_prefix(&format!("{key}=")))
        .and_then(|value| value.parse::<usize>().ok())
        .unwrap_or(0)
}

fn metric_f64(message: &str, key: &str) -> f64 {
    message
        .split_whitespace()
        .find_map(|token| token.strip_prefix(&format!("{key}=")))
        .and_then(|value| value.parse::<f64>().ok())
        .unwrap_or(0.0)
}
