use crate::errors::OptimizationError;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;

/// Status of the optimization solve
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SolutionStatus {
    /// Optimal solution found
    Optimal,
    /// Problem is infeasible
    Infeasible,
    /// Problem is unbounded
    Unbounded,
    /// Solver stopped due to iteration limit
    IterationLimit,
    /// Solver stopped due to time limit
    TimeLimit,
    /// Numerical difficulties encountered
    NumericalDifficulties,
    /// Solution found but may not be optimal
    Suboptimal,
    /// Unknown status
    Unknown,
}

impl fmt::Display for SolutionStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SolutionStatus::Optimal => write!(f, "Optimal"),
            SolutionStatus::Infeasible => write!(f, "Infeasible"),
            SolutionStatus::Unbounded => write!(f, "Unbounded"),
            SolutionStatus::IterationLimit => write!(f, "Iteration Limit Reached"),
            SolutionStatus::TimeLimit => write!(f, "Time Limit Reached"),
            SolutionStatus::NumericalDifficulties => write!(f, "Numerical Difficulties"),
            SolutionStatus::Suboptimal => write!(f, "Suboptimal"),
            SolutionStatus::Unknown => write!(f, "Unknown"),
        }
    }
}

/// Represents the solution to an optimization problem
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Solution {
    /// Status of the solve
    pub status: SolutionStatus,
    /// Variable values (empty if no solution found)
    pub variables: Vec<f64>,
    /// Objective function value (NaN if no solution found)
    pub objective_value: f64,
    /// Dual variable values (if available)
    pub dual_variables: Option<Vec<f64>>,
    /// Reduced costs (if available)
    pub reduced_costs: Option<Vec<f64>>,
    /// Solver statistics
    pub statistics: SolverStatistics,
}

/// Statistics from the solver run
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SolverStatistics {
    /// Number of iterations performed
    pub iterations: usize,
    /// Time spent solving
    pub solve_time: Duration,
    /// Number of times the basis was factorized (for simplex)
    pub basis_factorizations: Option<usize>,
    /// Number of degenerate pivots (for simplex)
    pub degenerate_pivots: Option<usize>,
    /// Final optimality gap (for approximate methods)
    pub optimality_gap: Option<f64>,
    /// Final feasibility violation
    pub feasibility_violation: Option<f64>,
    /// Additional solver-specific information
    pub additional_info: Vec<(String, String)>,
}

impl Solution {
    /// Creates a new solution with the given status
    pub fn new(status: SolutionStatus) -> Self {
        Solution {
            status,
            variables: Vec::new(),
            objective_value: f64::NAN,
            dual_variables: None,
            reduced_costs: None,
            statistics: SolverStatistics::default(),
        }
    }

    /// Creates an optimal solution
    pub fn optimal(
        variables: Vec<f64>,
        objective_value: f64,
        statistics: SolverStatistics,
    ) -> Self {
        Solution {
            status: SolutionStatus::Optimal,
            variables,
            objective_value,
            dual_variables: None,
            reduced_costs: None,
            statistics,
        }
    }

    /// Creates an infeasible solution
    pub fn infeasible(statistics: SolverStatistics) -> Self {
        Solution {
            status: SolutionStatus::Infeasible,
            variables: Vec::new(),
            objective_value: f64::NAN,
            dual_variables: None,
            reduced_costs: None,
            statistics,
        }
    }

    /// Creates an unbounded solution
    pub fn unbounded(statistics: SolverStatistics) -> Self {
        Solution {
            status: SolutionStatus::Unbounded,
            variables: Vec::new(),
            objective_value: if statistics.additional_info.iter()
                .any(|(k, _)| k == "maximize") { f64::INFINITY } else { f64::NEG_INFINITY },
            dual_variables: None,
            reduced_costs: None,
            statistics,
        }
    }

    /// Adds dual information to the solution
    pub fn with_dual_info(
        mut self,
        dual_variables: Vec<f64>,
        reduced_costs: Vec<f64>,
    ) -> Self {
        self.dual_variables = Some(dual_variables);
        self.reduced_costs = Some(reduced_costs);
        self
    }

    /// Checks if the solution is optimal
    pub fn is_optimal(&self) -> bool {
        self.status == SolutionStatus::Optimal
    }

    /// Checks if the solution is feasible
    pub fn is_feasible(&self) -> bool {
        matches!(self.status, SolutionStatus::Optimal | SolutionStatus::Suboptimal)
    }

    /// Checks if the problem is infeasible
    pub fn is_infeasible(&self) -> bool {
        self.status == SolutionStatus::Infeasible
    }

    /// Checks if the problem is unbounded
    pub fn is_unbounded(&self) -> bool {
        self.status == SolutionStatus::Unbounded
    }

    /// Gets the variable value by index
    pub fn get_variable(&self, index: usize) -> Option<f64> {
        self.variables.get(index).copied()
    }

    /// Gets the dual variable value by index
    pub fn get_dual_variable(&self, index: usize) -> Option<f64> {
        self.dual_variables.as_ref().and_then(|duals| duals.get(index).copied())
    }

    /// Gets the reduced cost by variable index
    pub fn get_reduced_cost(&self, index: usize) -> Option<f64> {
        self.reduced_costs.as_ref().and_then(|costs| costs.get(index).copied())
    }
}

impl Default for SolverStatistics {
    fn default() -> Self {
        SolverStatistics {
            iterations: 0,
            solve_time: Duration::new(0, 0),
            basis_factorizations: None,
            degenerate_pivots: None,
            optimality_gap: None,
            feasibility_violation: None,
            additional_info: Vec::new(),
        }
    }
}

impl SolverStatistics {
    /// Creates new solver statistics
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds additional information
    pub fn with_info(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.additional_info.push((key.into(), value.into()));
        self
    }

    /// Sets the number of iterations
    pub fn with_iterations(mut self, iterations: usize) -> Self {
        self.iterations = iterations;
        self
    }

    /// Sets the solve time
    pub fn with_solve_time(mut self, solve_time: Duration) -> Self {
        self.solve_time = solve_time;
        self
    }

    /// Adds simplex-specific statistics
    pub fn with_simplex_info(
        mut self,
        basis_factorizations: usize,
        degenerate_pivots: usize,
    ) -> Self {
        self.basis_factorizations = Some(basis_factorizations);
        self.degenerate_pivots = Some(degenerate_pivots);
        self
    }

    /// Gets additional information by key
    pub fn get_info(&self, key: &str) -> Option<&str> {
        self.additional_info.iter()
            .find(|(k, _)| k == key)
            .map(|(_, v)| v.as_str())
    }
}

impl fmt::Display for Solution {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "Solution Status: {}", self.status)?;
        
        match self.status {
            SolutionStatus::Optimal | SolutionStatus::Suboptimal => {
                writeln!(f, "Objective Value: {:.6}", self.objective_value)?;
                writeln!(f, "Variables:")?;
                for (i, &value) in self.variables.iter().enumerate() {
                    writeln!(f, "  x{}: {:.6}", i, value)?;
                }
                
                if let Some(ref duals) = self.dual_variables {
                    writeln!(f, "Dual Variables:")?;
                    for (i, &dual) in duals.iter().enumerate() {
                        writeln!(f, "  π{}: {:.6}", i, dual)?;
                    }
                }
                
                if let Some(ref costs) = self.reduced_costs {
                    writeln!(f, "Reduced Costs:")?;
                    for (i, &cost) in costs.iter().enumerate() {
                        writeln!(f, "  c̄{}: {:.6}", i, cost)?;
                    }
                }
            }
            _ => {
                writeln!(f, "No solution available")?;
            }
        }

        writeln!(f, "Statistics:")?;
        writeln!(f, "  Iterations: {}", self.statistics.iterations)?;
        writeln!(f, "  Solve Time: {:?}", self.statistics.solve_time)?;
        
        if let Some(factorizations) = self.statistics.basis_factorizations {
            writeln!(f, "  Basis Factorizations: {}", factorizations)?;
        }
        
        if let Some(gap) = self.statistics.optimality_gap {
            writeln!(f, "  Optimality Gap: {:.2e}", gap)?;
        }

        for (key, value) in &self.statistics.additional_info {
            writeln!(f, "  {}: {}", key, value)?;
        }

        Ok(())
    }
}

/// Result type for solver operations
pub type SolverResult = Result<Solution, OptimizationError>;

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

    #[test]
    fn test_solution_creation() {
        let stats = SolverStatistics::new()
            .with_iterations(10)
            .with_solve_time(Duration::from_millis(100));

        let solution = Solution::optimal(vec![1.0, 2.0], 5.0, stats);
        
        assert!(solution.is_optimal());
        assert!(solution.is_feasible());
        assert!(!solution.is_infeasible());
        assert_eq!(solution.get_variable(0), Some(1.0));
        assert_eq!(solution.get_variable(1), Some(2.0));
        assert_eq!(solution.get_variable(2), None);
    }

    #[test]
    fn test_solver_statistics() {
        let stats = SolverStatistics::new()
            .with_info("method", "simplex")
            .with_info("phase", "2")
            .with_simplex_info(5, 2);

        assert_eq!(stats.get_info("method"), Some("simplex"));
        assert_eq!(stats.get_info("unknown"), None);
        assert_eq!(stats.basis_factorizations, Some(5));
        assert_eq!(stats.degenerate_pivots, Some(2));
    }

    #[test]
    fn test_solution_status_display() {
        assert_eq!(SolutionStatus::Optimal.to_string(), "Optimal");
        assert_eq!(SolutionStatus::Infeasible.to_string(), "Infeasible");
        assert_eq!(SolutionStatus::Unbounded.to_string(), "Unbounded");
    }
}