//! BypassValve component for hydronic system regulation
//!
//! This component models a bypass valve that allows mixing of chilled water
//! from free cooling with return water to achieve the desired temperature.

use serde::{Deserialize, Serialize};

use crate::{Component, ComponentError, OperationalState};

/// Valve characteristics
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ValveCharacteristics {
    /// Linear: opening ∝ position
    Linear,
    /// Equal percentage: each % opening gives same % flow variation
    EqualPercentage,
    /// Quick opening: large variation at beginning
    QuickOpening,
}

/// Configuration for the bypass valve
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BypassValveConfig {
    /// Flow coefficient Cv (US gallons/min at 1 psi pressure drop)
    pub cv: f64,
    /// Valve characteristics
    pub characteristics: ValveCharacteristics,
    /// Minimum position (0.0-1.0)
    pub min_position: f64,
    /// Maximum position (0.0-1.0)
    pub max_position: f64,
    /// Nominal pressure drop (Pa)
    pub nominal_pressure_drop_pa: f64,
}

/// Bypass valve component
#[derive(Debug)]
pub struct BypassValve {
    /// Identifier
    id: String,
    /// Configuration
    config: BypassValveConfig,
    /// Current position (0.0-1.0)
    position: f64,
    /// Control mode
    control_mode: BypassValveControlMode,
    /// Setpoint (based on mode)
    setpoint: f64,
    /// Operational state
    operational_state: OperationalState,
    /// When true, the valve participates in the (P,h) graph solver as a
    /// 2-port throttling element on its outgoing edge.
    edge_coupled: bool,
    /// Global state index of the inlet edge pressure (set by the solver).
    inlet_p_idx: Option<usize>,
    /// Global state index of the inlet edge enthalpy (set by the solver).
    inlet_h_idx: Option<usize>,
    /// Global state index of the outlet edge pressure (set by the solver).
    outlet_p_idx: Option<usize>,
    /// Global state index of the outlet edge enthalpy (set by the solver).
    outlet_h_idx: Option<usize>,
}

/// Bypass valve control mode
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum BypassValveControlMode {
    /// Fixed position
    Manual,
    /// Temperature control
    TemperatureControl,
    /// Differential pressure control
    PressureControl,
    /// Optimized control
    Optimized,
}

impl BypassValve {
    /// Creates a new bypass valve
    pub fn new(id: &str, config: BypassValveConfig) -> Self {
        Self {
            id: id.to_string(),
            config,
            position: 0.0, // Closed by default
            control_mode: BypassValveControlMode::Manual,
            setpoint: 0.0,
            operational_state: OperationalState::Off,
            edge_coupled: false,
            inlet_p_idx: None,
            inlet_h_idx: None,
            outlet_p_idx: None,
            outlet_h_idx: None,
        }
    }

    /// Enables the edge-coupled (P,h) solver model, so the valve participates in
    /// the refrigeration/hydronic graph as a 2-port throttling element.
    ///
    /// The imposed pressure drop scales with the valve opening:
    /// `ΔP = nominal_pressure_drop_pa * (cv_full_open / effective_cv)²`, and the
    /// process is adiabatic (`h_out = h_in`).
    pub fn with_edge_coupling(mut self) -> Self {
        self.edge_coupled = true;
        if self.operational_state == OperationalState::Off {
            self.operational_state = OperationalState::On;
        }
        self
    }

    /// Returns the imposed pressure drop (Pa) for the current opening.
    fn imposed_pressure_drop_pa(&self) -> f64 {
        let cv_full = self.config.cv.max(1e-9);
        // effective_cv falls to ~0 when closed; clamp to keep ΔP finite.
        let cv_eff = self.effective_cv().max(cv_full * 1e-2);
        let ratio = cv_full / cv_eff;
        self.config.nominal_pressure_drop_pa * ratio * ratio
    }

    /// Sets the valve position
    pub fn set_position(&mut self, position: f64) -> Result<(), ComponentError> {
        let clamped = position.clamp(self.config.min_position, self.config.max_position);
        self.position = clamped;
        Ok(())
    }

    /// Calculates flow through the valve
    pub fn calculate_flow(&self, pressure_drop_pa: f64) -> f64 {
        // Valve equation: Q = Cv * sqrt(ΔP / SG)
        // SG (specific gravity) = 1 for water
        let effective_cv = self.effective_cv();
        effective_cv * (pressure_drop_pa / 1000.0).sqrt() // Approximate conversion
    }

    /// Returns current position
    pub fn position(&self) -> f64 {
        self.position
    }

    /// Returns effective Cv based on position
    pub fn effective_cv(&self) -> f64 {
        match self.config.characteristics {
            ValveCharacteristics::Linear => self.config.cv * self.position,
            ValveCharacteristics::EqualPercentage => {
                // R = capacity ratio (typically 50)
                let r: f64 = 50.0;
                self.config.cv * r.powf(self.position - 1.0)
            }
            ValveCharacteristics::QuickOpening => {
                self.config.cv * (2.0 * self.position - self.position.powi(2)).sqrt()
            }
        }
    }

    /// Returns the unique identifier
    pub fn id(&self) -> &str {
        &self.id
    }

    /// Returns the control mode
    pub fn control_mode(&self) -> BypassValveControlMode {
        self.control_mode
    }

    /// Sets the control mode
    pub fn set_control_mode(&mut self, mode: BypassValveControlMode) {
        self.control_mode = mode;
    }

    /// Returns the setpoint
    pub fn setpoint(&self) -> f64 {
        self.setpoint
    }

    /// Sets the setpoint
    pub fn set_setpoint(&mut self, setpoint: f64) {
        self.setpoint = setpoint;
    }

    /// Updates the valve position based on control mode and process variables
    pub fn update_control(
        &mut self,
        current_temperature: Option<f64>,
        current_pressure_drop: Option<f64>,
    ) -> Result<(), ComponentError> {
        match self.control_mode {
            BypassValveControlMode::Manual => {
                // Do nothing, position is fixed
            }
            BypassValveControlMode::TemperatureControl => {
                if let Some(temp) = current_temperature {
                    // Simple P controller: position = Kp * (setpoint - current)
                    let error = self.setpoint - temp;
                    let kp = 0.1; // Proportional gain
                    let new_position = self.position + kp * error;
                    self.set_position(new_position)?;
                }
            }
            BypassValveControlMode::PressureControl => {
                if let Some(pressure_drop) = current_pressure_drop {
                    // Maintain constant pressure drop
                    let error = self.setpoint - pressure_drop;
                    let kp = 0.01; // Proportional gain
                    let new_position = self.position + kp * error;
                    self.set_position(new_position)?;
                }
            }
            BypassValveControlMode::Optimized => {
                // TODO: Implement optimization logic
                // For now, use temperature control
                if let Some(temp) = current_temperature {
                    let error = self.setpoint - temp;
                    let kp = 0.1;
                    let new_position = self.position + kp * error;
                    self.set_position(new_position)?;
                }
            }
        }
        Ok(())
    }
}

impl Component for BypassValve {
    fn n_equations(&self) -> usize {
        2 // Mass balance + energy balance
    }

    fn set_system_context(
        &mut self,
        _state_offset: usize,
        external_edge_state_indices: &[(usize, usize, usize)],
    ) {
        // Layout: [0] = incoming edge, [1] = outgoing edge.
        // Triple: (m_idx, p_idx, h_idx)
        if !external_edge_state_indices.is_empty() {
            self.inlet_p_idx = Some(external_edge_state_indices[0].1);
            self.inlet_h_idx = Some(external_edge_state_indices[0].2);
        }
        if external_edge_state_indices.len() >= 2 {
            self.outlet_p_idx = Some(external_edge_state_indices[1].1);
            self.outlet_h_idx = Some(external_edge_state_indices[1].2);
        }
    }

    fn compute_residuals(
        &self,
        state: &[f64],
        residuals: &mut crate::ResidualVector,
    ) -> Result<(), ComponentError> {
        if self.edge_coupled {
            if let (Some(in_p), Some(in_h), Some(out_p), Some(out_h)) = (
                self.inlet_p_idx,
                self.inlet_h_idx,
                self.outlet_p_idx,
                self.outlet_h_idx,
            ) {
                if residuals.len() < 2 {
                    return Err(ComponentError::InvalidResidualDimensions {
                        expected: 2,
                        actual: residuals.len(),
                    });
                }
                let dp = self.imposed_pressure_drop_pa();
                residuals[0] = state[out_p] - (state[in_p] - dp);
                residuals[1] = state[out_h] - state[in_h];
                return Ok(());
            }
            return Err(ComponentError::InvalidState(
                "BypassValve edge-coupled model requires live inlet/outlet edge state indices"
                    .to_string(),
            ));
        }
        Ok(())
    }

    fn jacobian_entries(
        &self,
        _state: &[f64],
        jacobian: &mut crate::JacobianBuilder,
    ) -> Result<(), ComponentError> {
        if self.edge_coupled {
            if let (Some(in_p), Some(in_h), Some(out_p), Some(out_h)) = (
                self.inlet_p_idx,
                self.inlet_h_idx,
                self.outlet_p_idx,
                self.outlet_h_idx,
            ) {
                jacobian.add_entry(0, out_p, 1.0);
                jacobian.add_entry(0, in_p, -1.0);
                jacobian.add_entry(1, out_h, 1.0);
                jacobian.add_entry(1, in_h, -1.0);
            }
        }
        Ok(())
    }

    fn get_ports(&self) -> &[crate::ConnectedPort] {
        &[] // Placeholder
    }

    fn signature(&self) -> String {
        format!("BypassValve(id={})", self.id)
    }

    fn to_params(&self) -> crate::ComponentParams {
        crate::ComponentParams::new("BypassValve")
            .with_param("id", self.id.as_str())
            .with_param("position", self.position)
            .with_param(
                "config",
                serde_json::to_value(&self.config).unwrap_or(serde_json::Value::Null),
            )
            .with_param("controlMode", format!("{:?}", self.control_mode))
            .with_param("setpoint", self.setpoint)
    }
}

impl BypassValve {
    /// Returns the operational state
    pub fn operational_state(&self) -> OperationalState {
        self.operational_state
    }

    /// Sets the operational state
    pub fn set_operational_state(&mut self, state: OperationalState) -> Result<(), ComponentError> {
        self.operational_state = state;
        if state == OperationalState::Off {
            self.position = 0.0; // Close valve when off
        }
        Ok(())
    }
}

impl Default for BypassValveConfig {
    fn default() -> Self {
        Self {
            cv: 10.0, // Typical Cv for bypass valve
            characteristics: ValveCharacteristics::EqualPercentage,
            min_position: 0.0,
            max_position: 1.0,
            nominal_pressure_drop_pa: 10000.0, // 10 kPa
        }
    }
}

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

    #[test]
    fn test_bypass_valve_creation() {
        let config = BypassValveConfig::default();
        let valve = BypassValve::new("bv1", config);
        assert_eq!(valve.position(), 0.0);
        assert_eq!(valve.operational_state(), OperationalState::Off);
    }

    #[test]
    fn test_position_limits() {
        let config = BypassValveConfig {
            min_position: 0.1,
            max_position: 0.9,
            ..Default::default()
        };

        let mut valve = BypassValve::new("bv1", config);

        // Test below minimum
        valve.set_position(-0.1).unwrap();
        assert_eq!(valve.position(), 0.1);

        // Test above maximum
        valve.set_position(1.5).unwrap();
        assert_eq!(valve.position(), 0.9);

        // Test within range
        valve.set_position(0.5).unwrap();
        assert_eq!(valve.position(), 0.5);
    }

    #[test]
    fn test_linear_characteristics() {
        let config = BypassValveConfig {
            cv: 10.0,
            characteristics: ValveCharacteristics::Linear,
            ..Default::default()
        };

        let mut valve = BypassValve::new("bv1", config);

        valve.set_position(0.0).unwrap();
        assert_eq!(valve.effective_cv(), 0.0);

        valve.set_position(0.5).unwrap();
        assert_eq!(valve.effective_cv(), 5.0);

        valve.set_position(1.0).unwrap();
        assert_eq!(valve.effective_cv(), 10.0);
    }

    #[test]
    fn test_equal_percentage_characteristics() {
        let config = BypassValveConfig {
            cv: 10.0,
            characteristics: ValveCharacteristics::EqualPercentage,
            ..Default::default()
        };

        let mut valve = BypassValve::new("bv1", config);

        valve.set_position(0.0).unwrap();
        let cv_at_0 = valve.effective_cv();
        assert!(cv_at_0 > 0.0 && cv_at_0 < 1.0);

        valve.set_position(0.5).unwrap();
        let cv_at_50 = valve.effective_cv();
        assert!(cv_at_50 > cv_at_0 && cv_at_50 < 10.0);

        valve.set_position(1.0).unwrap();
        assert_eq!(valve.effective_cv(), 10.0);
    }

    #[test]
    fn test_flow_calculation() {
        let config = BypassValveConfig {
            cv: 10.0,
            characteristics: ValveCharacteristics::Linear,
            ..Default::default()
        };

        let mut valve = BypassValve::new("bv1", config);
        valve.set_position(0.5).unwrap();

        // At 50% position, Cv = 5
        let flow = valve.calculate_flow(10000.0); // 10 kPa
        assert!(flow > 0.0);
    }

    #[test]
    fn test_temperature_control() {
        let config = BypassValveConfig::default();
        let mut valve = BypassValve::new("bv1", config);
        valve.set_control_mode(BypassValveControlMode::TemperatureControl);
        valve.set_setpoint(12.0); // Target temperature

        // Initial position
        assert_eq!(valve.position(), 0.0);

        // Simulate temperature below setpoint
        valve.update_control(Some(10.0), None).unwrap();
        assert!(valve.position() > 0.0); // Should open

        // Simulate temperature above setpoint
        valve.update_control(Some(14.0), None).unwrap();
        assert!(valve.position() < 0.5); // Should close more
    }
}
