//! CoolProp backend implementation.
//!
//! This module provides the `CoolPropBackend` struct that implements the `FluidBackend` trait
//! using the CoolProp C++ library for thermodynamic property calculations.

#[cfg(feature = "coolprop")]
use crate::damped_backend::DampedBackend;
use crate::errors::{FluidError, FluidResult};
use crate::types::{CriticalPoint, FluidId, FluidState, Phase, Property};

#[cfg(feature = "coolprop")]
use crate::backend::FluidBackend;
#[cfg(feature = "coolprop")]
use crate::mixture::Mixture;
#[cfg(feature = "coolprop")]
use std::collections::HashMap;
#[cfg(feature = "coolprop")]
use std::sync::RwLock;

#[cfg(feature = "coolprop")]
use entropyk_coolprop_sys as coolprop;

/// A fluid property backend using the CoolProp C++ library.
///
/// This backend provides high-accuracy thermodynamic properties using the
/// CoolProp library, which implements the NIST REFPROP equations of state.
#[cfg(feature = "coolprop")]
pub struct CoolPropBackend {
    /// Cache for critical point data
    critical_cache: RwLock<HashMap<String, CriticalPoint>>,
    /// List of available fluids
    available_fluids: Vec<FluidId>,
}

#[cfg(feature = "coolprop")]
impl CoolPropBackend {
    /// Creates a new CoolPropBackend.
    pub fn new() -> Self {
        CoolPropBackend {
            critical_cache: RwLock::new(HashMap::new()),
            available_fluids: vec![
                // HFC Refrigerants
                FluidId::new("R134a"),
                FluidId::new("R410A"),
                FluidId::new("R404A"),
                FluidId::new("R407C"),
                FluidId::new("R32"),
                FluidId::new("R125"),
                FluidId::new("R143a"),
                FluidId::new("R152A"),
                FluidId::new("R22"),
                FluidId::new("R23"),
                FluidId::new("R41"),
                FluidId::new("R245fa"),
                FluidId::new("R245ca"),
                // HFO/HFC Low-GWP Refrigerants
                FluidId::new("R1234yf"),
                FluidId::new("R1234ze(E)"),
                FluidId::new("R1234ze(Z)"),
                FluidId::new("R1233zd(E)"),
                FluidId::new("R1243zf"),
                FluidId::new("R1336mzz(E)"),
                FluidId::new("R513A"),
                FluidId::new("R454B"),
                FluidId::new("R452B"),
                FluidId::new("R32"),
                // Natural Refrigerants
                FluidId::new("R744"),
                FluidId::new("R290"),
                FluidId::new("R600"),
                FluidId::new("R600a"),
                FluidId::new("R1270"),
                FluidId::new("R717"),
                // Other Refrigerants
                FluidId::new("R11"),
                FluidId::new("R12"),
                FluidId::new("R13"),
                FluidId::new("R14"),
                FluidId::new("R113"),
                FluidId::new("R114"),
                FluidId::new("R115"),
                FluidId::new("R116"),
                FluidId::new("R123"),
                FluidId::new("R124"),
                FluidId::new("R141b"),
                FluidId::new("R142b"),
                FluidId::new("R218"),
                FluidId::new("R227EA"),
                FluidId::new("R236EA"),
                FluidId::new("R236FA"),
                FluidId::new("R365MFC"),
                FluidId::new("RC318"),
                FluidId::new("R507A"),
                // Non-Refrigerant Fluids
                FluidId::new("Water"),
                FluidId::new("Air"),
                FluidId::new("Ammonia"),
                FluidId::new("CO2"),
                FluidId::new("Nitrogen"),
                FluidId::new("Oxygen"),
                FluidId::new("Argon"),
                FluidId::new("Helium"),
                FluidId::new("Hydrogen"),
                FluidId::new("Methane"),
                FluidId::new("Ethane"),
                FluidId::new("Propane"),
                FluidId::new("Butane"),
                FluidId::new("Ethylene"),
                FluidId::new("Propylene"),
                FluidId::new("Ethanol"),
                FluidId::new("Methanol"),
                FluidId::new("Acetone"),
                FluidId::new("Benzene"),
                FluidId::new("Toluene"),
            ],
        }
    }

    /// Creates a new CoolPropBackend with critical point damping enabled.
    ///
    /// This wraps the backend with a `DampedBackend` to apply C1-continuous
    /// damping to derivative properties (Cp, Cv, etc.) near the critical point,
    /// preventing NaN values in Newton-Raphson iterations.
    pub fn with_damping() -> DampedBackend<CoolPropBackend> {
        DampedBackend::new(Self::new())
    }

    /// Get the CoolProp internal name for a fluid.
    fn fluid_name(&self, fluid: &FluidId) -> String {
        match fluid.0.to_lowercase().as_str() {
            // HFC Refrigerants
            "r134a" => "R134a".to_string(),
            "r410a" => "R410A".to_string(),
            "r404a" => "R404A".to_string(),
            "r407c" => "R407C".to_string(),
            "r32" => "R32".to_string(),
            "r125" => "R125".to_string(),
            "r143a" => "R143a".to_string(),
            "r152a" => "R152A".to_string(),
            "r22" => "R22".to_string(),
            "r23" => "R23".to_string(),
            "r41" => "R41".to_string(),
            "r245fa" => "R245fa".to_string(),
            "r245ca" => "R245ca".to_string(),
            // HFO/HFC Low-GWP Refrigerants
            "r1234yf" => "R1234yf".to_string(),
            "r1234ze" | "r1234ze(e)" => "R1234ze(E)".to_string(),
            "r1234ze(z)" => "R1234ze(Z)".to_string(),
            "r1233zd" | "r1233zd(e)" => "R1233zd(E)".to_string(),
            "r1243zf" => "R1243zf".to_string(),
            "r1336mzz" | "r1336mzz(e)" => "R1336mzz(E)".to_string(),
            "r513a" => "R513A".to_string(),
            "r513b" => "R513B".to_string(),
            "r454b" => "R454B".to_string(),
            "r452b" => "R452B".to_string(),
            // Natural Refrigerants (aliases)
            "r744" | "co2" => "CO2".to_string(),
            "r290" | "propane" => "R290".to_string(),
            "r600" | "butane" | "n-butane" => "R600".to_string(),
            "r600a" | "isobutane" => "R600A".to_string(),
            "r1270" | "propylene" => "R1270".to_string(),
            "r717" | "ammonia" => "Ammonia".to_string(),
            // Other Refrigerants
            "r11" => "R11".to_string(),
            "r12" => "R12".to_string(),
            "r13" => "R13".to_string(),
            "r14" => "R14".to_string(),
            "r113" => "R113".to_string(),
            "r114" => "R114".to_string(),
            "r115" => "R115".to_string(),
            "r116" => "R116".to_string(),
            "r123" => "R123".to_string(),
            "r124" => "R124".to_string(),
            "r141b" => "R141b".to_string(),
            "r142b" => "R142b".to_string(),
            "r218" => "R218".to_string(),
            "r227ea" => "R227EA".to_string(),
            "r236ea" => "R236EA".to_string(),
            "r236fa" => "R236FA".to_string(),
            "r365mfc" => "R365MFC".to_string(),
            "rc318" => "RC318".to_string(),
            "r507a" => "R507A".to_string(),
            // Non-Refrigerant Fluids
            "water" => "Water".to_string(),
            "air" => "Air".to_string(),
            "nitrogen" => "Nitrogen".to_string(),
            "oxygen" => "Oxygen".to_string(),
            "argon" => "Argon".to_string(),
            "helium" => "Helium".to_string(),
            "hydrogen" => "Hydrogen".to_string(),
            "methane" => "Methane".to_string(),
            "ethane" => "Ethane".to_string(),
            "ethylene" => "Ethylene".to_string(),
            "ethanol" => "Ethanol".to_string(),
            "methanol" => "Methanol".to_string(),
            "acetone" => "Acetone".to_string(),
            "benzene" => "Benzene".to_string(),
            "toluene" => "Toluene".to_string(),
            // Pass through unknown names
            n => n.to_string(),
        }
    }

    /// Convert Property to CoolProp character code.
    fn property_code(property: Property) -> &'static str {
        match property {
            Property::Density => "D",
            Property::Enthalpy => "H",
            Property::Entropy => "S",
            Property::InternalEnergy => "U",
            Property::Cp => "C",
            Property::Cv => "O", // Cv in CoolProp
            Property::SpeedOfSound => "A",
            Property::Viscosity => "V",
            Property::ThermalConductivity => "L",
            Property::SurfaceTension => "I",
            Property::Quality => "Q",
            Property::Temperature => "T",
            Property::Pressure => "P",
        }
    }
    /// Property calculation for mixtures.
    fn property_mixture(
        &self,
        _fluid: FluidId,
        property: Property,
        state: FluidState,
    ) -> FluidResult<f64> {
        // Extract mixture from state
        let mixture = match state {
            FluidState::PressureTemperatureMixture(_, _, ref m) => m.clone(),
            FluidState::PressureEnthalpyMixture(_, _, ref m) => m.clone(),
            FluidState::PressureQualityMixture(_, _, ref m) => m.clone(),
            _ => unreachable!(),
        };

        if !self.is_mixture_supported(&mixture) {
            return Err(FluidError::MixtureNotSupported(format!(
                "One or more components not available: {:?}",
                mixture.components()
            )));
        }

        let cp_string = mixture.to_coolprop_string();
        let prop_code = Self::property_code(property);

        let result = match state {
            FluidState::PressureTemperatureMixture(p, t, _) => unsafe {
                coolprop::props_si_pt(prop_code, p.to_pascals(), t.to_kelvin(), &cp_string)
            },
            FluidState::PressureEnthalpyMixture(p, h, _) => unsafe {
                coolprop::props_si_ph(prop_code, p.to_pascals(), h.to_joules_per_kg(), &cp_string)
            },
            FluidState::PressureQualityMixture(p, q, _) => unsafe {
                coolprop::props_si_px(prop_code, p.to_pascals(), q.value(), &cp_string)
            },
            _ => unreachable!(),
        };

        if result.is_nan() {
            return Err(FluidError::InvalidState {
                reason: format!("CoolProp returned NaN for mixture at {:?}", state),
            });
        }

        Ok(result)
    }

    /// Phase calculation for mixtures.
    fn phase_mix(&self, fluid: FluidId, state: FluidState) -> FluidResult<Phase> {
        let quality = self.property_mixture(fluid, Property::Quality, state)?;

        if quality < 0.0 {
            Ok(Phase::Liquid)
        } else if quality > 1.0 {
            Ok(Phase::Vapor)
        } else if (quality - 0.0).abs() < 1e-6 {
            Ok(Phase::Liquid)
        } else if (quality - 1.0).abs() < 1e-6 {
            Ok(Phase::Vapor)
        } else {
            Ok(Phase::TwoPhase)
        }
    }
}

#[cfg(feature = "coolprop")]
impl Default for CoolPropBackend {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "coolprop")]
impl crate::backend::FluidBackend for CoolPropBackend {
    fn property(&self, fluid: FluidId, property: Property, state: FluidState) -> FluidResult<f64> {
        // Handle mixture states
        if state.is_mixture() {
            return self.property_mixture(fluid, property, state);
        }

        let coolprop_fluid = self.fluid_name(&fluid);
        let prop_code = Self::property_code(property);

        // Check if fluid is available
        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid { fluid: fluid.0 });
        }

        // Query property based on state input type
        let prof = crate::profiling::enabled();
        if prof {
            crate::profiling::init_if_enabled();
        }
        let t0 = prof.then(std::time::Instant::now);
        let result = match state {
            FluidState::PressureTemperature(p, t) => unsafe {
                coolprop::props_si_pt(prop_code, p.to_pascals(), t.to_kelvin(), &coolprop_fluid)
            },
            FluidState::PressureEnthalpy(p, h) => unsafe {
                coolprop::props_si_ph(
                    prop_code,
                    p.to_pascals(),
                    h.to_joules_per_kg(),
                    &coolprop_fluid,
                )
            },
            FluidState::PressureEntropy(p, s) => unsafe {
                // CoolProp supports PropsSI(output, "P", p, "S", s, fluid)
                coolprop::props_si_ps(
                    prop_code,
                    p.to_pascals(),
                    s.to_joules_per_kg_kelvin(),
                    &coolprop_fluid,
                )
            },
            FluidState::PressureQuality(p, q) => unsafe {
                coolprop::props_si_px(prop_code, p.to_pascals(), q.value(), &coolprop_fluid)
            },
            // Mixture variants handled above
            FluidState::PressureTemperatureMixture(_, _, _) => unreachable!(),
            FluidState::PressureEnthalpyMixture(_, _, _) => unreachable!(),
            FluidState::PressureQualityMixture(_, _, _) => unreachable!(),
        };

        if let Some(t0) = t0 {
            crate::profiling::record(
                &coolprop_fluid,
                prop_code,
                crate::profiling::variant_label(&state),
                t0.elapsed(),
            );
        }

        // Check for NaN (indicates error in CoolProp)
        if result.is_nan() {
            return Err(FluidError::InvalidState {
                reason: format!("CoolProp returned NaN for {} at {:?}", fluid, state),
            });
        }

        Ok(result)
    }

    fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint> {
        // Check cache first
        if let Some(cp) = self.critical_cache.read().unwrap().get(&fluid.0) {
            return Ok(*cp);
        }

        let coolprop_fluid = self.fluid_name(&fluid);

        unsafe {
            let tc = coolprop::critical_temperature(&coolprop_fluid);
            let pc = coolprop::critical_pressure(&coolprop_fluid);
            let dc = coolprop::critical_density(&coolprop_fluid);

            if tc.is_nan() || pc.is_nan() || dc.is_nan() {
                return Err(FluidError::NoCriticalPoint { fluid: fluid.0 });
            }

            let cp = CriticalPoint::new(
                entropyk_core::Temperature::from_kelvin(tc),
                entropyk_core::Pressure::from_pascals(pc),
                dc,
            );

            // Cache the result
            self.critical_cache.write().unwrap().insert(fluid.0, cp);

            Ok(cp)
        }
    }

    fn is_fluid_available(&self, fluid: &FluidId) -> bool {
        let coolprop_fluid = self.fluid_name(fluid);
        unsafe { coolprop::is_fluid_available(&coolprop_fluid) }
    }

    fn phase(&self, fluid: FluidId, state: FluidState) -> FluidResult<Phase> {
        // Handle mixture states
        if state.is_mixture() {
            return self.phase_mix(fluid, state);
        }

        let coolprop_fluid = self.fluid_name(&fluid);

        // Pressure is the first field of every non-mixture state variant.
        let p_pa = match &state {
            FluidState::PressureTemperature(p, _)
            | FluidState::PressureEnthalpy(p, _)
            | FluidState::PressureEntropy(p, _)
            | FluidState::PressureQuality(p, _) => p.to_pascals(),
            _ => unreachable!("mixture states handled above"),
        };

        // CoolProp reports a quality in [0, 1] only inside the two-phase dome;
        // outside it returns a sentinel (typically -1) for BOTH subcooled liquid
        // and superheated vapor, so quality alone cannot classify single-phase states.
        let quality = self.property(fluid.clone(), Property::Quality, state.clone())?;

        if (0.0..=1.0).contains(&quality) {
            if (quality - 0.0).abs() < 1e-6 {
                return Ok(Phase::Liquid);
            } else if (quality - 1.0).abs() < 1e-6 {
                return Ok(Phase::Vapor);
            }
            return Ok(Phase::TwoPhase);
        }

        // Single-phase (or undefined quality): classify by temperature relative to
        // the saturation temperatures at this pressure, with a supercritical guard.
        let t_k = self.property(fluid.clone(), Property::Temperature, state)?;

        let critical = self.critical_point(fluid.clone()).ok();
        if let Some(cp) = critical {
            if p_pa >= cp.pressure.to_pascals() && t_k >= cp.temperature.to_kelvin() {
                return Ok(Phase::Supercritical);
            }
        }

        let (t_bubble, t_dew) = unsafe {
            (
                coolprop::props_si_px("T", p_pa, 0.0, &coolprop_fluid),
                coolprop::props_si_px("T", p_pa, 1.0, &coolprop_fluid),
            )
        };

        // Saturation undefined (e.g. pressure at/above the critical pressure):
        // split by critical temperature when available, otherwise report Unknown.
        if t_bubble.is_nan() || t_dew.is_nan() {
            if let Some(cp) = critical {
                if p_pa >= cp.pressure.to_pascals() {
                    return Ok(if t_k >= cp.temperature.to_kelvin() {
                        Phase::Supercritical
                    } else {
                        Phase::Liquid
                    });
                }
            }
            return Ok(Phase::Unknown);
        }

        if t_k <= t_bubble {
            Ok(Phase::Liquid)
        } else if t_k >= t_dew {
            Ok(Phase::Vapor)
        } else {
            Ok(Phase::TwoPhase)
        }
    }

    fn list_fluids(&self) -> Vec<FluidId> {
        self.available_fluids.clone()
    }

    fn bubble_point(
        &self,
        pressure: entropyk_core::Pressure,
        mixture: &Mixture,
    ) -> FluidResult<entropyk_core::Temperature> {
        if !self.is_mixture_supported(mixture) {
            return Err(FluidError::MixtureNotSupported(format!(
                "One or more components not available: {:?}",
                mixture.components()
            )));
        }

        let cp_string = mixture.to_coolprop_string();
        let p_pa = pressure.to_pascals();

        unsafe {
            // For bubble point (saturated liquid), use a (P, Q=0) flash.
            let t = coolprop::props_si_px("T", p_pa, 0.0, &cp_string);
            if t.is_nan() {
                return Err(FluidError::NumericalError(
                    "CoolProp returned NaN for bubble point calculation".to_string(),
                ));
            }
            Ok(entropyk_core::Temperature::from_kelvin(t))
        }
    }

    fn dew_point(
        &self,
        pressure: entropyk_core::Pressure,
        mixture: &Mixture,
    ) -> FluidResult<entropyk_core::Temperature> {
        if !self.is_mixture_supported(mixture) {
            return Err(FluidError::MixtureNotSupported(format!(
                "One or more components not available: {:?}",
                mixture.components()
            )));
        }

        let cp_string = mixture.to_coolprop_string();
        let p_pa = pressure.to_pascals();

        unsafe {
            // For dew point (saturated vapor), use a (P, Q=1) flash.
            let t = coolprop::props_si_px("T", p_pa, 1.0, &cp_string);
            if t.is_nan() {
                return Err(FluidError::NumericalError(
                    "CoolProp returned NaN for dew point calculation".to_string(),
                ));
            }
            Ok(entropyk_core::Temperature::from_kelvin(t))
        }
    }

    fn is_mixture_supported(&self, mixture: &Mixture) -> bool {
        mixture
            .components()
            .iter()
            .all(|c| self.is_fluid_available(&FluidId::new(c)))
    }

    fn full_state(
        &self,
        fluid: FluidId,
        p: entropyk_core::Pressure,
        h: entropyk_core::Enthalpy,
    ) -> FluidResult<crate::types::ThermoState> {
        let coolprop_fluid = self.fluid_name(&fluid);

        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid { fluid: fluid.0 });
        }

        let p_pa = p.to_pascals();
        let h_j_kg = h.to_joules_per_kg();

        unsafe {
            let t_k = coolprop::props_si_ph("T", p_pa, h_j_kg, &coolprop_fluid);
            if t_k.is_nan() {
                return Err(FluidError::InvalidState {
                    reason: format!(
                        "CoolProp returned NaN for Temperature at P={}, h={} for {}",
                        p_pa, h_j_kg, fluid
                    ),
                });
            }

            let s_j_kg_k = coolprop::props_si_ph("S", p_pa, h_j_kg, &coolprop_fluid);
            let d_kg_m3 = coolprop::props_si_ph("D", p_pa, h_j_kg, &coolprop_fluid);
            let q = coolprop::props_si_ph("Q", p_pa, h_j_kg, &coolprop_fluid);

            let phase = self.phase(fluid.clone(), FluidState::from_ph(p, h))?;

            let quality = if (0.0..=1.0).contains(&q) {
                Some(crate::types::Quality::new(q))
            } else {
                None
            };

            let t_bubble = coolprop::props_si_px("T", p_pa, 0.0, &coolprop_fluid);
            let t_dew = coolprop::props_si_px("T", p_pa, 1.0, &coolprop_fluid);

            let (t_bubble_opt, subcooling) = if !t_bubble.is_nan() {
                (
                    Some(entropyk_core::Temperature::from_kelvin(t_bubble)),
                    if t_k < t_bubble {
                        Some(crate::types::TemperatureDelta::new(t_bubble - t_k))
                    } else {
                        None
                    },
                )
            } else {
                (None, None)
            };

            let (t_dew_opt, superheat) = if !t_dew.is_nan() {
                (
                    Some(entropyk_core::Temperature::from_kelvin(t_dew)),
                    if t_k > t_dew {
                        Some(crate::types::TemperatureDelta::new(t_k - t_dew))
                    } else {
                        None
                    },
                )
            } else {
                (None, None)
            };

            Ok(crate::types::ThermoState {
                fluid,
                pressure: p,
                temperature: entropyk_core::Temperature::from_kelvin(t_k),
                enthalpy: h,
                entropy: crate::types::Entropy::from_joules_per_kg_kelvin(s_j_kg_k),
                density: d_kg_m3,
                phase,
                quality,
                superheat,
                subcooling,
                t_bubble: t_bubble_opt,
                t_dew: t_dew_opt,
            })
        }
    }

    fn saturation_pressure_t(&self, fluid: FluidId, t_k: f64) -> FluidResult<f64> {
        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid { fluid: fluid.0 });
        }
        let coolprop_fluid = self.fluid_name(&fluid);
        let result = unsafe { coolprop::props_si_tq("P", t_k, 0.0, &coolprop_fluid) };
        if result.is_nan() {
            return Err(FluidError::InvalidState {
                reason: format!(
                    "CoolProp returned NaN for P_sat at T={:.2}K for {}",
                    t_k, fluid
                ),
            });
        }
        Ok(result)
    }

    fn saturation_enthalpy_t(&self, fluid: FluidId, t_k: f64, quality: f64) -> FluidResult<f64> {
        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid { fluid: fluid.0 });
        }
        let coolprop_fluid = self.fluid_name(&fluid);
        let result = unsafe { coolprop::props_si_tq("H", t_k, quality, &coolprop_fluid) };
        if result.is_nan() {
            return Err(FluidError::InvalidState {
                reason: format!(
                    "CoolProp returned NaN for H_sat at T={:.2}K, Q={} for {}",
                    t_k, quality, fluid
                ),
            });
        }
        Ok(result)
    }
}

/// A placeholder backend when CoolProp is not available.
///
/// This allows the crate to compile without CoolProp, but property
/// queries will return errors.
#[cfg(not(feature = "coolprop"))]
pub struct CoolPropBackend;

#[cfg(not(feature = "coolprop"))]
impl CoolPropBackend {
    /// Creates a new CoolPropBackend (placeholder).
    pub fn new() -> Self {
        CoolPropBackend
    }
}

#[cfg(not(feature = "coolprop"))]
impl Default for CoolPropBackend {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(not(feature = "coolprop"))]
impl crate::backend::FluidBackend for CoolPropBackend {
    fn property(
        &self,
        _fluid: FluidId,
        _property: Property,
        _state: FluidState,
    ) -> FluidResult<f64> {
        Err(FluidError::CoolPropError(
            "CoolProp not available. Enable 'coolprop' feature to use this backend.".to_string(),
        ))
    }

    fn critical_point(&self, _fluid: FluidId) -> FluidResult<CriticalPoint> {
        Err(FluidError::CoolPropError(
            "CoolProp not available. Enable 'coolprop' feature to use this backend.".to_string(),
        ))
    }

    fn is_fluid_available(&self, _fluid: &FluidId) -> bool {
        false
    }

    fn phase(&self, _fluid: FluidId, _state: FluidState) -> FluidResult<Phase> {
        Err(FluidError::CoolPropError(
            "CoolProp not available. Enable 'coolprop' feature to use this backend.".to_string(),
        ))
    }

    fn list_fluids(&self) -> Vec<FluidId> {
        Vec::new()
    }

    fn full_state(
        &self,
        _fluid: FluidId,
        _p: entropyk_core::Pressure,
        _h: entropyk_core::Enthalpy,
    ) -> FluidResult<crate::types::ThermoState> {
        Err(FluidError::CoolPropError(
            "CoolProp not available. Enable 'coolprop' feature to use this backend.".to_string(),
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::backend::FluidBackend;
    #[cfg(feature = "coolprop")]
    use crate::mixture::Mixture;
    #[cfg(feature = "coolprop")]
    use approx::assert_abs_diff_eq;
    #[cfg(feature = "coolprop")]
    use entropyk_core::{Pressure, Temperature};

    #[cfg(feature = "coolprop")]
    const R454B_REFERENCE_PRESSURE_PA: f64 = 1e6;
    #[cfg(feature = "coolprop")]
    const R454B_REFERENCE_BUBBLE_POINT_K: f64 = 288.145_565_440_567_5;
    #[cfg(feature = "coolprop")]
    const R454B_REFERENCE_DEW_POINT_K: f64 = 295.328_418_405_157_1;
    #[cfg(feature = "coolprop")]
    const R454B_REFERENCE_GLIDE_K: f64 =
        R454B_REFERENCE_DEW_POINT_K - R454B_REFERENCE_BUBBLE_POINT_K;
    #[cfg(feature = "coolprop")]
    const R454B_REFERENCE_TOLERANCE_K: f64 = 0.5;

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_backend_creation() {
        let backend = CoolPropBackend::new();
        let fluids = backend.list_fluids();
        assert!(!fluids.is_empty());
    }

    #[test]
    #[cfg(not(feature = "coolprop"))]
    fn test_backend_without_feature() {
        use crate::types::FluidState;
        use entropyk_core::{Pressure, Temperature};

        let backend = CoolPropBackend::new();
        let result = backend.property(
            FluidId::new("R134a"),
            Property::Density,
            FluidState::from_pt(Pressure::from_bar(1.0), Temperature::from_celsius(25.0)),
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_fluid_name_mapping() {
        #[cfg(feature = "coolprop")]
        {
            let backend = CoolPropBackend::new();
            assert_eq!(backend.fluid_name(&FluidId::new("R134a")), "R134a");
            assert_eq!(backend.fluid_name(&FluidId::new("CO2")), "CO2");
            assert_eq!(backend.fluid_name(&FluidId::new("R744")), "CO2");
        }
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_mixture_is_supported() {
        let backend = CoolPropBackend::new();

        // R454B = R32 + R1234yf (both available in CoolProp)
        let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();
        assert!(backend.is_mixture_supported(&mixture));

        // Unknown component should fail
        let bad_mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R999", 0.5)]).unwrap();
        assert!(!backend.is_mixture_supported(&bad_mixture));
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_bubble_point_r454b() {
        let backend = CoolPropBackend::new();
        let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();

        let pressure = Pressure::from_pascals(R454B_REFERENCE_PRESSURE_PA);
        let t_bubble = backend.bubble_point(pressure, &mixture).unwrap();

        assert_abs_diff_eq!(
            t_bubble.to_kelvin(),
            R454B_REFERENCE_BUBBLE_POINT_K,
            epsilon = R454B_REFERENCE_TOLERANCE_K
        );
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_dew_point_r454b() {
        let backend = CoolPropBackend::new();
        let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();

        let pressure = Pressure::from_pascals(R454B_REFERENCE_PRESSURE_PA);
        let t_dew = backend.dew_point(pressure, &mixture).unwrap();

        assert_abs_diff_eq!(
            t_dew.to_kelvin(),
            R454B_REFERENCE_DEW_POINT_K,
            epsilon = R454B_REFERENCE_TOLERANCE_K
        );
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_temperature_glide_nonzero() {
        let backend = CoolPropBackend::new();
        let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();

        let pressure = Pressure::from_pascals(R454B_REFERENCE_PRESSURE_PA);
        let glide = backend.temperature_glide(pressure, &mixture).unwrap();

        assert_abs_diff_eq!(
            glide,
            R454B_REFERENCE_GLIDE_K,
            epsilon = R454B_REFERENCE_TOLERANCE_K
        );
        assert!(
            glide > 0.0,
            "Expected positive temperature glide for zeotropic mixture"
        );
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_mixture_property_lookup() {
        let backend = CoolPropBackend::new();
        let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();

        // Test (P, T) mixture state
        let state = FluidState::from_pt_mix(
            Pressure::from_bar(10.0),
            Temperature::from_celsius(50.0),
            mixture,
        );

        let density = backend
            .property(FluidId::new("R454B"), Property::Density, state)
            .unwrap();

        assert!(density > 0.0);
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_full_state_extraction() {
        let backend = CoolPropBackend::new();
        let fluid = FluidId::new("R134a");
        let pressure = Pressure::from_bar(1.0);
        let enthalpy = entropyk_core::Enthalpy::from_kilojoules_per_kg(415.0); // Superheated vapor region

        let state = backend
            .full_state(fluid.clone(), pressure, enthalpy)
            .unwrap();

        assert_eq!(state.fluid, fluid);
        assert_eq!(state.pressure, pressure);
        assert_eq!(state.enthalpy, enthalpy);

        // Temperature should be valid
        assert!(state.temperature.to_celsius() > -30.0);
        assert!(state.density > 0.0);
        assert!(state.entropy.to_joules_per_kg_kelvin() > 0.0);

        // In superheated region, phase is Vapor, quality should be None, and superheat should exist
        assert_eq!(state.phase, Phase::Vapor);
        assert_eq!(state.quality, None);
        assert!(state.superheat.is_some());
        assert!(state.superheat.unwrap().kelvin() > 0.0);
        assert!(state.subcooling.is_none());
        assert!(state.t_dew.is_some());
        assert!(state.t_bubble.is_some());
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_phase_superheated_vapor_r134a() {
        let backend = CoolPropBackend::new();
        // R134a at 1 bar, 50°C is well into the superheated vapor region.
        let state = FluidState::from_pt(Pressure::from_bar(1.0), Temperature::from_celsius(50.0));
        let phase = backend.phase(FluidId::new("R134a"), state).unwrap();
        assert_eq!(phase, Phase::Vapor);
    }

    #[test]
    #[cfg(feature = "coolprop")]
    fn test_phase_subcooled_liquid_r134a() {
        let backend = CoolPropBackend::new();
        // R134a at 10 bar saturates near 39°C; 10°C is subcooled liquid.
        let state = FluidState::from_pt(Pressure::from_bar(10.0), Temperature::from_celsius(10.0));
        let phase = backend.phase(FluidId::new("R134a"), state).unwrap();
        assert_eq!(phase, Phase::Liquid);
    }
}
