//! Log Mean Temperature Difference (LMTD) Model
//!
//! Implements the LMTD method for heat exchanger calculations.
//!
//! ## Theory
//!
//! The heat transfer rate is calculated as:
//!
//! $$\dot{Q} = U \cdot A \cdot \Delta T_{lm} \cdot F$$
//!
//! Where:
//! - $\dot{Q}$: Heat transfer rate (W)
//! - $U$: Overall heat transfer coefficient (W/m²·K)
//! - $A$: Heat transfer area (m²)
//! - $\Delta T_{lm}$: Log mean temperature difference (K)
//! - $F$: Correction factor for flow configuration
//!
//! For counter-flow:
//! $$\Delta T_{lm} = \frac{\Delta T_1 - \Delta T_2}{\ln(\Delta T_1 / \Delta T_2)}$$
//!
//! Where:
//! - $\Delta T_1 = T_{hot,in} - T_{cold,out}$
//! - $\Delta T_2 = T_{hot,out} - T_{cold,in}$

use super::model::{FluidState, HeatTransferModel};
use crate::ResidualVector;
use entropyk_core::Power;

/// Flow configuration for the heat exchanger.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum FlowConfiguration {
    /// Counter-flow (most efficient)
    #[default]
    CounterFlow,
    /// Parallel-flow (co-current)
    ParallelFlow,
    /// Cross-flow with correction factor
    CrossFlow {
        /// Correction factor F (typically 0.8-1.0)
        correction_factor: f64,
    },
    /// Shell-and-tube with 1 shell pass, 2 tube passes
    ShellAndTube1_2,
}

impl FlowConfiguration {
    /// Returns the correction factor F for this configuration.
    pub fn correction_factor(&self) -> f64 {
        match self {
            FlowConfiguration::CounterFlow => 1.0,
            FlowConfiguration::ParallelFlow => 1.0,
            FlowConfiguration::CrossFlow { correction_factor } => *correction_factor,
            FlowConfiguration::ShellAndTube1_2 => 0.9,
        }
    }
}

/// LMTD (Log Mean Temperature Difference) heat transfer model.
///
/// Uses the classical LMTD method for heat exchanger sizing and rating.
///
/// # Example
///
/// ```
/// use entropyk_components::heat_exchanger::{LmtdModel, FlowConfiguration, HeatTransferModel};
/// use entropyk_components::heat_exchanger::model::FluidState;
///
/// let model = LmtdModel::new(5000.0, FlowConfiguration::CounterFlow);
/// assert_eq!(model.ua(), 5000.0);
/// ```
#[derive(Debug, Clone)]
pub struct LmtdModel {
    /// Overall heat transfer coefficient × Area (W/K), nominal
    ua: f64,
    /// UA calibration scale: UA_eff = ua_scale × ua (default 1.0)
    ua_scale: f64,
    /// Flow configuration
    flow_config: FlowConfiguration,
}

impl LmtdModel {
    /// Creates a new LMTD model.
    ///
    /// # Arguments
    ///
    /// * `ua` - Overall heat transfer coefficient × Area (W/K). Must be positive.
    /// * `flow_config` - Flow configuration (counter-flow, parallel-flow, etc.)
    ///
    /// # Panics
    ///
    /// Panics if `ua` is negative or NaN.
    ///
    /// # Example
    ///
    /// ```
    /// use entropyk_components::heat_exchanger::{LmtdModel, FlowConfiguration};
    ///
    /// let model = LmtdModel::new(10000.0, FlowConfiguration::CounterFlow);
    /// ```
    pub fn new(ua: f64, flow_config: FlowConfiguration) -> Self {
        assert!(
            ua.is_finite() && ua >= 0.0,
            "UA must be non-negative and finite, got {}",
            ua
        );
        Self {
            ua,
            ua_scale: 1.0,
            flow_config,
        }
    }

    /// Creates a counter-flow LMTD model.
    pub fn counter_flow(ua: f64) -> Self {
        Self::new(ua, FlowConfiguration::CounterFlow)
    }

    /// Creates a parallel-flow LMTD model.
    pub fn parallel_flow(ua: f64) -> Self {
        Self::new(ua, FlowConfiguration::ParallelFlow)
    }

    /// Creates a cross-flow LMTD model with correction factor.
    pub fn cross_flow(ua: f64, correction_factor: f64) -> Self {
        Self::new(ua, FlowConfiguration::CrossFlow { correction_factor })
    }

    /// Calculates the Log Mean Temperature Difference.
    ///
    /// For counter-flow:
    /// - ΔT₁ = T_hot,in - T_cold,out
    /// - ΔT₂ = T_hot,out - T_cold,in
    ///
    /// For parallel-flow:
    /// - ΔT₁ = T_hot,in - T_cold,in
    /// - ΔT₂ = T_hot,out - T_cold,out
    ///
    /// Special handling when ΔT₁ ≈ ΔT₂: uses arithmetic mean.
    pub fn lmtd(&self, t_hot_in: f64, t_hot_out: f64, t_cold_in: f64, t_cold_out: f64) -> f64 {
        let (dt1, dt2) = match self.flow_config {
            FlowConfiguration::CounterFlow
            | FlowConfiguration::CrossFlow { .. }
            | FlowConfiguration::ShellAndTube1_2 => (t_hot_in - t_cold_out, t_hot_out - t_cold_in),
            FlowConfiguration::ParallelFlow => (t_hot_in - t_cold_in, t_hot_out - t_cold_out),
        };

        // Zero-flow / zero LMTD regularization: avoid division by zero (Story 3.5)
        if dt1.abs() < 1e-10 && dt2.abs() < 1e-10 {
            return 0.0;
        }

        if (dt1 - dt2).abs() / dt1.max(dt2).max(1e-10) < 1e-6 {
            return (dt1 + dt2) / 2.0;
        }

        if dt1 <= 0.0 || dt2 <= 0.0 {
            return (dt1 + dt2) / 2.0;
        }

        (dt1 - dt2) / (dt1 / dt2).ln()
    }
}

impl HeatTransferModel for LmtdModel {
    fn compute_heat_transfer(
        &self,
        hot_inlet: &FluidState,
        hot_outlet: &FluidState,
        cold_inlet: &FluidState,
        cold_outlet: &FluidState,
        dynamic_ua_scale: Option<f64>,
    ) -> Power {
        let lmtd = self.lmtd(
            hot_inlet.temperature,
            hot_outlet.temperature,
            cold_inlet.temperature,
            cold_outlet.temperature,
        );

        let f = self.flow_config.correction_factor();
        let ua_eff = self.effective_ua(dynamic_ua_scale);
        let q = ua_eff * lmtd * f;

        Power::from_watts(q)
    }

    fn compute_residuals(
        &self,
        hot_inlet: &FluidState,
        hot_outlet: &FluidState,
        cold_inlet: &FluidState,
        cold_outlet: &FluidState,
        residuals: &mut ResidualVector,
        dynamic_ua_scale: Option<f64>,
    ) {
        let q = self
            .compute_heat_transfer(
                hot_inlet,
                hot_outlet,
                cold_inlet,
                cold_outlet,
                dynamic_ua_scale,
            )
            .to_watts();

        let q_hot = hot_inlet.mass_flow * (hot_inlet.enthalpy - hot_outlet.enthalpy);
        let q_cold = cold_inlet.mass_flow * (cold_outlet.enthalpy - cold_inlet.enthalpy);

        residuals[0] = q_hot - q;
        residuals[1] = q_cold - q;
    }

    fn n_equations(&self) -> usize {
        2
    }

    fn ua(&self) -> f64 {
        self.ua
    }

    fn ua_scale(&self) -> f64 {
        self.ua_scale
    }

    fn set_ua_scale(&mut self, s: f64) {
        self.ua_scale = s;
    }

    fn effective_ua(&self, dynamic_ua_scale: Option<f64>) -> f64 {
        self.ua * dynamic_ua_scale.unwrap_or(self.ua_scale)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::heat_exchanger::{EpsNtuModel, HeatTransferModel};
    use approx::assert_relative_eq;

    #[test]
    fn test_lmtd_model_creation() {
        let model = LmtdModel::new(5000.0, FlowConfiguration::CounterFlow);
        assert_eq!(model.ua(), 5000.0);
    }

    #[test]
    fn test_f_ua_scales_heat_transfer() {
        let mut model = LmtdModel::new(5000.0, FlowConfiguration::CounterFlow);
        assert_relative_eq!(model.effective_ua(None), 5000.0, epsilon = 1e-10);
        model.set_ua_scale(1.1);
        assert_relative_eq!(model.effective_ua(None), 5500.0, epsilon = 1e-10);
    }

    #[test]
    fn test_lmtd_counter_flow() {
        let model = LmtdModel::counter_flow(5000.0);

        let t_hot_in = 80.0;
        let t_hot_out = 60.0;
        let t_cold_in = 20.0;
        let t_cold_out = 50.0;

        let lmtd = model.lmtd(t_hot_in, t_hot_out, t_cold_in, t_cold_out);

        assert!(lmtd > 0.0);
        assert!(lmtd < (t_hot_in - t_cold_in));
    }

    #[test]
    fn test_lmtd_equal_deltas() {
        let model = LmtdModel::counter_flow(5000.0);

        let t_hot_in = 80.0;
        let t_hot_out = 60.0;
        let t_cold_in = 40.0;
        let t_cold_out = 60.0;

        let lmtd = model.lmtd(t_hot_in, t_hot_out, t_cold_in, t_cold_out);

        assert!((lmtd - 20.0).abs() < 0.1);
    }

    #[test]
    fn test_lmtd_parallel_flow() {
        let model = LmtdModel::parallel_flow(5000.0);

        let t_hot_in = 80.0;
        let t_hot_out = 60.0;
        let t_cold_in = 20.0;
        let t_cold_out = 40.0;

        let lmtd = model.lmtd(t_hot_in, t_hot_out, t_cold_in, t_cold_out);

        assert!(lmtd > 0.0);
    }

    #[test]
    fn test_compute_heat_transfer() {
        let model = LmtdModel::counter_flow(5000.0);

        let hot_inlet = FluidState::from_temperature(80.0 + 273.15);
        let hot_outlet = FluidState::from_temperature(60.0 + 273.15);
        let cold_inlet = FluidState::from_temperature(20.0 + 273.15);
        let cold_outlet = FluidState::from_temperature(50.0 + 273.15);

        let q =
            model.compute_heat_transfer(&hot_inlet, &hot_outlet, &cold_inlet, &cold_outlet, None);

        assert!(q.to_watts() > 0.0);
    }

    #[test]
    fn test_flow_configuration_correction_factor() {
        assert_eq!(FlowConfiguration::CounterFlow.correction_factor(), 1.0);
        assert_eq!(FlowConfiguration::ParallelFlow.correction_factor(), 1.0);
        assert_eq!(FlowConfiguration::ShellAndTube1_2.correction_factor(), 0.9);

        let cross = FlowConfiguration::CrossFlow {
            correction_factor: 0.85,
        };
        assert_eq!(cross.correction_factor(), 0.85);
    }

    #[test]
    fn test_n_equations() {
        let model = LmtdModel::counter_flow(1000.0);
        assert_eq!(model.n_equations(), 2);
    }

    #[test]
    fn test_lmtd_negative_deltas() {
        let model = LmtdModel::counter_flow(5000.0);

        let t_hot_in = 40.0;
        let t_hot_out = 50.0;
        let t_cold_in = 60.0;
        let t_cold_out = 70.0;

        let lmtd = model.lmtd(t_hot_in, t_hot_out, t_cold_in, t_cold_out);

        assert!(lmtd < 0.0);
    }

    #[test]
    #[should_panic(expected = "UA must be non-negative")]
    fn test_negative_ua_panics() {
        let _model = LmtdModel::new(-1000.0, FlowConfiguration::CounterFlow);
    }

    #[test]
    fn test_zero_ua_allowed() {
        let model = LmtdModel::new(0.0, FlowConfiguration::CounterFlow);
        assert_eq!(model.ua(), 0.0);
    }

    #[test]
    fn test_lmtd_vs_eps_ntu_comparison() {
        // AC #8: Compare LMTD vs ε-NTU results for same conditions
        // Verify both methods produce reasonable heat transfer values

        let ua = 5_000.0;
        let lmtd_model = LmtdModel::counter_flow(ua);
        let eps_ntu_model = EpsNtuModel::counter_flow(ua);

        // Typical HVAC operating conditions
        // Hot water cooling from 80°C to 60°C (353K to 333K)
        // Cold water heating from 20°C to 40°C (293K to 313K)
        let hot_inlet = FluidState::new(353.0, 200_000.0, 335_000.0, 0.5, 4180.0);
        let hot_outlet = FluidState::new(333.0, 195_000.0, 250_000.0, 0.5, 4180.0);
        let cold_inlet = FluidState::new(293.0, 101_325.0, 85_000.0, 0.3, 4180.0);
        let cold_outlet = FluidState::new(313.0, 101_325.0, 170_000.0, 0.3, 4180.0);

        let q_lmtd = lmtd_model
            .compute_heat_transfer(&hot_inlet, &hot_outlet, &cold_inlet, &cold_outlet, None)
            .to_watts();
        let q_eps_ntu = eps_ntu_model
            .compute_heat_transfer(&hot_inlet, &hot_outlet, &cold_inlet, &cold_outlet, None)
            .to_watts();

        // Both methods should give positive heat transfer
        assert!(q_lmtd > 0.0, "LMTD should give positive Q, got {}", q_lmtd);
        assert!(
            q_eps_ntu > 0.0,
            "ε-NTU should give positive Q, got {}",
            q_eps_ntu
        );

        // Verify reasonable magnitude for a 5 kW/K heat exchanger
        // LMTD should be around 30-40K, so Q should be 150-200 kW range for these temps
        assert!(
            q_lmtd > 100_000.0 && q_lmtd < 300_000.0,
            "LMTD Q unexpected: {}",
            q_lmtd
        );

        // ε-NTU uses inlet temps only, so result differs from LMTD
        assert!(
            q_eps_ntu < 300_000.0,
            "ε-NTU Q should be reasonable, got {}",
            q_eps_ntu
        );
    }
}
