use std::collections::HashMap;
use std::fmt::format;

use super::super::solid_state_kinetics_IVP::{IVPSolution, KineticModelIVP, KineticModelNames};
use super::kinetic_methods::integral_isoconversion::IntegralIsoconversionalSolver;
use super::kinetic_methods::{ConversionGridBuilder, ExperimentData, KineticDataView};
use super::one_experiment_dataset::TGADomainError;
use crate::Kinetics::experimental_kinetics::experiment_series_main::ExperimentMeta;
use RustedSciThe::numerical::ODE_api2::{SolverParam, SolverType, UniversalODESolver};
use std::time::Instant;
pub fn solution_to_experiment(sol: IVPSolution, heating_rate: f64, id: &str) -> ExperimentData {
    ExperimentData {
        meta: ExperimentMeta {
            id: id.to_string(),
            heating_rate: Some(heating_rate),
            isothermal_temperature: None,
            comment: None,
        },

        time: sol.time,
        temperature: sol.temperature,
        conversion: sol.conversion,
        conversion_rate: sol.conversion_rate,

        mass: None,
        mass_rate: None,
    }
}

pub fn simulate_tga_dataset(
    model: KineticModelNames,
    e: f64,
    a: f64,
    t0: f64,
    betas: &[f64],
    t_end: f64,
    solvertype: SolverType,
    params: Vec<f64>,
    id: &str,
) -> Result<KineticDataView, TGADomainError> {
    let mut experiments = Vec::with_capacity(betas.len());

    for &beta in betas {
        let mut ivp = KineticModelIVP::new(solvertype.clone());

        // Reduce solver work for unit tests by limiting the number of steps.
        // This avoids long-running integrations when t_end is large.
        let step = (t_end / 2000.0).max(1e-4).min(1.0);
        let max_iter = 500;
        let mut solver_params: HashMap<String, SolverParam> = HashMap::new();
        solver_params.insert("step_size".to_string(), SolverParam::Float(step));
        solver_params.insert("max_step".to_string(), SolverParam::Float(step));
        solver_params.insert("max_iterations".to_string(), SolverParam::Int(max_iter));
        solver_params.insert("tolerance".to_string(), SolverParam::Float(1e-3));
        solver_params.insert("rtol".to_string(), SolverParam::Float(1e-3));
        solver_params.insert("atol".to_string(), SolverParam::Float(1e-3));
        solver_params.insert("parallel".to_string(), SolverParam::Bool(true));
        ivp.set_solver_params(solver_params);

        let _ = ivp.set_model(model.clone(), params.clone());
        let _ = ivp.set_arrhenius(e, a);
        let _ = ivp.set_heating_program(t0, beta);
        let _ = ivp.set_time(t_end);

        let _ = ivp
            .solve()
            .map_err(|s| TGADomainError::InvalidOperation(format!("error solving ivp {}", s)));

        let sol: IVPSolution = ivp.get_solution().unwrap();

        let exp = solution_to_experiment(sol, beta, id);
        println!("problem for beta = {} solved", beta);
        experiments.push(exp);
    }

    Ok(KineticDataView { experiments })
}

#[test]
fn test_conversion_grid_build() {
    fn synthetic_experiment(id: &str, beta: f64) -> ExperimentData {
        let mut time = Vec::with_capacity(101);
        let mut temperature = Vec::with_capacity(101);
        let mut conversion = Vec::with_capacity(101);
        let mut conversion_rate = Vec::with_capacity(101);

        for i in 0..=100 {
            let t = i as f64;
            time.push(t);
            temperature.push(420.0 + beta * t);
            conversion.push(i as f64 / 100.0);
            conversion_rate.push(0.01);
        }

        ExperimentData {
            meta: ExperimentMeta {
                id: id.to_string(),
                heating_rate: Some(beta),
                isothermal_temperature: None,
                comment: None,
            },
            time,
            temperature,
            conversion,
            conversion_rate,
            mass: None,
            mass_rate: None,
        }
    }

    let data = KineticDataView {
        experiments: vec![
            synthetic_experiment("exp_a", 0.5),
            synthetic_experiment("exp_b", 1.0),
            synthetic_experiment("exp_c", 2.0),
        ],
    };

    let grid = ConversionGridBuilder::new()
        .eta_range(0.05, 0.95)
        .segments(80)
        .build_nonisothermal(&data)
        .unwrap();

    assert_eq!(grid.temperature.nrows(), 3);
    assert_eq!(grid.eta.len(), 80);

    assert!(grid.temperature[[0, 10]] > 300.0);
}

#[test]
fn test_kas_activation_energy() {
    let true_e = 50_000.0;
    let now = Instant::now();
    let data = simulate_tga_dataset(
        KineticModelNames::F1,
        true_e,
        1e6,
        0.0,
        &[2.0, 5.0, 10.0, 20.0],
        50.0,
        SolverType::BDF,
        vec![],
        "",
    )
    .unwrap();
    println!("tga simulated {:?}", now.elapsed());
    let grid = ConversionGridBuilder::new()
        .segments(80)
        .build_isothermal(&data)
        .unwrap();

    let solver = IntegralIsoconversionalSolver::kas();
    grid.report();
    let result = solver.solve(&grid).unwrap();
    result.pretty_print_and_assert(0.05, 0.95, 1000, Some(0.99));
}

/*
#[test]
fn test_kissinger_method() {

    let true_e = 100_000.0;

    let data = simulate_tga_dataset(
        KineticModel::F1,
        true_e,
        1e13,
        300.0,
        &[2.0, 5.0, 10.0, 20.0],
        2000.0,
    ).unwrap();

    let grid = ConversionGridBuilder::new()
        .segments(200)
        .build(&data)
        .unwrap();

    let res = kissinger_from_grid(&grid).unwrap();

    assert!((res.ea - true_e).abs() < 5000.0);
}

#[test]
fn test_cka_model_detection() {

    let data = simulate_tga_dataset(
        KineticModel::F1,
        120_000.0,
        1e13,
        300.0,
        &[10.0],
        2000.0,
    ).unwrap();

    let solver = CombinedKineticAnalysis::default();

    let res = solver.solve_all(&data).unwrap();

    assert!(res[0].regression.r.abs() > 0.99);
}

#[test]
fn test_conversion_monotonicity() {

    let data = simulate_tga_dataset(
        KineticModel::F1,
        100_000.0,
        1e13,
        300.0,
        &[10.0],
        2000.0,
    ).unwrap();

    let exp = &data.experiments[0];

    assert!(exp.conversion
        .windows(2)
        .all(|w| w[0] <= w[1]));
}

 */
