use crate::errors::{ProblemError, ProblemResult};
use crate::problem::{Constraint, ConstraintType, Objective, ObjectiveType, Variable};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

/// Represents a complete Linear Programming problem
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Problem {
    pub variables: Vec<Variable>,
    pub constraints: Vec<Constraint>,
    pub objective: Objective,
    pub name: Option<String>,
}

impl Problem {
    /// Creates a new optimization problem
    pub fn new(
        variables: Vec<Variable>,
        constraints: Vec<Constraint>,
        objective: Objective,
    ) -> ProblemResult<Self> {
        let problem = Problem {
            variables,
            constraints,
            objective,
            name: None,
        };

        problem.validate()?;
        Ok(problem)
    }

    /// Creates a new problem with a name
    pub fn new_with_name(
        name: impl Into<String>,
        variables: Vec<Variable>,
        constraints: Vec<Constraint>,
        objective: Objective,
    ) -> ProblemResult<Self> {
        let mut problem = Self::new(variables, constraints, objective)?;
        problem.name = Some(name.into());
        Ok(problem)
    }

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

    /// Validates the problem structure for consistency
    pub fn validate(&self) -> ProblemResult<()> {
        // Check if problem has variables
        if self.variables.is_empty() {
            return Err(ProblemError::EmptyProblem);
        }

        let num_vars = self.variables.len();

        // Check objective dimension
        if self.objective.num_variables() != num_vars {
            return Err(ProblemError::DimensionMismatch {
                expected: num_vars,
                actual: self.objective.num_variables(),
            });
        }

        // Check constraint dimensions
        for (_, constraint) in self.constraints.iter().enumerate() {
            if constraint.num_variables() != num_vars {
                return Err(ProblemError::InvalidConstraintLength {
                    expected: num_vars,
                    actual: constraint.num_variables(),
                });
            }
        }

        // Check for duplicate variable names
        let mut var_names = HashMap::new();
        for (i, var) in self.variables.iter().enumerate() {
            if let Some(&_prev_index) = var_names.get(&var.name) {
                return Err(ProblemError::DuplicateVariableName {
                    name: var.name.clone(),
                });
            }
            var_names.insert(&var.name, i);
        }

        Ok(())
    }

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

    /// Gets the number of constraints in the problem
    pub fn num_constraints(&self) -> usize {
        self.constraints.len()
    }

    /// Finds a variable by name
    pub fn find_variable(&self, name: &str) -> Option<(usize, &Variable)> {
        self.variables
            .iter()
            .enumerate()
            .find(|(_, var)| var.name == name)
    }

    /// Finds a constraint by name
    pub fn find_constraint(&self, name: &str) -> Option<(usize, &Constraint)> {
        self.constraints
            .iter()
            .enumerate()
            .find(|(_, constraint)| constraint.name.as_deref() == Some(name))
    }

    /// Evaluates the objective function for a given solution
    pub fn evaluate_objective(&self, solution: &[f64]) -> ProblemResult<f64> {
        self.objective.evaluate(solution)
    }

    /// Checks if a solution is feasible (satisfies all constraints)
    pub fn is_feasible(&self, solution: &[f64]) -> ProblemResult<bool> {
        if solution.len() != self.num_variables() {
            return Err(ProblemError::DimensionMismatch {
                expected: self.num_variables(),
                actual: solution.len(),
            });
        }

        // Check variable bounds
        for (_, (&value, var)) in solution.iter().zip(self.variables.iter()).enumerate() {
            if !var.is_feasible(value) {
                return Ok(false);
            }
        }

        // Check constraints
        for constraint in &self.constraints {
            if !constraint.is_satisfied(solution)? {
                return Ok(false);
            }
        }

        Ok(true)
    }

    /// Computes the total constraint violation for a solution
    pub fn total_violation(&self, solution: &[f64]) -> ProblemResult<f64> {
        if solution.len() != self.num_variables() {
            return Err(ProblemError::DimensionMismatch {
                expected: self.num_variables(),
                actual: solution.len(),
            });
        }

        let mut total_violation = 0.0;

        // Variable bound violations
        for (&value, var) in solution.iter().zip(self.variables.iter()) {
            if value < var.lower_bound {
                total_violation += var.lower_bound - value;
            }
            if value > var.upper_bound {
                total_violation += value - var.upper_bound;
            }
        }

        // Constraint violations
        for constraint in &self.constraints {
            total_violation += constraint.violation(solution)?;
        }

        Ok(total_violation)
    }

    /// Projects a solution onto the variable bounds (ignoring constraints)
    pub fn project_bounds(&self, solution: &[f64]) -> ProblemResult<Vec<f64>> {
        if solution.len() != self.num_variables() {
            return Err(ProblemError::DimensionMismatch {
                expected: self.num_variables(),
                actual: solution.len(),
            });
        }

        Ok(solution
            .iter()
            .zip(self.variables.iter())
            .map(|(&value, var)| var.project(value))
            .collect())
    }

    /// Converts the problem to standard form for simplex method
    /// Returns (standard_problem, initial_basic_variables, original_to_standard_mapping)
    pub fn to_standard_form(&self) -> ProblemResult<(Problem, Vec<usize>, Vec<usize>)> {
        let mut new_variables = self.variables.clone();
        let mut new_constraints = Vec::new();
        let original_to_standard = (0..self.num_variables()).collect::<Vec<_>>();

        // Convert objective to minimization
        let objective = self.objective.to_minimization();
        let mut new_objective_coeffs = objective.coefficients.clone();

        let mut initial_basic_vars = Vec::new();
        let mut slack_count = 0;
        let mut surplus_count = 0;
        let mut artificial_count = 0;

        // Process each constraint
        for constraint in &self.constraints {
            let mut coeffs = constraint.coefficients.clone();

            match constraint.constraint_type {
                ConstraintType::LessEqual => {
                    // Add slack variable: a₁x₁ + ... + s = b
                    let slack_var = Variable::new_non_negative(format!("s{}", slack_count));
                    let slack_idx = new_variables.len();
                    new_variables.push(slack_var);
                    coeffs.push(1.0);
                    initial_basic_vars.push(slack_idx);
                    slack_count += 1;
                }
                ConstraintType::GreaterEqual => {
                    // Add surplus and artificial variables: a₁x₁ + ... - u + a = b
                    let surplus_var = Variable::new_non_negative(format!("u{}", surplus_count));
                    let artificial_var =
                        Variable::new_non_negative(format!("a{}", artificial_count));

                    let _surplus_idx = new_variables.len();
                    new_variables.push(surplus_var);
                    coeffs.push(-1.0);

                    let artificial_idx = new_variables.len();
                    new_variables.push(artificial_var);
                    coeffs.push(1.0);
                    initial_basic_vars.push(artificial_idx);

                    surplus_count += 1;
                    artificial_count += 1;
                }
                ConstraintType::Equal => {
                    // Add artificial variable: a₁x₁ + ... + a = b
                    let artificial_var =
                        Variable::new_non_negative(format!("a{}", artificial_count));
                    let artificial_idx = new_variables.len();
                    new_variables.push(artificial_var);
                    coeffs.push(1.0);
                    initial_basic_vars.push(artificial_idx);
                    artificial_count += 1;
                }
            }

            let new_constraint = Constraint::new(coeffs, ConstraintType::Equal, constraint.rhs)?;
            new_constraints.push(new_constraint);
        }

        // Pad objective coefficients for auxiliary variables
        new_objective_coeffs.resize(new_variables.len(), 0.0);

        let standard_objective = Objective::new(new_objective_coeffs, ObjectiveType::Minimize)?;
        let standard_problem = Problem::new(new_variables, new_constraints, standard_objective)?;

        Ok((standard_problem, initial_basic_vars, original_to_standard))
    }

    /// Gets problem statistics
    pub fn statistics(&self) -> ProblemStatistics {
        let num_continuous = self
            .variables
            .iter()
            .filter(|v| matches!(v.var_type, crate::problem::VariableType::Continuous))
            .count();
        let num_integer = self
            .variables
            .iter()
            .filter(|v| matches!(v.var_type, crate::problem::VariableType::Integer))
            .count();
        let num_binary = self
            .variables
            .iter()
            .filter(|v| matches!(v.var_type, crate::problem::VariableType::Binary))
            .count();

        let num_le = self
            .constraints
            .iter()
            .filter(|c| c.constraint_type == ConstraintType::LessEqual)
            .count();
        let num_ge = self
            .constraints
            .iter()
            .filter(|c| c.constraint_type == ConstraintType::GreaterEqual)
            .count();
        let num_eq = self
            .constraints
            .iter()
            .filter(|c| c.constraint_type == ConstraintType::Equal)
            .count();

        ProblemStatistics {
            num_variables: self.num_variables(),
            num_constraints: self.num_constraints(),
            num_continuous,
            num_integer,
            num_binary,
            num_le_constraints: num_le,
            num_ge_constraints: num_ge,
            num_eq_constraints: num_eq,
            is_linear: true, // For now, we only support linear problems
            is_integer: num_integer > 0 || num_binary > 0,
        }
    }
}

/// Statistics about a problem
#[derive(Debug, Clone)]
pub struct ProblemStatistics {
    pub num_variables: usize,
    pub num_constraints: usize,
    pub num_continuous: usize,
    pub num_integer: usize,
    pub num_binary: usize,
    pub num_le_constraints: usize,
    pub num_ge_constraints: usize,
    pub num_eq_constraints: usize,
    pub is_linear: bool,
    pub is_integer: bool,
}

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

        writeln!(f, "\nObjective:")?;
        writeln!(f, "  {}", self.objective)?;

        writeln!(f, "\nVariables:")?;
        for (i, var) in self.variables.iter().enumerate() {
            writeln!(f, "  x{}: {}", i, var)?;
        }

        writeln!(f, "\nConstraints:")?;
        for (i, constraint) in self.constraints.iter().enumerate() {
            writeln!(f, "  c{}: {}", i, constraint)?;
        }

        Ok(())
    }
}

impl fmt::Display for ProblemStatistics {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "Problem Statistics:")?;
        writeln!(
            f,
            "  Variables: {} ({} continuous, {} integer, {} binary)",
            self.num_variables, self.num_continuous, self.num_integer, self.num_binary
        )?;
        writeln!(
            f,
            "  Constraints: {} ({} <=, {} >=, {} =)",
            self.num_constraints,
            self.num_le_constraints,
            self.num_ge_constraints,
            self.num_eq_constraints
        )?;
        writeln!(
            f,
            "  Type: {}",
            if self.is_integer {
                "Mixed Integer"
            } else {
                "Linear"
            }
        )?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::problem::{ConstraintType, ObjectiveType};
    use approx::assert_abs_diff_eq;

    fn create_test_problem() -> Problem {
        let variables = vec![
            Variable::new_non_negative("x"),
            Variable::new_non_negative("y"),
        ];

        let constraints = vec![
            Constraint::new(vec![1.0, 1.0], ConstraintType::LessEqual, 10.0).unwrap(),
            Constraint::new(vec![2.0, -1.0], ConstraintType::GreaterEqual, 2.0).unwrap(),
        ];

        let objective = Objective::new(vec![3.0, 2.0], ObjectiveType::Maximize).unwrap();

        Problem::new(variables, constraints, objective).unwrap()
    }

    #[test]
    fn test_problem_creation() {
        let problem = create_test_problem();
        assert_eq!(problem.num_variables(), 2);
        assert_eq!(problem.num_constraints(), 2);
    }

    #[test]
    fn test_problem_validation() {
        // Valid problem
        let problem = create_test_problem();
        assert!(problem.validate().is_ok());

        // Invalid: mismatched objective dimension
        let variables = vec![Variable::new_non_negative("x")];
        let constraints = vec![];
        let objective = Objective::new(vec![1.0, 2.0], ObjectiveType::Maximize).unwrap(); // 2 coeffs, 1 var

        let result = Problem::new(variables, constraints, objective);
        assert!(result.is_err());
    }

    #[test]
    fn test_feasibility_check() {
        let problem = create_test_problem();

        // Feasible solution
        assert!(problem.is_feasible(&[3.0, 4.0]).unwrap());

        // Infeasible solution (violates first constraint)
        assert!(!problem.is_feasible(&[8.0, 8.0]).unwrap());

        // Infeasible solution (negative value)
        assert!(!problem.is_feasible(&[-1.0, 5.0]).unwrap());
    }

    #[test]
    fn test_objective_evaluation() {
        let problem = create_test_problem();
        let value = problem.evaluate_objective(&[3.0, 4.0]).unwrap();
        assert_abs_diff_eq!(value, 3.0 * 3.0 + 2.0 * 4.0);
    }

    #[test]
    fn test_standard_form_conversion() {
        let problem = create_test_problem();
        let (standard_problem, _, _) = problem.to_standard_form().unwrap();

        // Should have original variables + slack + surplus + artificial
        assert_eq!(standard_problem.num_variables(), 5); // x, y, s0, u0, a0
        assert_eq!(standard_problem.num_constraints(), 2);

        // All constraints should be equalities
        for constraint in &standard_problem.constraints {
            assert_eq!(constraint.constraint_type, ConstraintType::Equal);
        }

        // Objective should be minimization
        assert_eq!(
            standard_problem.objective.objective_type,
            ObjectiveType::Minimize
        );
    }

    #[test]
    fn test_problem_statistics() {
        let problem = create_test_problem();
        let stats = problem.statistics();

        assert_eq!(stats.num_variables, 2);
        assert_eq!(stats.num_constraints, 2);
        assert_eq!(stats.num_continuous, 2);
        assert_eq!(stats.num_integer, 0);
        assert_eq!(stats.num_binary, 0);
        assert_eq!(stats.num_le_constraints, 1);
        assert_eq!(stats.num_ge_constraints, 1);
        assert_eq!(stats.num_eq_constraints, 0);
        assert!(!stats.is_integer);
    }
}

