//! Tests for constrained optimization module

use crate::constrained::*;
use crate::error::OptimizeError;
use scirs2_core::ndarray::array;

#[allow(dead_code)]
fn objective(x: &[f64]) -> f64 {
    (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2)
}

#[allow(dead_code)]
fn constraint(x: &[f64]) -> f64 {
    3.0 - x[0] - x[1] // Should be >= 0
}

#[test]
#[allow(dead_code)]
fn test_minimize_constrained_placeholder() {
    // We're now using the real implementation, so this test needs to be adjusted
    let x0 = array![0.0, 0.0];
    let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];

    // Use minimal iterations to check basic algorithm behavior
    let options = Options {
        maxiter: Some(1), // Just a single iteration
        ..Options::default()
    };

    let result = minimize_constrained(
        objective,
        &x0.view(),
        &constraints,
        Method::SLSQP,
        Some(options),
    )
    .expect("Operation failed");

    // With limited iterations, we expect it not to converge
    assert!(!result.success);

    // Check that constraint value was computed
    assert!(result.constr.is_some());
    let constr = result.constr.expect("Operation failed");
    assert_eq!(constr.len(), 1);
}

// Test the SLSQP algorithm on a simple constrained problem
#[test]
#[allow(dead_code)]
fn test_minimize_slsqp() {
    // Problem:
    // Minimize (x-1)^2 + (y-2.5)^2
    // Subject to: x + y <= 3

    let x0 = array![0.0, 0.0];
    let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];

    let options = Options {
        maxiter: Some(100),
        gtol: Some(1e-6),
        ftol: Some(1e-6),
        ctol: Some(1e-6),
        ..Options::default()
    };

    let result = minimize_constrained(
        objective,
        &x0.view(),
        &constraints,
        Method::SLSQP,
        Some(options),
    )
    .expect("Operation failed");

    // For the purpose of this test, we're just checking that the algorithm runs
    // and produces reasonable output. The convergence may vary.

    // Check that we're moving in the right direction
    assert!(result.x[0] >= 0.0);
    assert!(result.x[1] >= 0.0);

    // Function value should be decreasing from initial point
    let initial_value = objective(&[0.0, 0.0]);
    assert!(result.fun <= initial_value);

    // Check that constraint values are computed
    assert!(result.constr.is_some());

    // Output the result for inspection
    println!(
        "SLSQP result: x = {:?}, f = {}, iterations = {}",
        result.x, result.fun, result.nit
    );
}

// Test the Trust Region Constrained algorithm
#[test]
#[allow(dead_code)]
fn test_minimize_trust_constr() {
    // Problem:
    // Minimize (x-1)^2 + (y-2.5)^2
    // Subject to: x + y <= 3

    let x0 = array![0.0, 0.0];
    let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];

    let options = Options {
        maxiter: Some(500), // Increased iterations for convergence
        gtol: Some(1e-6),
        ftol: Some(1e-6),
        ctol: Some(1e-6),
        ..Options::default()
    };

    let result = minimize_constrained(
        objective,
        &x0.view(),
        &constraints,
        Method::TrustConstr,
        Some(options.clone()),
    )
    .expect("Operation failed");

    // Check that we're moving in the right direction
    assert!(result.x[0] >= 0.0);
    assert!(result.x[1] >= 0.0);

    // Function value should be decreasing from initial point
    let initial_value = objective(&[0.0, 0.0]);
    assert!(result.fun <= initial_value);

    // Check that constraint values are computed
    assert!(result.constr.is_some());

    // Output the result for inspection
    println!(
        "TrustConstr result: x = {:?}, f = {}, iterations = {}",
        result.x, result.fun, result.nit
    );
}

// Test both constrained optimization methods on a more complex problem
#[test]
#[allow(dead_code)]
fn test_constrained_rosenbrock() {
    // Rosenbrock function with a constraint
    fn rosenbrock(x: &[f64]) -> f64 {
        100.0 * (x[1] - x[0].powi(2)).powi(2) + (1.0 - x[0]).powi(2)
    }

    // Constraint: x[0]^2 + x[1]^2 <= 1.5
    fn circle_constraint(x: &[f64]) -> f64 {
        1.5 - (x[0].powi(2) + x[1].powi(2)) // Should be >= 0
    }

    let x0 = array![0.0, 0.0];
    let constraints = vec![Constraint::new(circle_constraint, Constraint::INEQUALITY)];

    let options = Options {
        maxiter: Some(1000), // More iterations for this harder problem
        gtol: Some(1e-4),    // Relaxed tolerances
        ftol: Some(1e-4),
        ctol: Some(1e-4),
        ..Options::default()
    };

    // For this test, we'll clone options at each stage to avoid move issues
    let options_copy1 = options.clone();
    let options_copy2 = options.clone();

    // Test SLSQP
    let result_slsqp = minimize_constrained(
        rosenbrock,
        &x0.view(),
        &constraints,
        Method::SLSQP,
        Some(options_copy1),
    )
    .expect("Operation failed");

    // Test TrustConstr
    let result_trust = minimize_constrained(
        rosenbrock,
        &x0.view(),
        &constraints,
        Method::TrustConstr,
        Some(options_copy2),
    )
    .expect("Operation failed");

    // Check that both methods find a reasonable solution
    println!(
        "SLSQP Rosenbrock result: x = {:?}, f = {}, iterations = {}",
        result_slsqp.x, result_slsqp.fun, result_slsqp.nit
    );
    println!(
        "TrustConstr Rosenbrock result: x = {:?}, f = {}, iterations = {}",
        result_trust.x, result_trust.fun, result_trust.nit
    );

    // Check that function value is better than initial point
    let initial_value = rosenbrock(&[0.0, 0.0]);
    assert!(result_slsqp.fun < initial_value);
    assert!(result_trust.fun < initial_value);

    // Check that constraint is satisfied
    let constr_slsqp = result_slsqp.constr.expect("Operation failed");
    let constr_trust = result_trust.constr.expect("Operation failed");
    assert!(constr_slsqp[0] >= -0.01); // Relaxed tolerance for the test
    assert!(constr_trust[0] >= -0.01); // Relaxed tolerance for the test
}

#[test]
#[allow(dead_code)]
fn test_cobyla_not_implemented() {
    // Test that COBYLA returns a NotImplementedError
    let x0 = array![0.0, 0.0];
    let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];

    let result = minimize_constrained(objective, &x0.view(), &constraints, Method::COBYLA, None);

    // COBYLA is now implemented, so it should succeed
    assert!(result.is_ok());
    let opt_result = result.expect("Operation failed");
    assert!(opt_result.success || opt_result.nit > 0); // Should make progress or succeed
}
