use crate::errors::{OptimizationError, Result};
use crate::problem::Problem;
use crate::solvers::SolverResult;
use std::time::Duration;

/// Configuration options for solvers
#[derive(Debug, Clone)]
pub struct SolverConfig {
    /// Maximum number of iterations
    pub max_iterations: usize,
    /// Maximum solve time
    pub max_time: Duration,
    /// Numerical tolerance for optimality
    pub optimality_tolerance: f64,
    /// Numerical tolerance for feasibility
    pub feasibility_tolerance: f64,
    /// Whether to enable verbose output
    pub verbose: bool,
    /// Presolve options
    pub presolve: bool,
    /// Scaling options
    pub scaling: bool,
    /// Additional solver-specific options
    pub advanced_options: Vec<(String, String)>,
}

impl Default for SolverConfig {
    fn default() -> Self {
        SolverConfig {
            max_iterations: 10_000,
            max_time: Duration::from_secs(300), // 5 minutes
            optimality_tolerance: 1e-8,
            feasibility_tolerance: 1e-8,
            verbose: false,
            presolve: true,
            scaling: true,
            advanced_options: Vec::new(),
        }
    }
}

impl SolverConfig {
    /// Creates a new solver configuration with default values
    pub fn new() -> Self {
        Self::default()
    }

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

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

    /// Sets the optimality tolerance
    pub fn with_optimality_tolerance(mut self, tolerance: f64) -> Self {
        self.optimality_tolerance = tolerance;
        self
    }

    /// Sets the feasibility tolerance
    pub fn with_feasibility_tolerance(mut self, tolerance: f64) -> Self {
        self.feasibility_tolerance = tolerance;
        self
    }

    /// Enables verbose output
    pub fn verbose(mut self) -> Self {
        self.verbose = true;
        self
    }

    /// Disables presolve
    pub fn without_presolve(mut self) -> Self {
        self.presolve = false;
        self
    }

    /// Disables scaling
    pub fn without_scaling(mut self) -> Self {
        self.scaling = false;
        self
    }

    /// Adds an advanced option
    pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.advanced_options.push((key.into(), value.into()));
        self
    }

    /// Gets an advanced option value
    pub fn get_option(&self, key: &str) -> Option<&str> {
        self.advanced_options
            .iter()
            .find(|(k, _)| k == key)
            .map(|(_, v)| v.as_str())
    }
}

/// Trait for optimization solvers
pub trait Solver: Send + Sync {
    /// Gets the name of the solver
    fn name(&self) -> &str;

    /// Solves the optimization problem
    fn solve(&mut self, problem: &Problem) -> SolverResult;

    /// Solves the optimization problem with custom configuration
    fn solve_with_config(&mut self, problem: &Problem, _config: &SolverConfig) -> SolverResult {
        // Default implementation ignores config and calls solve
        // Individual solvers should override this to use the configuration
        if _config.verbose {
            eprintln!("Warning: {} doesn't support custom configuration, using defaults", self.name());
        }
        self.solve(problem)
    }

    /// Checks if the solver can handle the given problem type
    fn can_solve(&self, problem: &Problem) -> bool;

    /// Gets solver-specific configuration options
    fn default_config(&self) -> SolverConfig {
        SolverConfig::default()
    }

    /// Validates problem before solving (optional preprocessing)
    fn validate_problem(&self, problem: &Problem) -> Result<()> {
        problem.validate().map_err(OptimizationError::from)
    }
}

/// Factory for creating solvers
pub struct SolverFactory;

impl SolverFactory {
    /// Creates a simplex solver
    pub fn simplex() -> Box<dyn Solver> {
        Box::new(crate::solvers::SimplexSolver::new())
    }

    /// Creates the best available solver for the given problem
    pub fn best_for_problem(problem: &Problem) -> Result<Box<dyn Solver>> {
        let stats = problem.statistics();

        // For now, we only have simplex
        if stats.is_integer {
            return Err(OptimizationError::SolverError {
                reason: "Integer programming not yet supported".to_string(),
            });
        }

        Ok(Self::simplex())
    }

    /// Lists all available solvers
    pub fn available_solvers() -> Vec<&'static str> {
        vec!["simplex"]
    }

    /// Creates a solver by name
    pub fn by_name(name: &str) -> Result<Box<dyn Solver>> {
        match name.to_lowercase().as_str() {
            "simplex" => Ok(Self::simplex()),
            _ => Err(OptimizationError::SolverError {
                reason: format!("Unknown solver: {}", name),
            }),
        }
    }
}

/// Convenient solve function that automatically selects the best solver
pub fn solve(problem: &Problem) -> SolverResult {
    let mut solver =
        SolverFactory::best_for_problem(problem).map_err(|e| OptimizationError::SolverError {
            reason: e.to_string(),
        })?;
    solver.solve(problem)
}

/// Solve with custom configuration
pub fn solve_with_config(problem: &Problem, config: &SolverConfig) -> SolverResult {
    let mut solver =
        SolverFactory::best_for_problem(problem).map_err(|e| OptimizationError::SolverError {
            reason: e.to_string(),
        })?;
    solver.solve_with_config(problem, config)
}

impl From<crate::errors::ProblemError> for OptimizationError {
    fn from(err: crate::errors::ProblemError) -> Self {
        OptimizationError::InvalidProblem {
            reason: err.to_string(),
        }
    }
}

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

    #[test]
    fn test_solver_config() {
        let config = SolverConfig::new()
            .with_max_iterations(1000)
            .with_optimality_tolerance(1e-6)
            .verbose()
            .with_option("method", "dual");

        assert_eq!(config.max_iterations, 1000);
        assert_eq!(config.optimality_tolerance, 1e-6);
        assert!(config.verbose);
        assert_eq!(config.get_option("method"), Some("dual"));
        assert_eq!(config.get_option("unknown"), None);
    }

    #[test]
    fn test_solver_factory() {
        let solvers = SolverFactory::available_solvers();
        assert!(!solvers.is_empty());
        assert!(solvers.contains(&"simplex"));

        let solver = SolverFactory::by_name("simplex").unwrap();
        assert_eq!(solver.name(), "Simplex");

        let result = SolverFactory::by_name("unknown");
        assert!(result.is_err());
    }

    fn create_test_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", 1.0), ("y", 1.0)], ObjectiveType::Maximize)
            .unwrap()
            .build()
            .unwrap()
    }

    #[test]
    fn test_best_solver_selection() {
        let problem = create_test_problem();
        let solver = SolverFactory::best_for_problem(&problem).unwrap();
        assert_eq!(solver.name(), "Simplex");
    }
}
