//! FloodedCondenser - Flooded (accumulation) condenser component
//!
//! Models a heat exchanger where condensed refrigerant forms a liquid bath
//! around the cooling tubes, regulating condensing pressure. The outlet
//! is subcooled liquid (not saturated or two-phase).
//!
//! ## Difference from Standard Condenser
//!
//! - Standard Condenser: May have two-phase or saturated liquid outlet
//! - FloodedCondenser: Outlet is always subcooled liquid, liquid bath maintains stable P_cond
//!
//! ## Example
//!
//! ```ignore
//! use entropyk_components::heat_exchanger::FloodedCondenser;
//! use entropyk_core::MassFlow;
//!
//! let cond = FloodedCondenser::new(15_000.0)
//!     .with_target_subcooling(5.0);
//!
//! assert_eq!(cond.n_equations(), 3);
//! ```

use super::eps_ntu::{EpsNtuModel, ExchangerType};
use super::exchanger::{HeatExchanger, HxSideConditions};
use crate::state_machine::{CircuitId, OperationalState, StateManageable};
use crate::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Calib, MassFlow, Power, Pressure};
use entropyk_fluids::{FluidBackend, FluidId, FluidState, Property, Quality};
use std::cell::Cell;
use std::sync::Arc;

const MIN_UA: f64 = 0.0;

/// Documentation pending
pub struct FloodedCondenser {
    inner: HeatExchanger<EpsNtuModel>,
    refrigerant_id: String,
    secondary_fluid_id: String,
    fluid_backend: Option<Arc<dyn FluidBackend>>,
    target_subcooling_k: f64,
    subcooling_control_enabled: bool,
    last_heat_transfer_w: Cell<f64>,
    last_subcooling_k: Cell<Option<f64>>,
    outlet_pressure_idx: Option<usize>,
    outlet_enthalpy_idx: Option<usize>,
}

impl std::fmt::Debug for FloodedCondenser {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FloodedCondenser")
            .field("ua", &self.ua())
            .field("refrigerant_id", &self.refrigerant_id)
            .field("secondary_fluid_id", &self.secondary_fluid_id)
            .field("target_subcooling_k", &self.target_subcooling_k)
            .field(
                "subcooling_control_enabled",
                &self.subcooling_control_enabled,
            )
            .field("has_fluid_backend", &self.fluid_backend.is_some())
            .finish()
    }
}

impl FloodedCondenser {
    /// Documentation pending
    pub fn new(ua: f64) -> Self {
        assert!(ua >= MIN_UA, "UA must be non-negative, got {}", ua);
        let model = EpsNtuModel::new(ua, ExchangerType::CounterFlow);
        Self {
            inner: HeatExchanger::new(model, "FloodedCondenser"),
            refrigerant_id: String::new(),
            secondary_fluid_id: String::new(),
            fluid_backend: None,
            target_subcooling_k: 5.0,
            subcooling_control_enabled: false,
            last_heat_transfer_w: Cell::new(0.0),
            last_subcooling_k: Cell::new(None),
            outlet_pressure_idx: None,
            outlet_enthalpy_idx: None,
        }
    }

    /// Documentation pending
    pub fn try_new(ua: f64) -> Result<Self, ComponentError> {
        if ua < MIN_UA {
            return Err(ComponentError::InvalidState(format!(
                "FloodedCondenser: UA must be non-negative, got {}",
                ua
            )));
        }
        let model = EpsNtuModel::new(ua, ExchangerType::CounterFlow);
        Ok(Self {
            inner: HeatExchanger::new(model, "FloodedCondenser"),
            refrigerant_id: String::new(),
            secondary_fluid_id: String::new(),
            fluid_backend: None,
            target_subcooling_k: 5.0,
            subcooling_control_enabled: false,
            last_heat_transfer_w: Cell::new(0.0),
            last_subcooling_k: Cell::new(None),
            outlet_pressure_idx: None,
            outlet_enthalpy_idx: None,
        })
    }

    /// Documentation pending
    pub fn with_refrigerant(mut self, fluid: impl Into<String>) -> Self {
        self.refrigerant_id = fluid.into();
        self
    }

    /// Documentation pending
    pub fn with_secondary_fluid(mut self, fluid: impl Into<String>) -> Self {
        self.secondary_fluid_id = fluid.into();
        self
    }

    /// Documentation pending
    pub fn with_fluid_backend(mut self, backend: Arc<dyn FluidBackend>) -> Self {
        self.fluid_backend = Some(backend);
        self
    }

    /// Documentation pending
    pub fn with_target_subcooling(mut self, subcooling_k: f64) -> Self {
        self.target_subcooling_k = subcooling_k.max(0.0);
        self
    }

    /// Documentation pending
    pub fn with_subcooling_control(mut self, enabled: bool) -> Self {
        self.subcooling_control_enabled = enabled;
        self
    }

    /// Documentation pending
    pub fn name(&self) -> &str {
        self.inner.name()
    }

    /// Documentation pending
    pub fn ua(&self) -> f64 {
        self.inner.ua()
    }

    /// Documentation pending
    pub fn calib(&self) -> &Calib {
        self.inner.calib()
    }

    /// Documentation pending
    pub fn set_calib(&mut self, calib: Calib) {
        self.inner.set_calib(calib);
    }

    /// Documentation pending
    pub fn target_subcooling(&self) -> f64 {
        self.target_subcooling_k
    }

    /// Documentation pending
    pub fn set_target_subcooling(&mut self, subcooling_k: f64) {
        self.target_subcooling_k = subcooling_k.max(0.0);
    }

    /// Documentation pending
    pub fn heat_transfer(&self) -> f64 {
        self.last_heat_transfer_w.get()
    }

    /// Documentation pending
    pub fn subcooling(&self) -> Option<f64> {
        self.last_subcooling_k.get()
    }

    /// Documentation pending
    pub fn set_outlet_indices(&mut self, p_idx: usize, h_idx: usize) {
        self.outlet_pressure_idx = Some(p_idx);
        self.outlet_enthalpy_idx = Some(h_idx);
    }

    /// Documentation pending
    pub fn set_secondary_conditions(&mut self, conditions: HxSideConditions) {
        self.inner.set_cold_conditions(conditions);
    }

    /// Documentation pending
    pub fn set_refrigerant_conditions(&mut self, conditions: HxSideConditions) {
        self.inner.set_hot_conditions(conditions);
    }

    fn compute_subcooling(&self, h_out: f64, p_pa: f64) -> Option<f64> {
        if self.refrigerant_id.is_empty() {
            return None;
        }
        let backend = self.fluid_backend.as_ref()?;
        let fluid = FluidId::new(&self.refrigerant_id);
        let p = Pressure::from_pascals(p_pa);

        let h_sat_l = backend
            .property(
                fluid.clone(),
                Property::Enthalpy,
                FluidState::from_px(p, Quality::new(0.0)),
            )
            .ok()?;

        if h_out < h_sat_l {
            let cp_l = backend
                .property(
                    fluid,
                    Property::Cp,
                    FluidState::from_px(Pressure::from_pascals(p_pa), Quality::new(0.0)),
                )
                .unwrap_or(4180.0);
            Some((h_sat_l - h_out) / cp_l)
        } else {
            None
        }
    }

    /// Documentation pending
    pub fn validate_outlet_subcooled(&self, h_out: f64, p_pa: f64) -> Result<f64, ComponentError> {
        if self.refrigerant_id.is_empty() {
            return Err(ComponentError::InvalidState(
                "FloodedCondenser: refrigerant_id not set".to_string(),
            ));
        }
        if self.fluid_backend.is_none() {
            return Err(ComponentError::CalculationFailed(
                "FloodedCondenser: FluidBackend not configured".to_string(),
            ));
        }
        match self.compute_subcooling(h_out, p_pa) {
            Some(sc) => Ok(sc),
            None => Err(ComponentError::InvalidState("FloodedCondenser outlet is not subcooled (h_out >= h_sat_l). Use standard Condenser for two-phase outlet.".to_string())),
        }
    }
}

impl Component for FloodedCondenser {
    fn n_equations(&self) -> usize {
        let base = 3;
        if self.subcooling_control_enabled {
            base + 1
        } else {
            base
        }
    }

    fn compute_residuals(
        &self,
        state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let mut inner_residuals = vec![0.0; 3];
        self.inner.compute_residuals(state, &mut inner_residuals)?;

        residuals[0] = inner_residuals[0];
        residuals[1] = inner_residuals[1];
        residuals[2] = inner_residuals[2];

        if let Some((heat, _)) = self.inner.energy_transfers(state) {
            self.last_heat_transfer_w.set(heat.to_watts());
        }

        if self.subcooling_control_enabled && residuals.len() >= 4 {
            if let (Some(p_idx), Some(h_idx)) = (self.outlet_pressure_idx, self.outlet_enthalpy_idx)
            {
                let p_pa = *state.get(p_idx).unwrap_or(&0.0);
                let h_out = *state.get(h_idx).unwrap_or(&0.0);

                if let Some(actual_subcooling) = self.compute_subcooling(h_out, p_pa) {
                    residuals[3] = actual_subcooling - self.target_subcooling_k;
                    self.last_subcooling_k.set(Some(actual_subcooling));
                } else {
                    residuals[3] = 0.0;
                    self.last_subcooling_k.set(None);
                }
            } else {
                residuals[3] = 0.0;
            }
        }

        Ok(())
    }

    fn jacobian_entries(
        &self,
        state: &StateSlice,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        self.inner.jacobian_entries(state, jacobian)
    }

    fn get_ports(&self) -> &[ConnectedPort] {
        self.inner.get_ports()
    }

    fn set_calib_indices(&mut self, indices: entropyk_core::CalibIndices) {
        self.inner.set_calib_indices(indices);
    }

    fn port_mass_flows(&self, state: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        self.inner.port_mass_flows(state)
    }

    fn energy_transfers(&self, state: &StateSlice) -> Option<(Power, Power)> {
        self.inner.energy_transfers(state)
    }

    fn set_fluid_backend_from_builder(
        &mut self,
        backend: std::sync::Arc<dyn entropyk_fluids::FluidBackend>,
    ) {
        if self.fluid_backend.is_none() {
            self.fluid_backend = Some(backend);
        }
    }

    fn signature(&self) -> String {
        format!(
            "FloodedCondenser(UA={:.0},fluid={},target_sc={:.1}K)",
            self.ua(),
            self.refrigerant_id,
            self.target_subcooling_k
        )
    }

    fn to_params(&self) -> crate::ComponentParams {
        crate::ComponentParams::new("FloodedCondenser")
            .with_param("fluid", self.refrigerant_id.as_str())
            .with_param("ua", self.ua())
            .with_param("targetSubcoolingK", self.target_subcooling_k)
            .with_param(
                "calib",
                serde_json::to_value(self.calib()).unwrap_or(serde_json::Value::Null),
            )
    }

    fn update_calib_factor(&mut self, factor: &str, value: f64) -> bool {
        self.inner.update_calib_factor(factor, value)
    }
}

impl StateManageable for FloodedCondenser {
    fn state(&self) -> OperationalState {
        self.inner.state()
    }

    fn set_state(&mut self, state: OperationalState) -> Result<(), ComponentError> {
        self.inner.set_state(state)
    }

    fn can_transition_to(&self, target: OperationalState) -> bool {
        self.inner.can_transition_to(target)
    }

    fn circuit_id(&self) -> &CircuitId {
        self.inner.circuit_id()
    }

    fn set_circuit_id(&mut self, circuit_id: CircuitId) {
        self.inner.set_circuit_id(circuit_id);
    }
}

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

    #[test]
    fn test_flooded_condenser_creation() {
        let cond = FloodedCondenser::new(15_000.0);
        assert_eq!(cond.ua(), 15_000.0);
        assert_eq!(cond.n_equations(), 3);
        assert_eq!(cond.target_subcooling(), 5.0);
        assert_eq!(cond.subcooling(), None);
    }

    #[test]
    #[should_panic(expected = "UA must be non-negative")]
    fn test_flooded_condenser_negative_ua_panics() {
        let _cond = FloodedCondenser::new(-100.0);
    }

    #[test]
    fn test_flooded_condenser_zero_ua_allowed() {
        let cond = FloodedCondenser::new(0.0);
        assert_eq!(cond.ua(), 0.0);
    }

    #[test]
    fn test_flooded_condenser_with_subcooling_control() {
        let cond = FloodedCondenser::new(15_000.0).with_subcooling_control(true);
        assert_eq!(cond.n_equations(), 4);
    }

    #[test]
    fn test_flooded_condenser_with_target_subcooling() {
        let cond = FloodedCondenser::new(15_000.0).with_target_subcooling(8.0);
        assert_eq!(cond.target_subcooling(), 8.0);
    }

    #[test]
    fn test_flooded_condenser_clamps_subcooling() {
        let cond = FloodedCondenser::new(15_000.0).with_target_subcooling(-5.0);
        assert_eq!(cond.target_subcooling(), 0.0);
    }

    #[test]
    fn test_flooded_condenser_compute_residuals() {
        let cond = FloodedCondenser::new(15_000.0);
        let state = vec![0.0; 10];
        let mut residuals = vec![0.0; 3];

        let result = cond.compute_residuals(&state, &mut residuals);
        assert!(matches!(result, Err(ComponentError::InvalidState(_))));
    }

    #[test]
    fn test_flooded_condenser_state_manageable() {
        let cond = FloodedCondenser::new(15_000.0);
        assert_eq!(cond.state(), OperationalState::On);
        assert!(cond.can_transition_to(OperationalState::Off));
    }

    #[test]
    fn test_flooded_condenser_set_state() {
        let mut cond = FloodedCondenser::new(15_000.0);
        assert_eq!(cond.state(), OperationalState::On);

        let result = cond.set_state(OperationalState::Off);
        assert!(result.is_ok());
        assert_eq!(cond.state(), OperationalState::Off);

        let result = cond.set_state(OperationalState::Bypass);
        assert!(result.is_ok());
        assert_eq!(cond.state(), OperationalState::Bypass);
    }

    #[test]
    fn test_flooded_condenser_signature() {
        let cond = FloodedCondenser::new(15_000.0)
            .with_refrigerant("R410A")
            .with_target_subcooling(7.0);

        let sig = cond.signature();
        assert!(sig.contains("FloodedCondenser"));
        assert!(sig.contains("R410A"));
        assert!(sig.contains("7.0K"));
    }

    #[test]
    fn test_flooded_condenser_set_target_subcooling() {
        let mut cond = FloodedCondenser::new(15_000.0);
        cond.set_target_subcooling(6.5);
        assert_eq!(cond.target_subcooling(), 6.5);
    }

    #[test]
    fn test_flooded_condenser_energy_transfers() {
        let cond = FloodedCondenser::new(15_000.0);
        let state = vec![0.0; 10];
        let (heat, work) = cond.energy_transfers(&state).unwrap();
        assert_eq!(heat.to_watts(), 0.0);
        assert_eq!(work.to_watts(), 0.0);
    }

    #[test]
    fn test_flooded_condenser_with_refrigerant() {
        let cond = FloodedCondenser::new(15_000.0).with_refrigerant("R134a");
        let sig = cond.signature();
        assert!(sig.contains("R134a"));
    }

    #[test]
    fn test_flooded_condenser_with_secondary_fluid() {
        let cond = FloodedCondenser::new(15_000.0).with_secondary_fluid("Water");
        let debug_str = format!("{:?}", cond);
        assert!(debug_str.contains("Water"));
    }

    #[test]
    fn test_validate_outlet_subcooled_no_refrigerant() {
        let cond = FloodedCondenser::new(15_000.0);
        let result = cond.validate_outlet_subcooled(200_000.0, 1_000_000.0);
        assert!(result.is_err());
        match result {
            Err(ComponentError::InvalidState(msg)) => {
                assert!(msg.contains("refrigerant_id not set"));
            }
            _ => panic!("Expected InvalidState error"),
        }
    }

    #[test]
    fn test_validate_outlet_subcooled_no_backend() {
        let cond = FloodedCondenser::new(15_000.0).with_refrigerant("R134a");
        let result = cond.validate_outlet_subcooled(200_000.0, 1_000_000.0);
        assert!(result.is_err());
        match result {
            Err(ComponentError::CalculationFailed(msg)) => {
                assert!(msg.contains("FluidBackend not configured"));
            }
            _ => panic!("Expected CalculationFailed error"),
        }
    }

    #[test]
    fn test_set_outlet_indices() {
        let mut cond = FloodedCondenser::new(15_000.0).with_subcooling_control(true);
        cond.set_outlet_indices(2, 3);

        let state = vec![0.0, 0.0, 1_000_000.0, 200_000.0, 0.0, 0.0];
        let mut residuals = vec![0.0; 4];
        let result = cond.compute_residuals(&state, &mut residuals);
        assert!(matches!(result, Err(ComponentError::InvalidState(_))));
    }

    #[test]
    fn test_heat_transfer_initial_value() {
        let cond = FloodedCondenser::new(15_000.0);
        assert_eq!(cond.heat_transfer(), 0.0);
    }

    #[test]
    fn test_try_new_valid_ua() {
        let cond = FloodedCondenser::try_new(15_000.0);
        assert!(cond.is_ok());
        assert_eq!(cond.unwrap().ua(), 15_000.0);
    }

    #[test]
    fn test_try_new_invalid_ua() {
        let result = FloodedCondenser::try_new(-100.0);
        assert!(result.is_err());
        match result {
            Err(ComponentError::InvalidState(msg)) => {
                assert!(msg.contains("UA must be non-negative"));
            }
            _ => panic!("Expected InvalidState error"),
        }
    }

    #[test]
    fn test_flooded_condenser_without_subcooling_control() {
        let cond = FloodedCondenser::new(15_000.0).with_subcooling_control(false);
        assert_eq!(cond.n_equations(), 3);
    }

    #[test]
    fn test_flooded_condenser_calib_default() {
        let cond = FloodedCondenser::new(15_000.0);
        let calib = cond.calib();
        assert_eq!(calib.z_ua, 1.0);
    }

    #[test]
    fn test_flooded_condenser_set_calib() {
        let mut cond = FloodedCondenser::new(15_000.0);
        let mut calib = Calib::default();
        calib.z_ua = 0.9;
        cond.set_calib(calib);
        assert_eq!(cond.calib().z_ua, 0.9);
    }

    #[test]
    fn test_subcooling_calculation_with_mock_backend() {
        use entropyk_core::Enthalpy;
        use entropyk_fluids::{CriticalPoint, FluidError, FluidResult, Phase, ThermoState};

        struct MockBackend;

        impl FluidBackend for MockBackend {
            fn property(
                &self,
                _fluid: FluidId,
                property: Property,
                _state: FluidState,
            ) -> FluidResult<f64> {
                match property {
                    Property::Enthalpy => Ok(250_000.0),
                    Property::Cp => Ok(4180.0),
                    _ => Err(FluidError::UnsupportedProperty {
                        property: format!("{:?}", property),
                    }),
                }
            }

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

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

            fn phase(&self, _fluid: FluidId, _state: FluidState) -> FluidResult<Phase> {
                Ok(Phase::Unknown)
            }

            fn full_state(
                &self,
                _fluid: FluidId,
                _p: Pressure,
                _h: Enthalpy,
            ) -> FluidResult<ThermoState> {
                Err(FluidError::UnsupportedProperty {
                    property: "full_state".to_string(),
                })
            }

            fn list_fluids(&self) -> Vec<FluidId> {
                vec![]
            }
        }

        let backend: Arc<dyn FluidBackend> = Arc::new(MockBackend);
        let cond = FloodedCondenser::new(15_000.0)
            .with_refrigerant("R134a")
            .with_fluid_backend(backend);

        let h_out = 200_000.0;
        let p_pa = 1_000_000.0;

        let subcooling = cond.compute_subcooling(h_out, p_pa);
        assert!(subcooling.is_some());
        let sc = subcooling.unwrap();
        let expected = (250_000.0 - 200_000.0) / 4180.0;
        assert!((sc - expected).abs() < 0.01);
    }

    #[test]
    fn test_validate_outlet_subcooled_with_mock_backend() {
        use entropyk_core::Enthalpy;
        use entropyk_fluids::{CriticalPoint, FluidError, FluidResult, Phase, ThermoState};

        struct MockBackend;

        impl FluidBackend for MockBackend {
            fn property(
                &self,
                _fluid: FluidId,
                property: Property,
                _state: FluidState,
            ) -> FluidResult<f64> {
                match property {
                    Property::Enthalpy => Ok(250_000.0),
                    Property::Cp => Ok(4180.0),
                    _ => Err(FluidError::UnsupportedProperty {
                        property: format!("{:?}", property),
                    }),
                }
            }

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

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

            fn phase(&self, _fluid: FluidId, _state: FluidState) -> FluidResult<Phase> {
                Ok(Phase::Unknown)
            }

            fn full_state(
                &self,
                _fluid: FluidId,
                _p: Pressure,
                _h: Enthalpy,
            ) -> FluidResult<ThermoState> {
                Err(FluidError::UnsupportedProperty {
                    property: "full_state".to_string(),
                })
            }

            fn list_fluids(&self) -> Vec<FluidId> {
                vec![]
            }
        }

        let backend: Arc<dyn FluidBackend> = Arc::new(MockBackend);
        let cond = FloodedCondenser::new(15_000.0)
            .with_refrigerant("R134a")
            .with_fluid_backend(backend);

        let h_out = 200_000.0;
        let p_pa = 1_000_000.0;

        let result = cond.validate_outlet_subcooled(h_out, p_pa);
        assert!(result.is_ok());
        let sc = result.unwrap();
        let expected = (250_000.0 - 200_000.0) / 4180.0;
        assert!((sc - expected).abs() < 0.01);
    }
}
