//! Test backend implementation for unit testing.
//!
//! This module provides a mock backend that returns simplified/idealized
//! property values for testing without requiring external dependencies
//! like CoolProp.

use crate::backend::FluidBackend;
use crate::errors::{FluidError, FluidResult};
#[cfg(test)]
use crate::mixture::Mixture;
use crate::types::{CriticalPoint, FluidId, FluidState, Phase, Property};
use entropyk_core::{Pressure, Temperature};
use std::collections::HashMap;

/// Test backend for unit testing.
///
/// This backend provides simplified thermodynamic property calculations
/// suitable for testing without external dependencies. Values are idealized
/// approximations and should NOT be used for real simulations.
pub struct TestBackend {
    /// Map of fluid names to critical points
    critical_points: HashMap<String, CriticalPoint>,
    /// List of available test fluids
    available_fluids: Vec<String>,
}

impl TestBackend {
    /// Creates a new TestBackend with default test fluids.
    pub fn new() -> Self {
        let mut critical_points = HashMap::new();

        // CO2 (R744)
        critical_points.insert(
            "CO2".to_string(),
            CriticalPoint::new(
                Temperature::from_kelvin(304.13),
                Pressure::from_pascals(7.3773e6),
                467.0,
            ),
        );

        // R134a
        critical_points.insert(
            "R134a".to_string(),
            CriticalPoint::new(
                Temperature::from_kelvin(374.21),
                Pressure::from_pascals(4.059e6),
                512.0,
            ),
        );

        // R410A
        critical_points.insert(
            "R410A".to_string(),
            CriticalPoint::new(
                Temperature::from_kelvin(344.49),
                Pressure::from_pascals(4.926e6),
                458.0,
            ),
        );

        // R32
        critical_points.insert(
            "R32".to_string(),
            CriticalPoint::new(
                Temperature::from_kelvin(351.25),
                Pressure::from_pascals(5.782e6),
                360.0,
            ),
        );

        // Water
        critical_points.insert(
            "Water".to_string(),
            CriticalPoint::new(
                Temperature::from_kelvin(647.096),
                Pressure::from_pascals(22.064e6),
                322.0,
            ),
        );

        let available_fluids = vec![
            "CO2".to_string(),
            "R134a".to_string(),
            "R410A".to_string(),
            "R32".to_string(),
            "Water".to_string(),
            "Nitrogen".to_string(),
            "Oxygen".to_string(),
            "Air".to_string(),
        ];

        TestBackend {
            critical_points,
            available_fluids,
        }
    }

    /// Simplified ideal gas property calculation.
    fn ideal_property(
        &self,
        fluid: &str,
        property: Property,
        state: FluidState,
    ) -> FluidResult<f64> {
        // Simple ideal gas approximations for testing
        // Real implementation would use proper equations of state
        match fluid {
            "Nitrogen" | "Oxygen" | "Air" => self.ideal_gas_property(property, state, 29.0),
            "Water" => self.water_property(property, state),
            _ => {
                // For refrigerants, use simplified correlations
                self.refrigerant_property(fluid, property, state)
            }
        }
    }

    fn ideal_gas_property(
        &self,
        property: Property,
        state: FluidState,
        _molar_mass: f64,
    ) -> FluidResult<f64> {
        let (p, t) = match state {
            FluidState::PressureTemperature(p, t) => (p.to_pascals(), t.to_kelvin()),
            _ => {
                return Err(FluidError::InvalidState {
                    reason: "TestBackend only supports P-T state for ideal gases".to_string(),
                })
            }
        };

        // Simplified ideal gas: R = 8.314 J/(mol·K), approximate
        let r_specific = 287.0; // J/(kg·K) for air

        match property {
            Property::Density => Ok(p / (r_specific * t)),
            Property::Enthalpy => Ok(1005.0 * t), // Cp * T, Cp ≈ 1005 J/(kg·K) for air
            Property::Entropy => Ok(r_specific * t.ln()), // Simplified
            Property::Cp => Ok(1005.0),           // Constant pressure specific heat
            Property::Cv => Ok(718.0),            // Constant volume specific heat
            Property::Temperature => Ok(t),
            Property::Pressure => Ok(p),
            Property::ThermalConductivity => Ok(0.025), // W/(m·K) for air
            Property::Viscosity => Ok(1.8e-5),          // Pa·s for air
            _ => Err(FluidError::UnsupportedProperty {
                property: property.to_string(),
            }),
        }
    }

    fn water_property(&self, property: Property, state: FluidState) -> FluidResult<f64> {
        let (p, t) = match state {
            FluidState::PressureTemperature(p, t) => (p.to_pascals(), t.to_kelvin()),
            _ => {
                return Err(FluidError::InvalidState {
                    reason: "TestBackend only supports P-T state for water".to_string(),
                })
            }
        };

        // Simplified water properties at ~1 atm
        if p < 1.1e5 && t > 273.15 && t < 373.15 {
            match property {
                Property::Density => Ok(1000.0),                 // kg/m³
                Property::Enthalpy => Ok(4200.0 * (t - 273.15)), // Cp * ΔT
                Property::Cp => Ok(4184.0),                      // J/(kg·K)
                Property::ThermalConductivity => Ok(0.6),        // W/(m·K)
                Property::Viscosity => Ok(0.001),                // Pa·s
                Property::Temperature => Ok(t),
                Property::Pressure => Ok(p),
                _ => Err(FluidError::UnsupportedProperty {
                    property: property.to_string(),
                }),
            }
        } else {
            Err(FluidError::InvalidState {
                reason: "Water property only valid in liquid region".to_string(),
            })
        }
    }

    fn refrigerant_property(
        &self,
        _fluid: &str,
        property: Property,
        state: FluidState,
    ) -> FluidResult<f64> {
        let (p, t) = match state {
            FluidState::PressureTemperature(p, t) => (p.to_pascals(), t.to_kelvin()),
            _ => {
                return Err(FluidError::InvalidState {
                    reason: "TestBackend only supports P-T state for refrigerants".to_string(),
                })
            }
        };

        // Simplified refrigerant properties
        match property {
            Property::Density => {
                // Rough approximation for liquid (~1000 kg/m³) vs vapor (~10-50 kg/m³)
                if p > 1e6 {
                    Ok(1000.0) // Liquid
                } else {
                    Ok(30.0) // Vapor
                }
            }
            Property::Enthalpy => {
                if p > 1e6 {
                    Ok(200000.0) // Liquid region
                } else {
                    Ok(400000.0) // Vapor region
                }
            }
            Property::Cp => Ok(1500.0), // Approximate
            Property::Temperature => Ok(t),
            Property::Pressure => Ok(p),
            Property::ThermalConductivity => Ok(0.015),
            Property::Viscosity => Ok(1.5e-5),
            _ => Err(FluidError::UnsupportedProperty {
                property: property.to_string(),
            }),
        }
    }

    fn determine_phase(&self, fluid: &str, state: FluidState) -> Phase {
        let (p, t) = match state {
            FluidState::PressureTemperature(p, t) => (p.to_pascals(), t.to_kelvin()),
            _ => return Phase::Unknown,
        };

        // Get critical point if available
        if let Some(cp) = self.critical_points.get(fluid) {
            let pc = cp.pressure_pascals();
            let tc = cp.temperature_kelvin();

            if p > pc && t > tc {
                return Phase::Supercritical;
            }
            if (p - pc).abs() / pc < 0.05 || (t - tc).abs() / tc < 0.05 {
                return Phase::Supercritical;
            }
        }

        // Simplified phase determination
        if p > 5e5 {
            Phase::Liquid
        } else {
            Phase::Vapor
        }
    }
}

impl Default for TestBackend {
    fn default() -> Self {
        Self::new()
    }
}

impl FluidBackend for TestBackend {
    fn property(&self, fluid: FluidId, property: Property, state: FluidState) -> FluidResult<f64> {
        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid {
                fluid: fluid.0.clone(),
            });
        }

        self.ideal_property(&fluid.0, property, state)
    }

    fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint> {
        self.critical_points
            .get(&fluid.0)
            .copied()
            .ok_or(FluidError::NoCriticalPoint { fluid: fluid.0 })
    }

    fn is_fluid_available(&self, fluid: &FluidId) -> bool {
        self.available_fluids.iter().any(|f| f == &fluid.0)
    }

    fn phase(&self, fluid: FluidId, state: FluidState) -> FluidResult<Phase> {
        if !self.is_fluid_available(&fluid) {
            return Err(FluidError::UnknownFluid { fluid: fluid.0 });
        }

        Ok(self.determine_phase(&fluid.0, state))
    }

    fn list_fluids(&self) -> Vec<FluidId> {
        self.available_fluids
            .iter()
            .map(|s| FluidId::new(s.clone()))
            .collect()
    }

    fn full_state(
        &self,
        fluid: FluidId,
        p: entropyk_core::Pressure,
        h: entropyk_core::Enthalpy,
    ) -> FluidResult<crate::types::ThermoState> {
        let t_k = self.property(
            fluid.clone(),
            Property::Temperature,
            FluidState::from_ph(p, h),
        )?;
        Err(FluidError::UnsupportedProperty {
            property: format!("full_state for TestBackend: Temperature is {:.2} K", t_k),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_backend_available_fluids() {
        let backend = TestBackend::new();

        assert!(backend.is_fluid_available(&FluidId::new("CO2")));
        assert!(backend.is_fluid_available(&FluidId::new("R134a")));
        assert!(!backend.is_fluid_available(&FluidId::new("R999")));
    }

    #[test]
    fn test_list_fluids() {
        let backend = TestBackend::new();
        let fluids = backend.list_fluids();

        assert!(fluids.len() > 0);
        assert!(fluids.iter().any(|f| f.0 == "CO2"));
    }

    #[test]
    fn test_critical_point() {
        let backend = TestBackend::new();

        let cp = backend.critical_point(FluidId::new("CO2")).unwrap();
        assert!((cp.temperature_kelvin() - 304.13).abs() < 0.1);
        assert!((cp.pressure_pascals() - 7.3773e6).abs() < 1e4);
    }

    #[test]
    fn test_critical_point_not_available() {
        let backend = TestBackend::new();

        let result = backend.critical_point(FluidId::new("UnknownFluid"));
        assert!(result.is_err());
    }

    #[test]
    fn test_property_nitrogen() {
        let backend = TestBackend::new();

        let state = FluidState::from_pt(
            Pressure::from_pascals(101325.0),
            Temperature::from_kelvin(300.0),
        );

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

        assert!(density > 1.0); // Should be ~1.16 kg/m³
    }

    #[test]
    fn test_property_water() {
        let backend = TestBackend::new();

        let state = FluidState::from_pt(
            Pressure::from_pascals(101325.0),
            Temperature::from_celsius(25.0),
        );

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

        assert!((density - 1000.0).abs() < 1.0);
    }

    #[test]
    fn test_property_unknown_fluid() {
        let backend = TestBackend::new();

        let state = FluidState::from_pt(
            Pressure::from_pascals(101325.0),
            Temperature::from_kelvin(300.0),
        );

        let result = backend.property(FluidId::new("R999"), Property::Density, state);

        assert!(result.is_err());
    }

    #[test]
    fn test_phase_co2_supercritical() {
        let backend = TestBackend::new();

        // Above critical point
        let state =
            FluidState::from_pt(Pressure::from_pascals(8e6), Temperature::from_kelvin(320.0));

        let phase = backend.phase(FluidId::new("CO2"), state).unwrap();
        assert_eq!(phase, Phase::Supercritical);
    }

    #[test]
    fn test_phase_liquid() {
        let backend = TestBackend::new();

        let state = FluidState::from_pt(
            Pressure::from_pascals(10e5),
            Temperature::from_celsius(25.0),
        );

        let phase = backend.phase(FluidId::new("Water"), state).unwrap();
        assert_eq!(phase, Phase::Liquid);
    }

    #[test]
    fn test_thermo_state_is_mixture() {
        let mix = Mixture::from_mass_fractions(&[("R32", 0.5), ("R1234yf", 0.5)]).unwrap();

        let state_pure =
            FluidState::from_pt(Pressure::from_bar(1.0), Temperature::from_celsius(25.0));
        assert!(!state_pure.is_mixture());

        let state_mix = FluidState::from_pt_mix(
            Pressure::from_bar(1.0),
            Temperature::from_celsius(25.0),
            mix,
        );
        assert!(state_mix.is_mixture());
    }
}
