//! Fan Component Implementation
//!
//! This module provides a fan component for air handling systems using
//! polynomial performance curves and affinity laws for variable speed operation.
//!
//! ## Performance Curves
//!
//! **Static Pressure Curve:** P_s = a₀ + a₁Q + a₂Q² + a₃Q³
//!
//! **Efficiency Curve:** η = b₀ + b₁Q + b₂Q²
//!
//! **Fan Power:** P_fan = Q × P_s / η
//!
//! ## Affinity Laws (Variable Speed)
//!
//! When operating at reduced speed (VFD):
//! - Q₂/Q₁ = N₂/N₁
//! - P₂/P₁ = (N₂/N₁)²
//! - Pwr₂/Pwr₁ = (N₂/N₁)³

use crate::polynomials::{AffinityLaws, PerformanceCurves, Polynomial1D};
use crate::port::{Connected, Disconnected, FluidId, Port};
use crate::state_machine::StateManageable;
use crate::{
    CircuitId, Component, ComponentError, ConnectedPort, JacobianBuilder, OperationalState,
    ResidualVector, SystemState,
};
use entropyk_core::{MassFlow, Power};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;

/// Fan performance curve coefficients.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FanCurves {
    /// Performance curves (static pressure, efficiency)
    curves: PerformanceCurves,
}

impl FanCurves {
    /// Creates fan curves from performance curves.
    pub fn new(curves: PerformanceCurves) -> Result<Self, ComponentError> {
        curves.validate()?;
        Ok(Self { curves })
    }

    /// Creates fan curves from polynomial coefficients.
    ///
    /// # Arguments
    ///
    /// * `pressure_coeffs` - Static pressure curve [a0, a1, a2, ...] in Pa
    /// * `eff_coeffs` - Efficiency coefficients [b0, b1, b2, ...] as decimal
    ///
    /// # Units
    ///
    /// * Q (flow) in m³/s
    /// * P_s (static pressure) in Pascals
    /// * η (efficiency) as decimal (0.0 to 1.0)
    pub fn from_coefficients(
        pressure_coeffs: Vec<f64>,
        eff_coeffs: Vec<f64>,
    ) -> Result<Self, ComponentError> {
        let pressure_curve = Polynomial1D::new(pressure_coeffs);
        let eff_curve = Polynomial1D::new(eff_coeffs);
        let curves = PerformanceCurves::simple(pressure_curve, eff_curve);
        Self::new(curves)
    }

    /// Creates a quadratic fan curve.
    pub fn quadratic(
        p0: f64,
        p1: f64,
        p2: f64,
        e0: f64,
        e1: f64,
        e2: f64,
    ) -> Result<Self, ComponentError> {
        Self::from_coefficients(vec![p0, p1, p2], vec![e0, e1, e2])
    }

    /// Creates a cubic fan curve (common for fans).
    pub fn cubic(
        p0: f64,
        p1: f64,
        p2: f64,
        p3: f64,
        e0: f64,
        e1: f64,
        e2: f64,
    ) -> Result<Self, ComponentError> {
        Self::from_coefficients(vec![p0, p1, p2, p3], vec![e0, e1, e2])
    }

    /// Returns static pressure at given flow rate (full speed).
    pub fn static_pressure_at_flow(&self, flow_m3_per_s: f64) -> f64 {
        self.curves.head_curve.evaluate(flow_m3_per_s)
    }

    /// Returns efficiency at given flow rate (full speed).
    pub fn efficiency_at_flow(&self, flow_m3_per_s: f64) -> f64 {
        let eta = self.curves.efficiency_curve.evaluate(flow_m3_per_s);
        eta.clamp(0.0, 1.0)
    }

    /// Returns reference to performance curves.
    pub fn curves(&self) -> &PerformanceCurves {
        &self.curves
    }
}

impl Default for FanCurves {
    fn default() -> Self {
        Self::quadratic(500.0, 0.0, 0.0, 0.7, 0.0, 0.0).unwrap()
    }
}

/// Standard air properties at sea level (for reference).
pub mod standard_air {
    /// Standard air density at 20°C, 101325 Pa (kg/m³)
    pub const DENSITY: f64 = 1.204;
    /// Standard air specific heat at constant pressure (J/(kg·K))
    pub const CP: f64 = 1005.0;
}

/// A fan component with polynomial performance curves.
///
/// Fans differ from pumps in that:
/// - They work with compressible fluids (air)
/// - Static pressure is typically much lower
/// - Common to use cubic curves for pressure
///
/// # Example
///
/// ```ignore
/// use entropyk_components::fan::{Fan, FanCurves};
/// use entropyk_components::port::{FluidId, Port};
/// use entropyk_core::{Pressure, Enthalpy};
///
/// // Create fan curves: P_s = 500 - 50*Q - 10*Q² (Pa, m³/s)
/// let curves = FanCurves::quadratic(500.0, -50.0, -10.0, 0.5, 0.2, -0.1).unwrap();
///
/// let inlet = Port::new(
///     FluidId::new("Air"),
///     Pressure::from_bar(1.01325),
///     Enthalpy::from_joules_per_kg(300000.0),
/// );
/// let outlet = Port::new(
///     FluidId::new("Air"),
///     Pressure::from_bar(1.01325),
///     Enthalpy::from_joules_per_kg(300000.0),
/// );
///
/// let fan = Fan::new(curves, inlet, outlet, 1.2).unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct Fan<State> {
    /// Performance curves
    curves: FanCurves,
    /// Inlet port
    port_inlet: Port<State>,
    /// Outlet port
    port_outlet: Port<State>,
    /// Air density in kg/m³
    air_density_kg_per_m3: f64,
    /// Speed ratio (0.0 to 1.0)
    speed_ratio: f64,
    /// Circuit identifier
    circuit_id: CircuitId,
    /// Operational state
    operational_state: OperationalState,
    /// Phantom data for type state
    _state: PhantomData<State>,
}

impl Fan<Disconnected> {
    /// Creates a new disconnected fan.
    ///
    /// # Arguments
    ///
    /// * `curves` - Fan performance curves
    /// * `port_inlet` - Inlet port (disconnected)
    /// * `port_outlet` - Outlet port (disconnected)
    /// * `air_density` - Air density in kg/m³ (use 1.2 for standard conditions)
    pub fn new(
        curves: FanCurves,
        port_inlet: Port<Disconnected>,
        port_outlet: Port<Disconnected>,
        air_density: f64,
    ) -> Result<Self, ComponentError> {
        if port_inlet.fluid_id() != port_outlet.fluid_id() {
            return Err(ComponentError::InvalidState(
                "Inlet and outlet ports must have the same fluid type".to_string(),
            ));
        }

        if air_density <= 0.0 {
            return Err(ComponentError::InvalidState(
                "Air density must be positive".to_string(),
            ));
        }

        Ok(Self {
            curves,
            port_inlet,
            port_outlet,
            air_density_kg_per_m3: air_density,
            speed_ratio: 1.0,
            circuit_id: CircuitId::default(),
            operational_state: OperationalState::default(),
            _state: PhantomData,
        })
    }

    /// Returns the fluid identifier.
    pub fn fluid_id(&self) -> &FluidId {
        self.port_inlet.fluid_id()
    }

    /// Returns the air density.
    pub fn air_density(&self) -> f64 {
        self.air_density_kg_per_m3
    }

    /// Returns the speed ratio.
    pub fn speed_ratio(&self) -> f64 {
        self.speed_ratio
    }

    /// Sets the speed ratio (0.0 to 1.0).
    pub fn set_speed_ratio(&mut self, ratio: f64) -> Result<(), ComponentError> {
        if !(0.0..=1.0).contains(&ratio) {
            return Err(ComponentError::InvalidState(
                "Speed ratio must be between 0.0 and 1.0".to_string(),
            ));
        }
        self.speed_ratio = ratio;
        Ok(())
    }
}

impl Fan<Connected> {
    /// Returns the inlet port.
    pub fn port_inlet(&self) -> &Port<Connected> {
        &self.port_inlet
    }

    /// Returns the outlet port.
    pub fn port_outlet(&self) -> &Port<Connected> {
        &self.port_outlet
    }

    /// Calculates the static pressure rise across the fan.
    ///
    /// Applies affinity laws for variable speed operation.
    pub fn static_pressure_rise(&self, flow_m3_per_s: f64) -> f64 {
        // Handle zero speed - fan produces no pressure
        if self.speed_ratio <= 0.0 {
            return 0.0;
        }

        // Handle negative flow gracefully by using a linear extrapolation from Q=0 
        // to prevent polynomial extrapolation issues with quadratic/cubic terms
        if flow_m3_per_s < 0.0 {
            let p0 = self.curves.static_pressure_at_flow(0.0);
            let p_eps = self.curves.static_pressure_at_flow(1e-6);
            let dp_dq = (p_eps - p0) / 1e-6;
            
            let pressure = p0 + dp_dq * flow_m3_per_s;
            return AffinityLaws::scale_head(pressure, self.speed_ratio);
        }

        // Handle exactly zero flow
        if flow_m3_per_s == 0.0 {
            let pressure = self.curves.static_pressure_at_flow(0.0);
            return AffinityLaws::scale_head(pressure, self.speed_ratio);
        }

        let equivalent_flow = AffinityLaws::unscale_flow(flow_m3_per_s, self.speed_ratio);
        let pressure = self.curves.static_pressure_at_flow(equivalent_flow);
        AffinityLaws::scale_head(pressure, self.speed_ratio)
    }

    /// Calculates total pressure (static + velocity pressure).
    ///
    /// Total pressure = Static pressure + ½ρv²
    ///
    /// # Arguments
    ///
    /// * `flow_m3_per_s` - Volumetric flow rate
    /// * `duct_area_m2` - Duct cross-sectional area
    pub fn total_pressure_rise(&self, flow_m3_per_s: f64, duct_area_m2: f64) -> f64 {
        let static_p = self.static_pressure_rise(flow_m3_per_s);

        if duct_area_m2 <= 0.0 {
            return static_p;
        }

        // Velocity pressure: P_v = ½ρv²
        let velocity = flow_m3_per_s / duct_area_m2;
        let velocity_pressure = 0.5 * self.air_density_kg_per_m3 * velocity * velocity;

        static_p + velocity_pressure
    }

    /// Calculates efficiency at the given flow rate.
    pub fn efficiency(&self, flow_m3_per_s: f64) -> f64 {
        // Handle zero speed - fan is not running
        if self.speed_ratio <= 0.0 {
            return 0.0;
        }

        // Handle zero flow
        if flow_m3_per_s <= 0.0 {
            return self.curves.efficiency_at_flow(0.0);
        }

        let equivalent_flow = AffinityLaws::unscale_flow(flow_m3_per_s, self.speed_ratio);
        self.curves.efficiency_at_flow(equivalent_flow)
    }

    /// Calculates the fan power consumption.
    ///
    /// P_fan = Q × P_s / η
    pub fn fan_power(&self, flow_m3_per_s: f64) -> Power {
        if flow_m3_per_s <= 0.0 || self.speed_ratio <= 0.0 {
            return Power::from_watts(0.0);
        }

        let pressure = self.static_pressure_rise(flow_m3_per_s);
        let eta = self.efficiency(flow_m3_per_s);

        if eta <= 0.0 {
            return Power::from_watts(0.0);
        }

        let power_w = flow_m3_per_s * pressure / eta;
        Power::from_watts(power_w)
    }

    /// Calculates mass flow from volumetric flow.
    pub fn mass_flow_from_volumetric(&self, flow_m3_per_s: f64) -> MassFlow {
        MassFlow::from_kg_per_s(flow_m3_per_s * self.air_density_kg_per_m3)
    }

    /// Calculates volumetric flow from mass flow.
    pub fn volumetric_from_mass_flow(&self, mass_flow: MassFlow) -> f64 {
        mass_flow.to_kg_per_s() / self.air_density_kg_per_m3
    }

    /// Returns the air density.
    pub fn air_density(&self) -> f64 {
        self.air_density_kg_per_m3
    }

    /// Returns the speed ratio.
    pub fn speed_ratio(&self) -> f64 {
        self.speed_ratio
    }

    /// Sets the speed ratio (0.0 to 1.0).
    pub fn set_speed_ratio(&mut self, ratio: f64) -> Result<(), ComponentError> {
        if !(0.0..=1.0).contains(&ratio) {
            return Err(ComponentError::InvalidState(
                "Speed ratio must be between 0.0 and 1.0".to_string(),
            ));
        }
        self.speed_ratio = ratio;
        Ok(())
    }

    /// Returns both ports as a slice for solver topology.
    pub fn get_ports_slice(&self) -> [&Port<Connected>; 2] {
        [&self.port_inlet, &self.port_outlet]
    }
}

impl Component for Fan<Connected> {
    fn compute_residuals(
        &self,
        state: &SystemState,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        if residuals.len() != self.n_equations() {
            return Err(ComponentError::InvalidResidualDimensions {
                expected: self.n_equations(),
                actual: residuals.len(),
            });
        }

        match self.operational_state {
            OperationalState::Off => {
                residuals[0] = state[0];
                residuals[1] = 0.0;
                return Ok(());
            }
            OperationalState::Bypass => {
                let p_in = self.port_inlet.pressure().to_pascals();
                let p_out = self.port_outlet.pressure().to_pascals();
                let h_in = self.port_inlet.enthalpy().to_joules_per_kg();
                let h_out = self.port_outlet.enthalpy().to_joules_per_kg();

                residuals[0] = p_in - p_out;
                residuals[1] = h_in - h_out;
                return Ok(());
            }
            OperationalState::On => {}
        }

        if state.len() < 2 {
            return Err(ComponentError::InvalidStateDimensions {
                expected: 2,
                actual: state.len(),
            });
        }

        let mass_flow_kg_s = state[0];
        let _power_w = state[1];

        let flow_m3_s = mass_flow_kg_s / self.air_density_kg_per_m3;
        let delta_p_calc = self.static_pressure_rise(flow_m3_s);

        let p_in = self.port_inlet.pressure().to_pascals();
        let p_out = self.port_outlet.pressure().to_pascals();
        let delta_p_actual = p_out - p_in;

        residuals[0] = delta_p_calc - delta_p_actual;

        let power_calc = self.fan_power(flow_m3_s).to_watts();
        residuals[1] = power_calc - _power_w;

        Ok(())
    }

    fn jacobian_entries(
        &self,
        state: &SystemState,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        if state.len() < 2 {
            return Err(ComponentError::InvalidStateDimensions {
                expected: 2,
                actual: state.len(),
            });
        }

        let mass_flow_kg_s = state[0];
        let flow_m3_s = mass_flow_kg_s / self.air_density_kg_per_m3;

        let h = 0.001;
        let p_plus = self.static_pressure_rise(flow_m3_s + h / self.air_density_kg_per_m3);
        let p_minus = self.static_pressure_rise(flow_m3_s - h / self.air_density_kg_per_m3);
        let dp_dm = (p_plus - p_minus) / (2.0 * h);

        jacobian.add_entry(0, 0, dp_dm);
        jacobian.add_entry(0, 1, 0.0);

        let pow_plus = self
            .fan_power(flow_m3_s + h / self.air_density_kg_per_m3)
            .to_watts();
        let pow_minus = self
            .fan_power(flow_m3_s - h / self.air_density_kg_per_m3)
            .to_watts();
        let dpow_dm = (pow_plus - pow_minus) / (2.0 * h);

        jacobian.add_entry(1, 0, dpow_dm);
        jacobian.add_entry(1, 1, -1.0);

        Ok(())
    }

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

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

impl StateManageable for Fan<Connected> {
    fn state(&self) -> OperationalState {
        self.operational_state
    }

    fn set_state(&mut self, state: OperationalState) -> Result<(), ComponentError> {
        if self.operational_state.can_transition_to(state) {
            let from = self.operational_state;
            self.operational_state = state;
            self.on_state_change(from, state);
            Ok(())
        } else {
            Err(ComponentError::InvalidStateTransition {
                from: self.operational_state,
                to: state,
                reason: "Transition not allowed".to_string(),
            })
        }
    }

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

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

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::port::FluidId;
    use approx::assert_relative_eq;
    use entropyk_core::{Enthalpy, Pressure};

    fn create_test_curves() -> FanCurves {
        // Typical centrifugal fan:
        // P_s = 500 - 100*Q - 200*Q² (Pa, Q in m³/s)
        // η = 0.5 + 0.3*Q - 0.5*Q²
        FanCurves::quadratic(500.0, -100.0, -200.0, 0.5, 0.3, -0.5).unwrap()
    }

    fn create_test_fan_connected() -> Fan<Connected> {
        let curves = create_test_curves();
        let inlet = Port::new(
            FluidId::new("Air"),
            Pressure::from_bar(1.01325),
            Enthalpy::from_joules_per_kg(300000.0),
        );
        let outlet = Port::new(
            FluidId::new("Air"),
            Pressure::from_bar(1.01325),
            Enthalpy::from_joules_per_kg(300000.0),
        );
        let (inlet_conn, outlet_conn) = inlet.connect(outlet).unwrap();

        Fan {
            curves,
            port_inlet: inlet_conn,
            port_outlet: outlet_conn,
            air_density_kg_per_m3: 1.2,
            speed_ratio: 1.0,
            circuit_id: CircuitId::default(),
            operational_state: OperationalState::default(),
            _state: PhantomData,
        }
    }

    #[test]
    fn test_fan_curves_creation() {
        let curves = create_test_curves();
        assert_eq!(curves.static_pressure_at_flow(0.0), 500.0);
        assert_relative_eq!(curves.efficiency_at_flow(0.0), 0.5);
    }

    #[test]
    fn test_fan_static_pressure() {
        let curves = create_test_curves();
        // P_s = 500 - 100*1 - 200*1 = 200 Pa
        let pressure = curves.static_pressure_at_flow(1.0);
        assert_relative_eq!(pressure, 200.0, epsilon = 1e-10);
    }

    #[test]
    fn test_fan_creation() {
        let fan = create_test_fan_connected();
        assert_relative_eq!(fan.air_density(), 1.2, epsilon = 1e-10);
        assert_eq!(fan.speed_ratio(), 1.0);
    }

    #[test]
    fn test_fan_pressure_rise_full_speed() {
        let fan = create_test_fan_connected();
        let pressure = fan.static_pressure_rise(0.0);
        assert_relative_eq!(pressure, 500.0, epsilon = 1e-10);
    }

    #[test]
    fn test_fan_pressure_rise_half_speed() {
        let mut fan = create_test_fan_connected();
        fan.set_speed_ratio(0.5).unwrap();

        // At 50% speed, shut-off pressure is 25% of full speed
        let pressure = fan.static_pressure_rise(0.0);
        assert_relative_eq!(pressure, 125.0, epsilon = 1e-10);
    }

    #[test]
    fn test_fan_fan_power() {
        let fan = create_test_fan_connected();

        // At Q=1 m³/s: P_s ≈ 200 Pa, η ≈ 0.3
        // P = 1 * 200 / 0.3 ≈ 667 W
        let power = fan.fan_power(1.0);
        assert!(power.to_watts() > 0.0);
        assert!(power.to_watts() < 2000.0);
    }

    #[test]
    fn test_fan_affinity_laws_power() {
        let fan_full = create_test_fan_connected();

        let mut fan_half = create_test_fan_connected();
        fan_half.set_speed_ratio(0.5).unwrap();

        let power_full = fan_full.fan_power(1.0);
        let power_half = fan_half.fan_power(0.5);

        // Ratio should be approximately 0.125 (cube law)
        let ratio = power_half.to_watts() / power_full.to_watts();
        assert_relative_eq!(ratio, 0.125, epsilon = 0.1);
    }

    #[test]
    fn test_fan_total_pressure() {
        let fan = create_test_fan_connected();

        // With a duct area of 0.5 m²
        let total_p = fan.total_pressure_rise(1.0, 0.5);
        let static_p = fan.static_pressure_rise(1.0);

        // Total > Static due to velocity pressure
        assert!(total_p > static_p);
    }

    #[test]
    fn test_fan_component_n_equations() {
        let fan = create_test_fan_connected();
        assert_eq!(fan.n_equations(), 2);
    }

    #[test]
    fn test_fan_state_manageable() {
        let fan = create_test_fan_connected();
        assert_eq!(fan.state(), OperationalState::On);
        assert!(fan.can_transition_to(OperationalState::Off));
    }

    #[test]
    fn test_standard_air_constants() {
        assert_relative_eq!(standard_air::DENSITY, 1.204, epsilon = 0.01);
        assert_relative_eq!(standard_air::CP, 1005.0);
    }
}
