use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ockham::prelude::*;
use std::time::Duration;

// generate random LP problems for benchmarking
fn generate_random_problem(num_vars: usize, num_constraints: usize) -> Problem {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    
    // simple deterministic "random" num gen for reproducible benchmarks
    fn pseudo_random(seed: u64) -> f64 {
        let mut hasher = DefaultHasher::new();
        seed.hash(&mut hasher);
        let hash = hasher.finish();
        (hash % 10000) as f64 / 10000.0 * 10.0 - 5.0 // range [-5, 5]
    }
    
    let mut builder = ProblemBuilder::new()
        .name(&format!("Random LP {}x{}", num_vars, num_constraints));
    
    // Add variables
    for i in 0..num_vars {
        let lower = if pseudo_random(i as u64) > 0.0 { 0.0 } else { -100.0 };
        builder = builder.add_variable(&format!("x{}", i), lower, 100.0).unwrap();
    }
    
    // Add constraints
    for i in 0..num_constraints {
        let mut coeffs = Vec::new();
        for j in 0..num_vars {
            coeffs.push(pseudo_random((i * num_vars + j) as u64));
        }
        
        let rhs = pseudo_random((i + 1000) as u64) * 50.0;
        let constraint_type = match i % 3 {
            0 => ConstraintType::LessEqual,
            1 => ConstraintType::GreaterEqual, 
            _ => ConstraintType::Equal,
        };
        
        builder = builder.add_constraint(coeffs, constraint_type, rhs).unwrap();
    }
    
    // Add objective
    let mut obj_coeffs = Vec::new();
    for i in 0..num_vars {
        obj_coeffs.push(pseudo_random((i + 2000) as u64));
    }
    
    builder = builder.objective(obj_coeffs, ObjectiveType::Maximize).unwrap();
    
    builder.build().unwrap()
}

// benchmark diff problem sizes
fn bench_problem_sizes(c: &mut Criterion) {
    let mut group = c.benchmark_group("problem_sizes");
    group.measurement_time(Duration::from_secs(30));
    
    for &(vars, constraints) in &[(5, 3), (10, 8), (20, 15), (50, 30), (100, 75)] {
        let problem = generate_random_problem(vars, constraints);
        group.throughput(Throughput::Elements((vars + constraints) as u64));
        
        group.bench_with_input(
            BenchmarkId::new("simplex_solve", format!("{}v{}c", vars, constraints)),
            &problem,
            |b, problem| {
                b.iter(|| {
                    let mut solver = SimplexSolver::new();
                    // ignore solve errors for benchmarking - simplex needs fixes
                    let result = solver.solve(black_box(problem)).unwrap_or_else(|_| {
                        Solution::new(SolutionStatus::Unknown)
                    });
                    black_box(result)
                });
            },
        );
    }
    group.finish();
}

// Benchmark different pivot rules
fn bench_pivot_rules(c: &mut Criterion) {
    let mut group = c.benchmark_group("pivot_rules");
    let problem = generate_random_problem(20, 15);
    
    for rule in &[
        ("most_negative", PivotRule::MostNegative),
        ("first_negative", PivotRule::FirstNegative),
        ("steepest_edge", PivotRule::SteepestEdge),
    ] {
        group.bench_with_input(
            BenchmarkId::new("pivot_rule", rule.0),
            &problem,
            |b, problem| {
                b.iter(|| {
                    let mut solver = SimplexSolver::new().with_pivot_rule(rule.1);
                    let result = solver.solve(black_box(problem)).unwrap_or_else(|_| {
                        Solution::new(SolutionStatus::Unknown)
                    });
                    black_box(result)
                });
            },
        );
    }
    group.finish();
}

// Benchmark presolve operations
fn bench_presolve(c: &mut Criterion) {
    let mut group = c.benchmark_group("presolve");
    
    // Create a problem that benefits from presolve
    let problem = ProblemBuilder::new()
        .add_variable("x1", 0.0, 0.0).unwrap() // Fixed variable
        .add_non_negative_variable("x2").unwrap()
        .add_non_negative_variable("x3").unwrap()
        .add_non_negative_variable("x4").unwrap()
        .add_constraint(vec![1.0, 1.0, 0.0, 0.0], ConstraintType::LessEqual, 10.0).unwrap()
        .add_constraint(vec![1.0, 1.0, 0.0, 0.0], ConstraintType::LessEqual, 10.0).unwrap() // Duplicate
        .add_constraint(vec![0.0, 1.0, 1.0, 0.0], ConstraintType::LessEqual, 15.0).unwrap()
        .add_constraint(vec![0.0, 0.0, 1.0, 1.0], ConstraintType::LessEqual, 20.0).unwrap()
        .objective(vec![0.0, 3.0, 2.0, 1.0], ObjectiveType::Maximize).unwrap()
        .build()
        .unwrap();
    
    group.bench_function("presolve_apply", |b| {
        b.iter(|| {
            let presolve = Presolve::new();
            let result = presolve.apply(black_box(&problem));
            black_box(result)
        });
    });
    
    // Compare solve times with and without presolve
    group.bench_function("solve_without_presolve", |b| {
        b.iter(|| {
            let mut solver = SimplexSolver::new();
            let result = solver.solve(black_box(&problem)).unwrap_or_else(|_| {
                Solution::new(SolutionStatus::Unknown)
            });
            black_box(result)
        });
    });
    
    group.bench_function("solve_with_presolve", |b| {
        b.iter(|| {
            let presolve = Presolve::new();
            let presolve_result = presolve.apply(&problem).unwrap();
            let mut solver = SimplexSolver::new(); 
            let result = solver.solve(black_box(&presolve_result.problem)).unwrap_or_else(|_| {
                Solution::new(SolutionStatus::Unknown)
            });
            black_box(result)
        });
    });
    
    group.finish();
}

// Benchmark scaling operations
fn bench_scaling(c: &mut Criterion) {
    let mut group = c.benchmark_group("scaling");
    
    // Create a poorly scaled problem
    let problem = ProblemBuilder::new()
        .add_non_negative_variable("x1").unwrap()
        .add_non_negative_variable("x2").unwrap()
        .add_constraint_by_name(
            &[("x1", 1e6), ("x2", 1e-6)],
            ConstraintType::LessEqual,
            1.0,
        ).unwrap()
        .add_constraint_by_name(
            &[("x1", 1e-3), ("x2", 1e3)], 
            ConstraintType::LessEqual,
            1.0,
        ).unwrap()
        .objective_by_name(
            &[("x1", 1e8), ("x2", 1e-8)],
            ObjectiveType::Maximize,
        ).unwrap()
        .build()
        .unwrap();
    
    for method in &[
        ("none", ScalingMethod::None),
        ("equilibration", ScalingMethod::Equilibration),
        ("geometric", ScalingMethod::Geometric), 
        ("max_element", ScalingMethod::MaxElement),
    ] {
        group.bench_with_input(
            BenchmarkId::new("scaling_method", method.0),
            &problem,
            |b, problem| {
                b.iter(|| {
                    let scaling = Scaling::new(method.1);
                    let result = scaling.apply(black_box(problem));
                    black_box(result)
                });
            },
        );
    }
    
    group.finish();
}

// Benchmark problem construction
fn bench_problem_construction(c: &mut Criterion) {
    let mut group = c.benchmark_group("problem_construction");
    
    group.bench_function("builder_pattern", |b| {
        b.iter(|| {
            let 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();
            black_box(problem)
        });
    });
    
    group.bench_function("direct_construction", |b| {
        b.iter(|| {
            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(),
            ];
            let objective = Objective::new(vec![3.0, 2.0], ObjectiveType::Maximize).unwrap();
            let problem = Problem::new(variables, constraints, objective).unwrap();
            black_box(problem)
        });
    });
    
    group.finish();
}

criterion_group!(
    benches,
    bench_problem_sizes,
    bench_pivot_rules, 
    bench_presolve,
    bench_scaling,
    bench_problem_construction
);
criterion_main!(benches);