use crate::eval::evaluator::eval;
use crate::value::Value;
use hamelin_lib::tree::ast::identifier::SimpleIdentifier;
use hamelin_lib::tree::builder::{add, array, field_ref, tuple, ExpressionBuilder};
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::{INT, STRING};
use std::sync::Arc;

use super::test_helpers::TestContext;

#[test]
fn test_eval_with_variables() {
    let mut ctx = TestContext::default();
    ctx.set("x", Value::Int(10), INT);
    ctx.set("y", Value::Int(20), INT);

    // Test: x + y = 30
    let expr = type_check_expression(
        add(field_ref("x"), field_ref("y")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

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

#[test]
fn test_eval_field_reference() {
    let mut ctx = TestContext::default();
    ctx.set("x", Value::Int(100), INT);

    let expr = type_check_expression(
        field_ref("x").build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Int(100));
}

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

    // Building an expression with an undefined variable should result in an error expression
    let expr = type_check_expression(
        field_ref("undefined").build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    // The expression should be an error expression since the variable is not in the translation context
    assert!(matches!(
        expr.kind,
        hamelin_lib::tree::typed_ast::expression::TypedExpressionKind::Error(_)
    ));
}

#[test]
fn test_eval_array_with_variables() {
    let mut ctx = TestContext::default();
    ctx.set("x", Value::Int(10), INT);
    ctx.set("y", Value::Int(20), INT);

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

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

#[test]
fn test_eval_tuple_with_variables() {
    let mut ctx = TestContext::default();
    ctx.set("name", Value::String("Alice".to_string()), STRING);
    ctx.set("age", Value::Int(30), INT);

    // Create tuple with variables (name, age, "static")
    let expr = type_check_expression(
        tuple()
            .element(field_ref("name"))
            .element(field_ref("age"))
            .element("static")
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(
        result,
        Value::Tuple(vec![
            Value::String("Alice".to_string()),
            Value::Int(30),
            Value::String("static".to_string())
        ])
    );
}

#[test]
fn test_eval_struct_with_variables() {
    use hamelin_lib::tree::builder::struct_literal;

    let mut ctx = TestContext::default();
    ctx.set("x", Value::Int(42), INT);
    ctx.set("y", Value::String("test".to_string()), STRING);

    // Create struct using variables
    let expr = struct_literal()
        .field("field_x", field_ref("x"))
        .field("field_y", field_ref("y"))
        .field("constant", 100);

    let typed = type_check_expression(
        expr.build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&typed, &ctx.env).unwrap();

    let mut expected_fields = ordermap::OrderMap::new();
    expected_fields.insert(
        "field_x".parse::<SimpleIdentifier>().unwrap(),
        Value::Int(42),
    );
    expected_fields.insert(
        "field_y".parse::<SimpleIdentifier>().unwrap(),
        Value::String("test".to_string()),
    );
    expected_fields.insert(
        "constant".parse::<SimpleIdentifier>().unwrap(),
        Value::Int(100),
    );

    assert_eq!(result, Value::Struct(expected_fields));
}

#[test]
fn test_eval_variable_with_different_types() {
    let mut ctx = TestContext::default();
    ctx.set("int_var", Value::Int(123), INT);
    ctx.set(
        "string_var",
        Value::String("hello world".to_string()),
        STRING,
    );

    // Test int variable
    let expr = type_check_expression(
        field_ref("int_var").build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Int(123));

    // Test string variable
    let expr = type_check_expression(
        field_ref("string_var").build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::String("hello world".to_string()));
}

#[test]
fn test_eval_null_variable() {
    let mut ctx = TestContext::default();
    ctx.set("nullable_var", Value::Null, INT);

    let expr = type_check_expression(
        field_ref("nullable_var").build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Null);
    assert!(result.is_null());
}
