//! Table generation from CoolProp or reference data.
//!
//! When the `coolprop` feature is enabled, generates tables by querying CoolProp.
//! Otherwise, provides template/reference tables for testing.

use crate::errors::FluidResult;
use std::path::Path;

/// Generate a fluid table and save to JSON.
///
/// When `coolprop` feature is enabled, uses CoolProp to compute property values.
/// Otherwise, loads from embedded reference data (R134a only).
pub fn generate_table(fluid_name: &str, output_path: &Path) -> FluidResult<()> {
    #[cfg(feature = "coolprop")]
    {
        generate_from_coolprop(fluid_name, output_path)
    }

    #[cfg(not(feature = "coolprop"))]
    {
        generate_from_reference(fluid_name, output_path)
    }
}

/// Map user-facing fluid name to CoolProp internal name.
#[cfg(feature = "coolprop")]
fn fluid_name_to_coolprop(name: &str) -> String {
    match name.to_lowercase().as_str() {
        "r134a" => "R134a".to_string(),
        "r410a" => "R410A".to_string(),
        "r404a" => "R404A".to_string(),
        "r407c" => "R407C".to_string(),
        "r32" => "R32".to_string(),
        "r125" => "R125".to_string(),
        "co2" | "r744" => "CO2".to_string(),
        "r290" => "R290".to_string(),
        "r600" => "R600".to_string(),
        "r600a" => "R600A".to_string(),
        "water" => "Water".to_string(),
        "air" => "Air".to_string(),
        n => n.to_string(),
    }
}

#[cfg(feature = "coolprop")]
fn generate_from_coolprop(fluid_name: &str, output_path: &Path) -> FluidResult<()> {
    use entropyk_coolprop_sys as coolprop;
    use serde::Serialize;

    let cp_fluid = fluid_name_to_coolprop(fluid_name);
    if !unsafe { coolprop::is_fluid_available(&cp_fluid) } {
        return Err(crate::errors::FluidError::UnknownFluid {
            fluid: fluid_name.to_string(),
        });
    }

    // Critical point
    let tc = unsafe { coolprop::critical_temperature(&cp_fluid) };
    let pc = unsafe { coolprop::critical_pressure(&cp_fluid) };
    let rho_c = unsafe { coolprop::critical_density(&cp_fluid) };
    if tc.is_nan() || pc.is_nan() || rho_c.is_nan() {
        return Err(crate::errors::FluidError::NoCriticalPoint {
            fluid: fluid_name.to_string(),
        });
    }

    // Single-phase grid: P (Pa), T (K) - similar to r134a.json
    let pressures: Vec<f64> = vec![
        100_000.0,
        200_000.0,
        500_000.0,
        1_000_000.0,
        2_000_000.0,
        3_000_000.0,
    ];
    let temperatures: Vec<f64> = vec![250.0, 270.0, 290.0, 298.15, 320.0, 350.0];

    let mut density = Vec::with_capacity(pressures.len() * temperatures.len());
    let mut enthalpy = Vec::with_capacity(pressures.len() * temperatures.len());
    let mut entropy = Vec::with_capacity(pressures.len() * temperatures.len());
    let mut cp = Vec::with_capacity(pressures.len() * temperatures.len());
    let mut cv = Vec::with_capacity(pressures.len() * temperatures.len());

    for &p in &pressures {
        for &t in &temperatures {
            let d = unsafe { coolprop::props_si_pt("D", p, t, &cp_fluid) };
            let h = unsafe { coolprop::props_si_pt("H", p, t, &cp_fluid) };
            let s = unsafe { coolprop::props_si_pt("S", p, t, &cp_fluid) };
            let cp_val = unsafe { coolprop::props_si_pt("C", p, t, &cp_fluid) };
            let cv_val = unsafe { coolprop::props_si_pt("O", p, t, &cp_fluid) };
            if d.is_nan() || h.is_nan() {
                return Err(crate::errors::FluidError::InvalidState {
                    reason: format!("CoolProp NaN at P={} Pa, T={} K", p, t),
                });
            }
            density.push(d);
            enthalpy.push(h);
            entropy.push(s);
            cp.push(cp_val);
            cv.push(cv_val);
        }
    }

    // Saturation table: T from triple to critical
    let t_min = 250.0;
    let t_max = (tc - 1.0).min(350.0);
    let n_sat = 12;
    let temp_points: Vec<f64> = (0..n_sat)
        .map(|i| t_min + (t_max - t_min) * (i as f64) / ((n_sat - 1) as f64))
        .collect();

    let mut sat_temps = Vec::with_capacity(n_sat);
    let mut sat_pressure = Vec::with_capacity(n_sat);
    let mut h_liq = Vec::with_capacity(n_sat);
    let mut h_vap = Vec::with_capacity(n_sat);
    let mut rho_liq = Vec::with_capacity(n_sat);
    let mut rho_vap = Vec::with_capacity(n_sat);
    let mut s_liq = Vec::with_capacity(n_sat);
    let mut s_vap = Vec::with_capacity(n_sat);

    for &t in &temp_points {
        let p_sat = unsafe { coolprop::props_si_tq("P", t, 0.0, &cp_fluid) };
        if p_sat.is_nan() || p_sat <= 0.0 {
            continue;
        }
        sat_temps.push(t);
        sat_pressure.push(p_sat);
        h_liq.push(unsafe { coolprop::props_si_tq("H", t, 0.0, &cp_fluid) });
        h_vap.push(unsafe { coolprop::props_si_tq("H", t, 1.0, &cp_fluid) });
        rho_liq.push(unsafe { coolprop::props_si_tq("D", t, 0.0, &cp_fluid) });
        rho_vap.push(unsafe { coolprop::props_si_tq("D", t, 1.0, &cp_fluid) });
        s_liq.push(unsafe { coolprop::props_si_tq("S", t, 0.0, &cp_fluid) });
        s_vap.push(unsafe { coolprop::props_si_tq("S", t, 1.0, &cp_fluid) });
    }

    #[derive(Serialize)]
    struct JsonTable {
        fluid: String,
        critical_point: JsonCriticalPoint,
        single_phase: JsonSinglePhase,
        saturation: JsonSaturation,
    }
    #[derive(Serialize)]
    struct JsonCriticalPoint {
        tc: f64,
        pc: f64,
        rho_c: f64,
    }
    #[derive(Serialize)]
    struct JsonSinglePhase {
        pressure: Vec<f64>,
        temperature: Vec<f64>,
        density: Vec<f64>,
        enthalpy: Vec<f64>,
        entropy: Vec<f64>,
        cp: Vec<f64>,
        cv: Vec<f64>,
    }
    #[derive(Serialize)]
    struct JsonSaturation {
        temperature: Vec<f64>,
        pressure: Vec<f64>,
        h_liq: Vec<f64>,
        h_vap: Vec<f64>,
        rho_liq: Vec<f64>,
        rho_vap: Vec<f64>,
        s_liq: Vec<f64>,
        s_vap: Vec<f64>,
    }

    let json = JsonTable {
        fluid: fluid_name.to_string(),
        critical_point: JsonCriticalPoint { tc, pc, rho_c },
        single_phase: JsonSinglePhase {
            pressure: pressures,
            temperature: temperatures,
            density,
            enthalpy,
            entropy,
            cp,
            cv,
        },
        saturation: JsonSaturation {
            temperature: sat_temps,
            pressure: sat_pressure,
            h_liq,
            h_vap,
            rho_liq,
            rho_vap,
            s_liq,
            s_vap,
        },
    };

    let contents = serde_json::to_string_pretty(&json).map_err(|e| {
        crate::errors::FluidError::InvalidState {
            reason: format!("JSON serialization failed: {}", e),
        }
    })?;

    std::fs::write(output_path, contents).map_err(|e| {
        crate::errors::FluidError::TableNotFound {
            path: format!("{}: {}", output_path.display(), e),
        }
    })?;

    Ok(())
}

#[cfg(not(feature = "coolprop"))]
fn generate_from_reference(fluid_name: &str, output_path: &Path) -> FluidResult<()> {
    if fluid_name == "R134a" {
        let json = include_str!("../../data/r134a.json");
        std::fs::write(output_path, json).map_err(|e| {
            crate::errors::FluidError::TableNotFound {
                path: format!("{}: {}", output_path.display(), e),
            }
        })?;
        Ok(())
    } else {
        Err(crate::errors::FluidError::UnknownFluid {
            fluid: fluid_name.to_string(),
        })
    }
}

#[cfg(all(test, feature = "coolprop"))]
mod tests {
    use super::*;
    use crate::backend::FluidBackend;
    use crate::coolprop::CoolPropBackend;
    use crate::tabular_backend::TabularBackend;
    use crate::types::{FluidId, FluidState, Property};
    use approx::assert_relative_eq;
    use entropyk_core::{Pressure, Temperature};

    /// Validate generated tables against CoolProp spot checks (AC #2).
    #[test]
    fn test_generated_table_vs_coolprop_spot_checks() {
        let temp = std::env::temp_dir().join("entropyk_r134a_test.json");
        generate_table("R134a", &temp).expect("generate_table must succeed");

        let mut tabular = TabularBackend::new();
        tabular.load_table(&temp).unwrap();
        let _ = std::fs::remove_file(&temp);

        let coolprop = CoolPropBackend::new();
        let fluid = FluidId::new("R134a");

        // Spot check: grid point (200 kPa, 290 K)
        let state = FluidState::from_pt(
            Pressure::from_pascals(200_000.0),
            Temperature::from_kelvin(290.0),
        );
        let rho_t = tabular
            .property(fluid.clone(), Property::Density, state.clone())
            .unwrap();
        let rho_c = coolprop
            .property(fluid.clone(), Property::Density, state)
            .unwrap();
        assert_relative_eq!(rho_t, rho_c, epsilon = 0.0001 * rho_c.max(1.0));

        // Spot check: interpolated point (1 bar, 25°C)
        let state2 = FluidState::from_pt(Pressure::from_bar(1.0), Temperature::from_celsius(25.0));
        let h_t = tabular
            .property(fluid.clone(), Property::Enthalpy, state2.clone())
            .unwrap();
        let h_c = coolprop
            .property(fluid.clone(), Property::Enthalpy, state2)
            .unwrap();
        assert_relative_eq!(h_t, h_c, epsilon = 0.0001 * h_c.max(1.0));
    }
}
