use crate::problem::Problem;
use std::collections::HashMap;

/// Validation utilities for optimization problems
pub struct Validator {
    /// Tolerance for numerical comparisons
    pub tolerance: f64,
    /// Whether to check for numerical issues
    pub check_numerical_issues: bool,
    /// Whether to validate problem structure
    pub check_structure: bool,
}

/// Validation result containing warnings and errors
#[derive(Debug, Clone)]
pub struct ValidationResult {
    /// Whether the problem is valid
    pub is_valid: bool,
    /// Critical errors that prevent solving
    pub errors: Vec<ValidationError>,
    /// Warnings about potential issues
    pub warnings: Vec<ValidationWarning>,
    /// General problem information
    pub info: Vec<String>,
}

/// Types of validation errors
#[derive(Debug, Clone)]
pub enum ValidationError {
    EmptyProblem,
    DimensionMismatch {
        expected: usize,
        actual: usize,
        context: String,
    },
    DuplicateVariableName {
        name: String,
    },
    InvalidBounds {
        variable: String,
        lower: f64,
        upper: f64,
    },
    InfiniteCoefficient {
        context: String,
        value: f64,
    },
    NanCoefficient {
        context: String,
    },
    TrivialConstraint {
        constraint_index: usize,
    },
    InconsistentConstraint {
        constraint_index: usize,
        reason: String,
    },
}

/// Types of validation warnings
#[derive(Debug, Clone)]
pub enum ValidationWarning {
    LargeCoefficientRange { min: f64, max: f64 },
    NearSingularMatrix,
    PoorScaling { context: String, ratio: f64 },
    RedundantConstraint { constraint_index: usize },
    WeakBounds { variable: String },
    LargeRhs { constraint_index: usize, value: f64 },
    SmallCoefficient { context: String, value: f64 },
}

impl Default for Validator {
    fn default() -> Self {
        Validator {
            tolerance: 1e-10,
            check_numerical_issues: true,
            check_structure: true,
        }
    }
}

impl Validator {
    /// Creates a new validator with default settings
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a validator with custom tolerance
    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
        self.tolerance = tolerance;
        self
    }

    /// Validates a problem and returns detailed results
    pub fn validate(&self, problem: &Problem) -> ValidationResult {
        let mut result = ValidationResult {
            is_valid: true,
            errors: Vec::new(),
            warnings: Vec::new(),
            info: Vec::new(),
        };

        // Basic structure validation
        if self.check_structure {
            self.validate_structure(problem, &mut result);
        }

        // Numerical validation
        if self.check_numerical_issues {
            self.validate_numerical_properties(problem, &mut result);
        }

        // Additional checks
        self.validate_constraints(problem, &mut result);
        self.validate_variables(problem, &mut result);
        self.validate_objective(problem, &mut result);

        // Add general information
        self.add_problem_info(problem, &mut result);

        result.is_valid = result.errors.is_empty();
        result
    }

    /// Validates basic problem structure
    fn validate_structure(&self, problem: &Problem, result: &mut ValidationResult) {
        // Check for empty problem
        if problem.variables.is_empty() {
            result.errors.push(ValidationError::EmptyProblem);
            return;
        }

        let num_vars = problem.num_variables();

        // Check objective dimension
        if problem.objective.num_variables() != num_vars {
            result.errors.push(ValidationError::DimensionMismatch {
                expected: num_vars,
                actual: problem.objective.num_variables(),
                context: "objective function".to_string(),
            });
        }

        // Check constraint dimensions
        for (i, constraint) in problem.constraints.iter().enumerate() {
            if constraint.num_variables() != num_vars {
                result.errors.push(ValidationError::DimensionMismatch {
                    expected: num_vars,
                    actual: constraint.num_variables(),
                    context: format!("constraint {}", i),
                });
            }
        }

        // Check for duplicate variable names
        let mut name_counts = HashMap::new();
        for var in &problem.variables {
            *name_counts.entry(&var.name).or_insert(0) += 1;
        }

        for (name, count) in name_counts {
            if count > 1 {
                result
                    .errors
                    .push(ValidationError::DuplicateVariableName { name: name.clone() });
            }
        }
    }

    /// Validates numerical properties
    fn validate_numerical_properties(&self, problem: &Problem, result: &mut ValidationResult) {
        let mut all_coefficients = Vec::new();

        // Collect all finite coefficients
        for constraint in &problem.constraints {
            for &coeff in &constraint.coefficients {
                if coeff.is_finite() && coeff.abs() > self.tolerance {
                    all_coefficients.push(coeff.abs());
                }
            }
        }

        for &coeff in &problem.objective.coefficients {
            if coeff.is_finite() && coeff.abs() > self.tolerance {
                all_coefficients.push(coeff.abs());
            }
        }

        if !all_coefficients.is_empty() {
            let min_coeff = all_coefficients
                .iter()
                .fold(f64::INFINITY, |a, &b| a.min(b));
            let max_coeff = all_coefficients.iter().fold(0.0f64, |a, &b| a.max(b));

            if max_coeff / min_coeff > 1e12 {
                result
                    .warnings
                    .push(ValidationWarning::LargeCoefficientRange {
                        min: min_coeff,
                        max: max_coeff,
                    });
            }

            // Check for poor scaling in individual constraints
            for (i, constraint) in problem.constraints.iter().enumerate() {
                let constraint_coeffs: Vec<f64> = constraint
                    .coefficients
                    .iter()
                    .filter(|&&c| c.is_finite() && c.abs() > self.tolerance)
                    .map(|&c| c.abs())
                    .collect();

                if constraint_coeffs.len() > 1 {
                    let min_c = constraint_coeffs
                        .iter()
                        .fold(f64::INFINITY, |a, &b| a.min(b));
                    let max_c = constraint_coeffs.iter().fold(0.0f64, |a, &b| a.max(b));
                    let ratio = max_c / min_c;

                    if ratio > 1e8 {
                        result.warnings.push(ValidationWarning::PoorScaling {
                            context: format!("constraint {}", i),
                            ratio,
                        });
                    }
                }
            }
        }
    }

    /// Validates constraints
    fn validate_constraints(&self, problem: &Problem, result: &mut ValidationResult) {
        for (i, constraint) in problem.constraints.iter().enumerate() {
            // Check for NaN and infinite values
            if !constraint.rhs.is_finite() {
                if constraint.rhs.is_nan() {
                    result.errors.push(ValidationError::NanCoefficient {
                        context: format!("constraint {} RHS", i),
                    });
                } else {
                    result.errors.push(ValidationError::InfiniteCoefficient {
                        context: format!("constraint {} RHS", i),
                        value: constraint.rhs,
                    });
                }
            }

            for (j, &coeff) in constraint.coefficients.iter().enumerate() {
                if coeff.is_nan() {
                    result.errors.push(ValidationError::NanCoefficient {
                        context: format!("constraint {} coefficient {}", i, j),
                    });
                } else if coeff.is_infinite() {
                    result.errors.push(ValidationError::InfiniteCoefficient {
                        context: format!("constraint {} coefficient {}", i, j),
                        value: coeff,
                    });
                }
            }

            // Check for trivial constraints (all coefficients zero)
            if constraint
                .coefficients
                .iter()
                .all(|&c| c.abs() <= self.tolerance)
            {
                if constraint.rhs.abs() > self.tolerance {
                    result.errors.push(ValidationError::InconsistentConstraint {
                        constraint_index: i,
                        reason: "Zero coefficients with non-zero RHS".to_string(),
                    });
                } else {
                    result.errors.push(ValidationError::TrivialConstraint {
                        constraint_index: i,
                    });
                }
            }

            // Check for large RHS values
            if constraint.rhs.abs() > 1e8 {
                result.warnings.push(ValidationWarning::LargeRhs {
                    constraint_index: i,
                    value: constraint.rhs,
                });
            }

            // Check for very small coefficients
            for &coeff in &constraint.coefficients {
                if coeff != 0.0 && coeff.abs() < 1e-12 {
                    result.warnings.push(ValidationWarning::SmallCoefficient {
                        context: format!("constraint {}", i),
                        value: coeff,
                    });
                }
            }
        }

        // Check for potentially redundant constraints (simplified check)
        for i in 0..problem.constraints.len() {
            for j in i + 1..problem.constraints.len() {
                if self.constraints_potentially_redundant(
                    &problem.constraints[i],
                    &problem.constraints[j],
                ) {
                    result
                        .warnings
                        .push(ValidationWarning::RedundantConstraint {
                            constraint_index: j,
                        });
                }
            }
        }
    }

    /// Validates variables
    fn validate_variables(&self, problem: &Problem, result: &mut ValidationResult) {
        for (_, var) in problem.variables.iter().enumerate() {
            // Check bounds
            if var.lower_bound > var.upper_bound {
                result.errors.push(ValidationError::InvalidBounds {
                    variable: var.name.clone(),
                    lower: var.lower_bound,
                    upper: var.upper_bound,
                });
            }

            // Check for NaN bounds
            if var.lower_bound.is_nan() || var.upper_bound.is_nan() {
                result.errors.push(ValidationError::NanCoefficient {
                    context: format!("variable {} bounds", var.name),
                });
            }

            // Warn about weak bounds (very large ranges)
            if var.is_bounded() && (var.upper_bound - var.lower_bound) > 1e10 {
                result.warnings.push(ValidationWarning::WeakBounds {
                    variable: var.name.clone(),
                });
            }
        }
    }

    /// Validates objective function
    fn validate_objective(&self, problem: &Problem, result: &mut ValidationResult) {
        // Check for NaN and infinite values in objective
        if !problem.objective.constant.is_finite() {
            if problem.objective.constant.is_nan() {
                result.errors.push(ValidationError::NanCoefficient {
                    context: "objective constant".to_string(),
                });
            } else {
                result.errors.push(ValidationError::InfiniteCoefficient {
                    context: "objective constant".to_string(),
                    value: problem.objective.constant,
                });
            }
        }

        for (i, &coeff) in problem.objective.coefficients.iter().enumerate() {
            if coeff.is_nan() {
                result.errors.push(ValidationError::NanCoefficient {
                    context: format!("objective coefficient {}", i),
                });
            } else if coeff.is_infinite() {
                result.errors.push(ValidationError::InfiniteCoefficient {
                    context: format!("objective coefficient {}", i),
                    value: coeff,
                });
            } else if coeff != 0.0 && coeff.abs() < 1e-12 {
                result.warnings.push(ValidationWarning::SmallCoefficient {
                    context: "objective".to_string(),
                    value: coeff,
                });
            }
        }
    }

    /// Adds general problem information
    fn add_problem_info(&self, problem: &Problem, result: &mut ValidationResult) {
        let stats = problem.statistics();

        result
            .info
            .push(format!("Variables: {}", stats.num_variables));
        result
            .info
            .push(format!("Constraints: {}", stats.num_constraints));
        result.info.push(format!(
            "Problem type: {}",
            if stats.is_integer {
                "Mixed Integer"
            } else {
                "Linear"
            }
        ));

        if stats.num_continuous > 0 {
            result
                .info
                .push(format!("Continuous variables: {}", stats.num_continuous));
        }
        if stats.num_integer > 0 {
            result
                .info
                .push(format!("Integer variables: {}", stats.num_integer));
        }
        if stats.num_binary > 0 {
            result
                .info
                .push(format!("Binary variables: {}", stats.num_binary));
        }

        result.info.push(format!(
            "Constraint types: {} <=, {} >=, {} =",
            stats.num_le_constraints, stats.num_ge_constraints, stats.num_eq_constraints
        ));
    }

    /// Simple check if two constraints might be redundant
    fn constraints_potentially_redundant(
        &self,
        c1: &crate::problem::Constraint,
        c2: &crate::problem::Constraint,
    ) -> bool {
        if c1.constraint_type != c2.constraint_type {
            return false;
        }

        if c1.coefficients.len() != c2.coefficients.len() {
            return false;
        }

        // Check if constraints are scalar multiples of each other
        let mut ratio = None;
        for (_, (&a, &b)) in c1
            .coefficients
            .iter()
            .zip(c2.coefficients.iter())
            .enumerate()
        {
            if a.abs() <= self.tolerance && b.abs() <= self.tolerance {
                continue;
            }

            if a.abs() <= self.tolerance || b.abs() <= self.tolerance {
                return false;
            }

            let current_ratio = a / b;
            match ratio {
                None => ratio = Some(current_ratio),
                Some(r) => {
                    if (current_ratio - r).abs() > self.tolerance {
                        return false;
                    }
                }
            }
        }

        if let Some(r) = ratio {
            (c1.rhs - r * c2.rhs).abs() <= self.tolerance
        } else {
            false
        }
    }
}

impl std::fmt::Display for ValidationResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "Validation Result: {}",
            if self.is_valid { "VALID" } else { "INVALID" }
        )?;

        if !self.errors.is_empty() {
            writeln!(f, "\nErrors ({}):", self.errors.len())?;
            for (i, error) in self.errors.iter().enumerate() {
                writeln!(f, "  {}: {}", i + 1, error)?;
            }
        }

        if !self.warnings.is_empty() {
            writeln!(f, "\nWarnings ({}):", self.warnings.len())?;
            for (i, warning) in self.warnings.iter().enumerate() {
                writeln!(f, "  {}: {}", i + 1, warning)?;
            }
        }

        if !self.info.is_empty() {
            writeln!(f, "\nProblem Information:")?;
            for info in &self.info {
                writeln!(f, "  {}", info)?;
            }
        }

        Ok(())
    }
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ValidationError::EmptyProblem => write!(f, "Problem has no variables"),
            ValidationError::DimensionMismatch {
                expected,
                actual,
                context,
            } => write!(
                f,
                "Dimension mismatch in {}: expected {} but got {}",
                context, expected, actual
            ),
            ValidationError::DuplicateVariableName { name } => {
                write!(f, "Duplicate variable name: '{}'", name)
            }
            ValidationError::InvalidBounds {
                variable,
                lower,
                upper,
            } => write!(
                f,
                "Invalid bounds for '{}': [{}, {}]",
                variable, lower, upper
            ),
            ValidationError::InfiniteCoefficient { context, value } => {
                write!(f, "Infinite coefficient in {}: {}", context, value)
            }
            ValidationError::NanCoefficient { context } => {
                write!(f, "NaN coefficient in {}", context)
            }
            ValidationError::TrivialConstraint { constraint_index } => write!(
                f,
                "Trivial constraint {}: all coefficients are zero",
                constraint_index
            ),
            ValidationError::InconsistentConstraint {
                constraint_index,
                reason,
            } => write!(
                f,
                "Inconsistent constraint {}: {}",
                constraint_index, reason
            ),
        }
    }
}

impl std::fmt::Display for ValidationWarning {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ValidationWarning::LargeCoefficientRange { min, max } => {
                write!(f, "Large coefficient range: [{:.2e}, {:.2e}]", min, max)
            }
            ValidationWarning::NearSingularMatrix => write!(f, "Matrix may be near singular"),
            ValidationWarning::PoorScaling { context, ratio } => {
                write!(f, "Poor scaling in {}: ratio {:.2e}", context, ratio)
            }
            ValidationWarning::RedundantConstraint { constraint_index } => {
                write!(f, "Potentially redundant constraint {}", constraint_index)
            }
            ValidationWarning::WeakBounds { variable } => {
                write!(f, "Very large bounds for variable '{}'", variable)
            }
            ValidationWarning::LargeRhs {
                constraint_index,
                value,
            } => write!(
                f,
                "Large RHS value in constraint {}: {:.2e}",
                constraint_index, value
            ),
            ValidationWarning::SmallCoefficient { context, value } => {
                write!(f, "Very small coefficient in {}: {:.2e}", context, value)
            }
        }
    }
}

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

    #[test]
    fn test_valid_problem() {
        let problem = create_valid_problem();
        let validator = Validator::new();
        let result = validator.validate(&problem);

        assert!(result.is_valid);
        assert!(result.errors.is_empty());
    }

    #[test]
    fn test_invalid_bounds() {
        let problem = ProblemBuilder::new()
            .add_variable("x", 10.0, 5.0)
            .unwrap() // Invalid bounds
            .objective(vec![1.0], ObjectiveType::Maximize)
            .unwrap()
            .build();

        // Should fail during problem construction, not validation
        assert!(problem.is_err());
    }

    #[test]
    fn test_dimension_mismatch() {
        // This is tricky to test since ProblemBuilder prevents dimension mismatches
        // But we can test the validator logic directly
        let _validator = Validator::new();

        // Create a problem with mismatched dimensions by bypassing builder
        let variables = vec![
            Variable::new_non_negative("x"),
            Variable::new_non_negative("y"),
        ];
        let constraints = vec![
            Constraint::new(vec![1.0], ConstraintType::LessEqual, 1.0).unwrap(), // Wrong dimension
        ];
        let objective = Objective::new(vec![1.0, 1.0], ObjectiveType::Maximize).unwrap();

        // This should fail at Problem::new() due to validation
        let result = Problem::new(variables, constraints, objective);
        assert!(result.is_err());
    }

    #[test]
    fn test_numerical_warnings() {
        let problem = ProblemBuilder::new()
            .add_non_negative_variable("x")
            .unwrap()
            .add_non_negative_variable("y")
            .unwrap()
            .add_constraint_by_name(
                &[("x", 1e10), ("y", 1e-10)], // Poor scaling
                ConstraintType::LessEqual,
                1.0,
            )
            .unwrap()
            .objective_by_name(&[("x", 1.0), ("y", 1.0)], ObjectiveType::Maximize)
            .unwrap()
            .build()
            .unwrap();

        let validator = Validator::new();
        let result = validator.validate(&problem);

        assert!(result.is_valid); // Still valid, but has warnings
        assert!(!result.warnings.is_empty());

        // Should have poor scaling warning
        let has_scaling_warning = result
            .warnings
            .iter()
            .any(|w| matches!(w, ValidationWarning::PoorScaling { .. }));
        assert!(has_scaling_warning);
    }

    #[test]
    fn test_large_coefficient_range_warning() {
        let problem = ProblemBuilder::new()
            .add_non_negative_variable("x")
            .unwrap()
            .add_non_negative_variable("y")
            .unwrap()
            .add_constraint_by_name(
                &[("x", 1e15), ("y", 1.0)], // Very large range
                ConstraintType::LessEqual,
                1.0,
            )
            .unwrap()
            .objective_by_name(
                &[("x", 1e-15), ("y", 1.0)], // Very large range
                ObjectiveType::Maximize,
            )
            .unwrap()
            .build()
            .unwrap();

        let validator = Validator::new();
        let result = validator.validate(&problem);

        let has_range_warning = result
            .warnings
            .iter()
            .any(|w| matches!(w, ValidationWarning::LargeCoefficientRange { .. }));
        assert!(has_range_warning);
    }

    fn create_valid_problem() -> Problem {
        ProblemBuilder::new()
            .add_non_negative_variable("x")
            .unwrap()
            .add_non_negative_variable("y")
            .unwrap()
            .add_constraint_by_name(&[("x", 1.0), ("y", 1.0)], ConstraintType::LessEqual, 10.0)
            .unwrap()
            .objective_by_name(&[("x", 3.0), ("y", 2.0)], ObjectiveType::Maximize)
            .unwrap()
            .build()
            .unwrap()
    }
}
