use crate::eval::environment::Environment;
use crate::eval::evaluator::eval;
use crate::value::Value;
use hamelin_lib::tree::ast::identifier::SimpleIdentifier;
use hamelin_lib::tree::typed_ast::environment::TypeEnvironment;

use hamelin_lib::tree::builder::{array, field_ref, index, ExpressionBuilder};
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::tree::typed_ast::expression::TypedExpressionKind;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::INT;
use std::sync::Arc;

use super::test_helpers::{setup_environment, TestContext};

#[test]
fn test_eval_array_literal() {
    let env = setup_environment();

    let expr = type_check_expression(
        array().element(1).element(2).element(3).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;

    let result = eval(&expr, &env).unwrap();
    match result {
        Value::Array(arr) => {
            assert_eq!(arr.len(), 3);
            assert_eq!(arr[0], Value::Int(1));
            assert_eq!(arr[1], Value::Int(2));
            assert_eq!(arr[2], Value::Int(3));
        }
        _ => panic!("Expected Array value"),
    }
}

#[test]
fn test_eval_empty_array_literal() {
    let env = setup_environment();

    let expr = type_check_expression(array().build(), ExpressionTypeCheckOptions::default()).output;

    let result = eval(&expr, &env).unwrap();
    match result {
        Value::Array(arr) => {
            assert_eq!(arr.len(), 0);
        }
        _ => panic!("Expected Array value"),
    }
}

#[test]
fn test_eval_mixed_array_types() {
    // Test array with mixed types - type system should prevent this and create error expression
    let expr = type_check_expression(
        array().element(42).element("mixed").element(true).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;

    // The expression should be an error expression since arrays can't have mixed types
    assert!(matches!(expr.kind, TypedExpressionKind::Error(_)));
}

#[test]
fn test_eval_nested_array() {
    let env = setup_environment();

    // Create nested arrays
    let inner_array = array().element(1).element(2);
    let outer_array = array().element(inner_array);
    let expr =
        type_check_expression(outer_array.build(), ExpressionTypeCheckOptions::default()).output;

    let result = eval(&expr, &env).unwrap();
    match result {
        Value::Array(arr) => {
            assert_eq!(arr.len(), 1);
            match &arr[0] {
                Value::Array(inner) => {
                    assert_eq!(inner.len(), 2);
                    assert_eq!(inner[0], Value::Int(1));
                    assert_eq!(inner[1], Value::Int(2));
                }
                _ => panic!("Expected nested Array value"),
            }
        }
        _ => panic!("Expected Array value"),
    }
}

#[test]
fn test_eval_array_with_variables() {
    let mut env = Environment::new();

    // Bind some variables in eval environment
    env.bind(SimpleIdentifier::new("x"), Value::Int(10));
    env.bind(SimpleIdentifier::new("y"), Value::Int(20)); // Same type as x

    // Setup translation environment with bindings
    let mut trans_env = TypeEnvironment::default();
    trans_env.bind_str("x", INT);
    trans_env.bind_str("y", INT);

    // Create array with variables [x, 5, y, 42]
    let expr = array()
        .element(field_ref("x"))
        .element(5)
        .element(field_ref("y"))
        .element(42)
        .build();
    let expr = type_check_expression(
        expr,
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(trans_env))
            .build(),
    )
    .output;

    let result = eval(&expr, &env).unwrap();
    assert_eq!(
        result,
        Value::Array(vec![
            Value::Int(10),
            Value::Int(5),
            Value::Int(20),
            Value::Int(42)
        ])
    );
}

#[test]
fn test_eval_array_index_access() {
    let ctx = TestContext::default();

    // Helper to build test array
    let make_arr = || array().element(10).element(20).element(30).element(40);

    // Access array elements by index
    let expr = index(make_arr(), 0);
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Int(10));

    let expr = index(make_arr(), 2);
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Int(30));

    let expr = index(make_arr(), 3);
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Int(40));
}

#[test]
fn test_eval_index_access_errors() {
    let ctx = TestContext::default();

    // Helper to build test array
    let make_arr = || array().element(1).element(2);

    // Out of bounds index
    let expr = index(make_arr(), 5);
    let result = ctx.try_eval_expr(&expr);
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.contains("out of bounds"));

    // Negative index should be an error
    let expr = index(make_arr(), -1);
    let result = ctx.try_eval_expr(&expr);
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.contains("out of bounds"));
}

#[test]
fn test_eval_single_element_array() {
    let ctx = TestContext::new();

    let expr = array().element(42);
    let result = ctx.eval_expr(&expr);

    assert_eq!(result, Value::Array(vec![Value::Int(42)]));
}

#[test]
fn test_eval_array_of_strings() {
    let ctx = TestContext::new();

    let expr = array().element("first").element("second").element("third");
    let result = ctx.eval_expr(&expr);

    assert_eq!(
        result,
        Value::Array(vec![
            Value::String("first".to_string()),
            Value::String("second".to_string()),
            Value::String("third".to_string())
        ])
    );
}

#[test]
fn test_eval_array_of_booleans() {
    let ctx = TestContext::new();

    let expr = array().element(true).element(false).element(true);
    let result = ctx.eval_expr(&expr);

    assert_eq!(
        result,
        Value::Array(vec![
            Value::Boolean(true),
            Value::Boolean(false),
            Value::Boolean(true)
        ])
    );
}

#[test]
fn test_eval_array_index_first_element() {
    let ctx = TestContext::new();

    let arr = array().element("zero").element("one").element("two");
    let expr = index(arr, 0);
    let result = ctx.eval_expr(&expr);

    assert_eq!(result, Value::String("zero".to_string()));
}

#[test]
fn test_eval_array_index_last_element() {
    let ctx = TestContext::new();

    let arr = array().element(100).element(200).element(300);
    let expr = index(arr, 2); // Last element (0-indexed)
    let result = ctx.eval_expr(&expr);

    assert_eq!(result, Value::Int(300));
}
