use crate::errors::{ProblemError, ProblemResult};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
use approx::AbsDiffEq;

/// Represents the type of a linear constraint
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ConstraintType {
    /// Less than or equal to (<=)
    LessEqual,
    /// Greater than or equal to (>=)
    GreaterEqual,
    /// Equal to (==)
    Equal,
}

impl fmt::Display for ConstraintType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ConstraintType::LessEqual => write!(f, "<="),
            ConstraintType::GreaterEqual => write!(f, ">="),
            ConstraintType::Equal => write!(f, "="),
        }
    }
}

/// Represents a linear constraint in the form of `a_1*x_1 + a_2*x_2 + ... (op) rhs`
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Constraint {
    /// Coefficients of the variables in the constraint
    pub coefficients: Vec<f64>,
    /// Type of constraint (<=, >=, =)
    pub constraint_type: ConstraintType,
    /// Right-hand side value
    pub rhs: f64,
    /// Optional name for the constraint
    pub name: Option<String>,
}

impl Constraint {
    /// Creates a new constraint with the given coefficients, type, and RHS
    pub fn new(
        coefficients: Vec<f64>,
        constraint_type: ConstraintType,
        rhs: f64,
    ) -> ProblemResult<Self> {
        if coefficients.is_empty() {
            return Err(ProblemError::InvalidConstraint {
                reason: "Constraint must have at least one coefficient".to_string(),
            });
        }

        if coefficients.iter().any(|&c| !c.is_finite()) {
            return Err(ProblemError::InvalidConstraint {
                reason: "All coefficients must be finite".to_string(),
            });
        }

        if !rhs.is_finite() {
            return Err(ProblemError::InvalidConstraint {
                reason: "Right-hand side must be finite".to_string(),
            });
        }

        Ok(Constraint {
            coefficients,
            constraint_type,
            rhs,
            name: None,
        })
    }

    /// Creates a new constraint with a name
    pub fn new_with_name(
        name: impl Into<String>,
        coefficients: Vec<f64>,
        constraint_type: ConstraintType,
        rhs: f64,
    ) -> ProblemResult<Self> {
        let mut constraint = Self::new(coefficients, constraint_type, rhs)?;
        constraint.name = Some(name.into());
        Ok(constraint)
    }

    /// Sets the name of the constraint
    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    /// Gets the number of variables in this constraint
    pub fn num_variables(&self) -> usize {
        self.coefficients.len()
    }

    /// Evaluates the left-hand side of the constraint for a given variable assignment
    pub fn evaluate_lhs(&self, variables: &[f64]) -> ProblemResult<f64> {
        if variables.len() != self.coefficients.len() {
            return Err(ProblemError::DimensionMismatch {
                expected: self.coefficients.len(),
                actual: variables.len(),
            });
        }

        Ok(self.coefficients
            .iter()
            .zip(variables.iter())
            .map(|(&coeff, &var)| coeff * var)
            .sum())
    }

    /// Checks if a variable assignment satisfies this constraint
    pub fn is_satisfied(&self, variables: &[f64]) -> ProblemResult<bool> {
        let lhs = self.evaluate_lhs(variables)?;
        let epsilon = f64::EPSILON * (1.0 + lhs.abs().max(self.rhs.abs()));
        
        Ok(match self.constraint_type {
            ConstraintType::LessEqual => lhs <= self.rhs + epsilon,
            ConstraintType::GreaterEqual => lhs >= self.rhs - epsilon,
            ConstraintType::Equal => (lhs - self.rhs).abs() <= epsilon,
        })
    }

    /// Returns the violation amount if the constraint is not satisfied
    /// Positive values indicate constraint violation
    pub fn violation(&self, variables: &[f64]) -> ProblemResult<f64> {
        let lhs = self.evaluate_lhs(variables)?;
        
        Ok(match self.constraint_type {
            ConstraintType::LessEqual => (lhs - self.rhs).max(0.0),
            ConstraintType::GreaterEqual => (self.rhs - lhs).max(0.0),
            ConstraintType::Equal => (lhs - self.rhs).abs(),
        })
    }

    /// Normalizes the constraint by dividing all coefficients and RHS by the L2 norm
    pub fn normalize(&mut self) {
        let norm = self.coefficients.iter().map(|&c| c * c).sum::<f64>().sqrt();
        
        if norm > f64::EPSILON {
            for coeff in &mut self.coefficients {
                *coeff /= norm;
            }
            self.rhs /= norm;
        }
    }

    /// Returns a normalized copy of this constraint
    pub fn normalized(&self) -> Self {
        let mut constraint = self.clone();
        constraint.normalize();
        constraint
    }

    /// Checks if all coefficients are approximately zero
    pub fn is_trivial(&self) -> bool {
        self.coefficients.iter().all(|&c| c.abs() <= f64::EPSILON)
    }

    /// Multiplies the entire constraint by a scalar
    pub fn multiply_by(&mut self, scalar: f64) {
        if scalar < 0.0 {
            // Flip the constraint type when multiplying by negative
            self.constraint_type = match self.constraint_type {
                ConstraintType::LessEqual => ConstraintType::GreaterEqual,
                ConstraintType::GreaterEqual => ConstraintType::LessEqual,
                ConstraintType::Equal => ConstraintType::Equal,
            };
        }
        
        for coeff in &mut self.coefficients {
            *coeff *= scalar;
        }
        self.rhs *= scalar;
    }
}

impl fmt::Display for Constraint {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(ref name) = self.name {
            write!(f, "{}: ", name)?;
        }

        for (i, &coeff) in self.coefficients.iter().enumerate() {
            if i == 0 {
                if coeff == 1.0 {
                    write!(f, "x{}", i)?;
                } else if coeff == -1.0 {
                    write!(f, "-x{}", i)?;
                } else {
                    write!(f, "{:.3}x{}", coeff, i)?;
                }
            } else if coeff > 0.0 {
                if coeff == 1.0 {
                    write!(f, " + x{}", i)?;
                } else {
                    write!(f, " + {:.3}x{}", coeff, i)?;
                }
            } else if coeff < 0.0 {
                if coeff == -1.0 {
                    write!(f, " - x{}", i)?;
                } else {
                    write!(f, " - {:.3}x{}", -coeff, i)?;
                }
            }
        }

        write!(f, " {} {:.3}", self.constraint_type, self.rhs)
    }
}

impl AbsDiffEq for Constraint {
    type Epsilon = f64;

    fn default_epsilon() -> Self::Epsilon {
        f64::EPSILON
    }

    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
        self.constraint_type == other.constraint_type &&
        self.coefficients.len() == other.coefficients.len() &&
        self.coefficients.iter()
            .zip(other.coefficients.iter())
            .all(|(&a, &b)| (a - b).abs() <= epsilon) &&
        (self.rhs - other.rhs).abs() <= epsilon
    }
}

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

    #[test]
    fn test_constraint_creation() {
        let constraint = Constraint::new(vec![2.0, 3.0], ConstraintType::LessEqual, 10.0).unwrap();
        assert_eq!(constraint.coefficients, vec![2.0, 3.0]);
        assert_eq!(constraint.constraint_type, ConstraintType::LessEqual);
        assert_eq!(constraint.rhs, 10.0);
        assert!(constraint.name.is_none());
    }

    #[test]
    fn test_constraint_with_name() {
        let constraint = Constraint::new_with_name(
            "capacity",
            vec![1.0, 2.0],
            ConstraintType::LessEqual,
            5.0
        ).unwrap();
        assert_eq!(constraint.name, Some("capacity".to_string()));
    }

    #[test]
    fn test_invalid_constraint() {
        // Empty coefficients
        assert!(Constraint::new(vec![], ConstraintType::Equal, 0.0).is_err());
        
        // Infinite coefficient
        assert!(Constraint::new(vec![f64::INFINITY], ConstraintType::Equal, 0.0).is_err());
        
        // Infinite RHS
        assert!(Constraint::new(vec![1.0], ConstraintType::Equal, f64::INFINITY).is_err());
    }

    #[test]
    fn test_constraint_evaluation() {
        let constraint = Constraint::new(vec![2.0, 3.0], ConstraintType::LessEqual, 10.0).unwrap();
        
        let lhs = constraint.evaluate_lhs(&[1.0, 2.0]).unwrap();
        assert_abs_diff_eq!(lhs, 8.0);
        
        assert!(constraint.is_satisfied(&[1.0, 2.0]).unwrap());
        assert!(!constraint.is_satisfied(&[3.0, 3.0]).unwrap());
    }

    #[test]
    fn test_constraint_violation() {
        let le_constraint = Constraint::new(vec![1.0, 1.0], ConstraintType::LessEqual, 5.0).unwrap();
        assert_abs_diff_eq!(le_constraint.violation(&[3.0, 4.0]).unwrap(), 2.0);
        assert_abs_diff_eq!(le_constraint.violation(&[2.0, 2.0]).unwrap(), 0.0);
        
        let ge_constraint = Constraint::new(vec![1.0, 1.0], ConstraintType::GreaterEqual, 5.0).unwrap();
        assert_abs_diff_eq!(ge_constraint.violation(&[2.0, 2.0]).unwrap(), 1.0);
        assert_abs_diff_eq!(ge_constraint.violation(&[3.0, 4.0]).unwrap(), 0.0);
        
        let eq_constraint = Constraint::new(vec![1.0, 1.0], ConstraintType::Equal, 5.0).unwrap();
        assert_abs_diff_eq!(eq_constraint.violation(&[2.0, 2.0]).unwrap(), 1.0);
        assert_abs_diff_eq!(eq_constraint.violation(&[3.0, 2.0]).unwrap(), 0.0);
    }

    #[test]
    fn test_constraint_normalization() {
        let mut constraint = Constraint::new(vec![3.0, 4.0], ConstraintType::Equal, 10.0).unwrap();
        constraint.normalize();
        
        let norm = (3.0_f64.powi(2) + 4.0_f64.powi(2)).sqrt();
        assert_abs_diff_eq!(constraint.coefficients[0], 3.0 / norm);
        assert_abs_diff_eq!(constraint.coefficients[1], 4.0 / norm);
        assert_abs_diff_eq!(constraint.rhs, 10.0 / norm);
    }

    #[test]
    fn test_constraint_multiplication() {
        let mut constraint = Constraint::new(vec![1.0, 2.0], ConstraintType::LessEqual, 5.0).unwrap();
        constraint.multiply_by(-1.0);
        
        assert_eq!(constraint.coefficients, vec![-1.0, -2.0]);
        assert_eq!(constraint.rhs, -5.0);
        assert_eq!(constraint.constraint_type, ConstraintType::GreaterEqual);
    }
}