//! Danfoss compressor data backend.
//!
//! Loads AHRI 540 compressor coefficients from JSON files in the
//! `data/danfoss/compressors/` directory.

use std::collections::HashMap;
use std::path::PathBuf;

use crate::error::VendorError;
use crate::vendor_api::{BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend};

/// Backend for Danfoss scroll compressor data.
///
/// Loads an index file (`index.json`) listing available compressor models,
/// then eagerly pre-caches each model's JSON file into memory.
///
/// # Example
///
/// ```no_run
/// use entropyk_vendors::compressors::danfoss::DanfossBackend;
/// use entropyk_vendors::VendorBackend;
///
/// let backend = DanfossBackend::new().expect("load danfoss data");
/// let models = backend.list_compressor_models().unwrap();
/// println!("Available: {:?}", models);
/// ```
#[derive(Debug)]
pub struct DanfossBackend {
    /// Root path to the Danfoss data directory.
    data_path: PathBuf,
    /// Pre-loaded compressor coefficients keyed by model name.
    compressor_cache: HashMap<String, CompressorCoefficients>,
    /// Sorted list of available models.
    sorted_models: Vec<String>,
}

impl DanfossBackend {
    /// Create a new Danfoss backend, loading all compressor models from disk.
    ///
    /// The data directory is resolved via the `ENTROPYK_DATA` environment variable.
    /// If unset, it falls back to the compile-time `CARGO_MANIFEST_DIR/data` in debug mode,
    /// or `./data` in release mode.
    pub fn new() -> Result<Self, VendorError> {
        let base_path = std::env::var("ENTROPYK_DATA")
            .map(PathBuf::from)
            .unwrap_or_else(|_| {
                #[cfg(debug_assertions)]
                {
                    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data")
                }
                #[cfg(not(debug_assertions))]
                {
                    PathBuf::from("data")
                }
            });

        let data_path = base_path.join("danfoss");

        let mut backend = Self {
            data_path,
            compressor_cache: HashMap::new(),
            sorted_models: Vec::new(),
        };

        backend.load_index()?;

        Ok(backend)
    }

    /// Create a new Danfoss backend from a custom data path.
    ///
    /// Useful for testing with alternative data directories.
    pub fn from_path(data_path: PathBuf) -> Result<Self, VendorError> {
        let mut backend = Self {
            data_path,
            compressor_cache: HashMap::new(),
            sorted_models: Vec::new(),
        };

        backend.load_index()?;

        Ok(backend)
    }

    /// Load the compressor index and pre-cache all referenced models.
    fn load_index(&mut self) -> Result<(), VendorError> {
        let index_path = self.data_path.join("compressors").join("index.json");
        let index_content =
            std::fs::read_to_string(&index_path).map_err(|e| VendorError::IoError {
                path: index_path.display().to_string(),
                source: e,
            })?;

        let models: Vec<String> = serde_json::from_str(&index_content).map_err(|e| {
            VendorError::InvalidFormat(format!("Parse error in {}: {}", index_path.display(), e))
        })?;

        for model in models {
            match self.load_model(&model) {
                Ok(coeffs) => {
                    self.compressor_cache.insert(model.clone(), coeffs);
                    self.sorted_models.push(model);
                }
                Err(e) => {
                    log::warn!("[entropyk-vendors] Skipping Danfoss model {}: {}", model, e);
                }
            }
        }
        self.sorted_models.sort();

        Ok(())
    }

    /// Load a single compressor model from its JSON file.
    fn load_model(&self, model: &str) -> Result<CompressorCoefficients, VendorError> {
        if model.contains('/') || model.contains('\\') || model.contains("..") {
            return Err(VendorError::ModelNotFound(model.to_string()));
        }

        let model_path = self
            .data_path
            .join("compressors")
            .join(format!("{}.json", model));

        let content = std::fs::read_to_string(&model_path).map_err(|e| VendorError::IoError {
            path: model_path.display().to_string(),
            source: e,
        })?;

        let coeffs: CompressorCoefficients = serde_json::from_str(&content).map_err(|e| {
            VendorError::InvalidFormat(format!("Parse error in {}: {}", model_path.display(), e))
        })?;

        Ok(coeffs)
    }
}

impl VendorBackend for DanfossBackend {
    fn vendor_name(&self) -> &str {
        "Danfoss"
    }

    fn list_compressor_models(&self) -> Result<Vec<String>, VendorError> {
        Ok(self.sorted_models.clone())
    }

    fn get_compressor_coefficients(
        &self,
        model: &str,
    ) -> Result<CompressorCoefficients, VendorError> {
        self.compressor_cache
            .get(model)
            .cloned()
            .ok_or_else(|| VendorError::ModelNotFound(model.to_string()))
    }

    fn list_bphx_models(&self) -> Result<Vec<String>, VendorError> {
        // Danfoss does not provide BPHX data
        Ok(vec![])
    }

    fn get_bphx_parameters(&self, model: &str) -> Result<BphxParameters, VendorError> {
        Err(VendorError::InvalidFormat(format!(
            "Danfoss does not provide BPHX data (requested: {})",
            model
        )))
    }

    fn compute_ua(&self, model: &str, _params: &UaCalcParams) -> Result<f64, VendorError> {
        Err(VendorError::InvalidFormat(format!(
            "Danfoss does not provide BPHX/UA data (requested: {})",
            model
        )))
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    #[test]
    fn test_danfoss_backend_new() {
        let backend = DanfossBackend::new();
        assert!(backend.is_ok(), "DanfossBackend::new() should succeed");
    }

    #[test]
    fn test_danfoss_backend_from_path() {
        let base_path = std::env::var("ENTROPYK_DATA")
            .map(PathBuf::from)
            .unwrap_or_else(|_| {
                #[cfg(debug_assertions)]
                {
                    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data")
                }
                #[cfg(not(debug_assertions))]
                {
                    PathBuf::from("data")
                }
            });

        let backend = DanfossBackend::from_path(base_path.join("danfoss"));
        assert!(
            backend.is_ok(),
            "DanfossBackend::from_path() should succeed"
        );
    }

    #[test]
    fn test_danfoss_vendor_name() {
        let backend = DanfossBackend::new().unwrap();
        assert_eq!(backend.vendor_name(), "Danfoss");
    }

    #[test]
    fn test_danfoss_list_compressor_models() {
        let backend = DanfossBackend::new().unwrap();
        let models = backend.list_compressor_models().unwrap();
        assert_eq!(models.len(), 2);
        assert!(models.contains(&"SH140-4".to_string()));
        assert!(models.contains(&"SH090-4".to_string()));
        assert_eq!(models, vec!["SH090-4".to_string(), "SH140-4".to_string()]);
    }

    #[test]
    fn test_danfoss_get_compressor_sh140() {
        let backend = DanfossBackend::new().unwrap();
        let coeffs = backend.get_compressor_coefficients("SH140-4").unwrap();

        assert_eq!(coeffs.model, "SH140-4");
        assert_eq!(coeffs.manufacturer, "Danfoss");
        assert_eq!(coeffs.refrigerant, "R410A");
        assert_eq!(coeffs.capacity_coeffs.len(), 10);
        assert_eq!(coeffs.power_coeffs.len(), 10);
        // Check first capacity coefficient
        assert!((coeffs.capacity_coeffs[0] - 38000.0).abs() < 1e-10);
        // Check first power coefficient
        assert!((coeffs.power_coeffs[0] - 9500.0).abs() < 1e-10);
        // Check last capacity coefficient
        assert!((coeffs.capacity_coeffs[9] - 0.015).abs() < 1e-10);
        // Check last power coefficient
        assert!((coeffs.power_coeffs[9] - 0.008).abs() < 1e-10);
        // mass_flow_coeffs not provided in Danfoss data
        assert!(coeffs.mass_flow_coeffs.is_none());
    }

    #[test]
    fn test_danfoss_get_compressor_sh090() {
        let backend = DanfossBackend::new().unwrap();
        let coeffs = backend.get_compressor_coefficients("SH090-4").unwrap();

        assert_eq!(coeffs.model, "SH090-4");
        assert_eq!(coeffs.manufacturer, "Danfoss");
        assert_eq!(coeffs.refrigerant, "R410A");
        assert!((coeffs.capacity_coeffs[0] - 25000.0).abs() < 1e-10);
        assert!((coeffs.power_coeffs[0] - 6000.0).abs() < 1e-10);
        assert!((coeffs.capacity_coeffs[9] - 0.01).abs() < 1e-10);
        assert!((coeffs.power_coeffs[9] - 0.005).abs() < 1e-10);
    }

    #[test]
    fn test_danfoss_validity_range() {
        let backend = DanfossBackend::new().unwrap();
        let coeffs = backend.get_compressor_coefficients("SH140-4").unwrap();

        assert!((coeffs.validity.t_suction_min - (-15.0)).abs() < 1e-10);
        assert!((coeffs.validity.t_suction_max - 15.0).abs() < 1e-10);
        assert!((coeffs.validity.t_discharge_min - 30.0).abs() < 1e-10);
        assert!((coeffs.validity.t_discharge_max - 65.0).abs() < 1e-10);
    }

    #[test]
    fn test_danfoss_model_not_found() {
        let backend = DanfossBackend::new().unwrap();
        let result = backend.get_compressor_coefficients("NONEXISTENT");
        assert!(result.is_err());
        match result.unwrap_err() {
            VendorError::ModelNotFound(m) => assert_eq!(m, "NONEXISTENT"),
            other => panic!("Expected ModelNotFound, got: {:?}", other),
        }
    }

    #[test]
    fn test_danfoss_list_bphx_empty() {
        let backend = DanfossBackend::new().unwrap();
        let models = backend.list_bphx_models().unwrap();
        assert!(models.is_empty());
    }

    #[test]
    fn test_danfoss_get_bphx_returns_error() {
        let backend = DanfossBackend::new().unwrap();
        let result = backend.get_bphx_parameters("anything");
        assert!(result.is_err());
        match result.unwrap_err() {
            VendorError::InvalidFormat(msg) => {
                assert!(msg.contains("Danfoss does not provide BPHX"));
            }
            other => panic!("Expected InvalidFormat, got: {:?}", other),
        }
    }

    #[test]
    fn test_danfoss_object_safety() {
        let backend: Box<dyn VendorBackend> = Box::new(DanfossBackend::new().unwrap());
        assert_eq!(backend.vendor_name(), "Danfoss");
        let models = backend.list_compressor_models().unwrap();
        assert!(!models.is_empty());
    }
}
