//! Tests for struct expression handling
//!
//! Tests both splittable (struct literals) and non-splittable (array access, function calls)
//! struct expressions, and their interaction with narrow field bindings.

use crate::err::TranslationErrors;
use crate::tree::ast::query::Query;
use crate::tree::ast::{IntoTyped, ParseWithErrors, TypeCheckExecutor};
use crate::tree::tests::shared::catalog_with_tables;
use crate::types::array::Array;
use crate::types::struct_type::Struct;
use crate::types::{Type, INT, STRING};
use rstest::rstest;

/// Test cases for struct expression handling
#[rstest]
// Splittable: struct literal creates separate field bindings
#[case::struct_literal_creates_separate_bindings(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("name", STRING),
            ("ip", STRING),
        ]
    )],
    r#"
FROM events
| LET host_info = {name: name, ip: ip}
"#,
    Struct::default()
        .with_str("host_info", Struct::default()
            .with_str("name", STRING)
            .with_str("ip", STRING)
            .into())
        .with_str("id", INT)
        .with_str("name", STRING)
        .with_str("ip", STRING)
)]
// Splittable: struct literal with nested structs
#[case::nested_struct_literal(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("name", STRING),
            ("ip", STRING),
        ]
    )],
    r#"
FROM events
| LET data = {host: {name: name, ip: ip}}
"#,
    Struct::default()
        .with_str("data", Struct::default()
            .with_str("host", Struct::default()
                .with_str("name", STRING)
                .with_str("ip", STRING)
                .into())
            .into())
        .with_str("id", INT)
        .with_str("name", STRING)
        .with_str("ip", STRING)
)]
// Splittable: rebinding a field after struct literal should work
#[case::rebind_field_after_struct_literal(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("name", STRING),
            ("ip", STRING),
        ]
    )],
    r#"
FROM events
| LET host_info = {name: name, ip: ip, status: "active"}
| LET host_info.status = "inactive"
"#,
    Struct::default()
        .with_str("host_info", Struct::default()
            .with_str("status", STRING)  // Modified field comes first
            .with_str("name", STRING)
            .with_str("ip", STRING)
            .into())
        .with_str("id", INT)
        .with_str("name", STRING)
        .with_str("ip", STRING)
)]
// Non-splittable: array element access returns terminal struct
#[case::array_element_access_terminal_struct(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("host", Type::Struct(
                        Struct::default()
                            .with_str("name", STRING)
                            .with_str("ip", STRING)
                    ))
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first_data = all_data[0]
"#,
    Struct::default()
        .with_str("first_data", Type::Struct(
            Struct::default()
                .with_str("host", Type::Struct(
                    Struct::default()
                        .with_str("name", STRING)
                        .with_str("ip", STRING)
                ))
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("host", Type::Struct(
                    Struct::default()
                        .with_str("name", STRING)
                        .with_str("ip", STRING)
                ))
        )).into())
)]
// Non-splittable: rebinding a field after array access should merge
#[case::rebind_field_after_array_access(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("host", Type::Struct(
                        Struct::default()
                            .with_str("name", STRING)
                            .with_str("ip", STRING)
                    ))
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first_data = all_data[0]
| LET first_data.host = first_data.host.name
"#,
    Struct::default()
        .with_str("first_data", Type::Struct(
            Struct::default()
                .with_str("host", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("host", Type::Struct(
                    Struct::default()
                        .with_str("name", STRING)
                        .with_str("ip", STRING)
                ))
        )).into())
)]
// Non-splittable: column reference with struct type
#[case::column_reference_struct_type(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("metadata", Type::Struct(
                Struct::default()
                    .with_str("host", STRING)
                    .with_str("ip", STRING)
            ))
        ]
    )],
    r#"
FROM events
| LET meta = metadata
"#,
    Struct::default()
        .with_str("meta", Type::Struct(
            Struct::default()
                .with_str("host", STRING)
                .with_str("ip", STRING)
        ))
        .with_str("id", INT)
        .with_str("metadata", Type::Struct(
            Struct::default()
                .with_str("host", STRING)
                .with_str("ip", STRING)
        ))
)]
// Non-splittable: rebinding nested field after column reference
#[case::rebind_nested_field_after_column_reference(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("metadata", Type::Struct(
                Struct::default()
                    .with_str("host", Type::Struct(
                        Struct::default()
                            .with_str("name", STRING)
                            .with_str("ip", STRING)
                    ))
            ))
        ]
    )],
    r#"
FROM events
| LET meta = metadata
| LET meta.host = meta.host.name
"#,
    Struct::default()
        .with_str("meta", Type::Struct(
            Struct::default()
                .with_str("host", STRING)
        ))
        .with_str("id", INT)
        .with_str("metadata", Type::Struct(
            Struct::default()
                .with_str("host", Type::Struct(
                    Struct::default()
                        .with_str("name", STRING)
                        .with_str("ip", STRING)
                ))
        ))
)]
// Mixed: multiple levels of nesting with overrides
#[case::multiple_nesting_levels_with_overrides(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("level1", Type::Struct(
                        Struct::default()
                            .with_str("level2", Type::Struct(
                                Struct::default()
                                    .with_str("value", STRING)
                                    .with_str("extra", INT)
                            ))
                    ))
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| LET first.level1.level2 = first.level1.level2.value
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("level1", Type::Struct(
                    Struct::default()
                        .with_str("level2", STRING)
                ))
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("level1", Type::Struct(
                    Struct::default()
                        .with_str("level2", Type::Struct(
                            Struct::default()
                                .with_str("value", STRING)
                                .with_str("extra", INT)
                        ))
                ))
        )).into())
)]
// Test field order preservation when desugaring with many fields
#[case::field_order_preservation_many_fields(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
                    .with_str("d", Type::Struct(
                        Struct::default()
                            .with_str("x", STRING)
                            .with_str("y", STRING)
                    ))
                    .with_str("e", STRING)
                    .with_str("f", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| LET first.d = first.d.x
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("d", STRING)  // Modified field comes first
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
                .with_str("e", STRING)
                .with_str("f", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
                .with_str("d", Type::Struct(
                    Struct::default()
                        .with_str("x", STRING)
                        .with_str("y", STRING)
                ))
                .with_str("e", STRING)
                .with_str("f", STRING)
        )).into())
)]
// Test DROP on non-splittable struct expression
#[case::drop_field_from_struct_expression(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| DROP first.b
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("c", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
        )).into())
)]
// Test DROP multiple fields from non-splittable struct
#[case::drop_multiple_fields_from_struct_expression(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
                    .with_str("d", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| DROP first.a, first.c
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("b", STRING)
                .with_str("d", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
                .with_str("d", STRING)
        )).into())
)]
// Test DROP nested field from non-splittable struct
#[case::drop_nested_field_from_struct_expression(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("nested", Type::Struct(
                        Struct::default()
                            .with_str("x", STRING)
                            .with_str("y", STRING)
                    ))
                    .with_str("b", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| DROP first.nested
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("nested", Type::Struct(
                    Struct::default()
                        .with_str("x", STRING)
                        .with_str("y", STRING)
                ))
                .with_str("b", STRING)
        )).into())
)]
// Test multiple DROP commands on struct expression
#[case::multiple_drop_commands_on_struct(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
                    .with_str("d", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| DROP first.a
| DROP first.c
"#,
    Struct::default()
        .with_str("first", Type::Struct(
            Struct::default()
                .with_str("b", STRING)
                .with_str("d", STRING)
        ))
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
                .with_str("d", STRING)
        )).into())
)]
// Test DROP from struct literal (clean case)
#[case::drop_from_struct_literal(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("name", STRING),
            ("email", STRING),
        ]
    )],
    r#"
FROM events
| LET user = {name: name, email: email}
| DROP user.email
"#,
    Struct::default()
        .with_str("user", Struct::default()
            .with_str("name", STRING)
            .into())
        .with_str("id", INT)
        .with_str("name", STRING)
        .with_str("email", STRING)
)]
// Test DROP all fields of a struct - struct disappears entirely
#[case::drop_all_fields_of_struct(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| DROP first.a, first.b
"#,
    // When all fields are dropped, the parent struct disappears entirely
    Struct::default()
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
        )).into())
)]
// Test SELECT from struct expression
#[case::select_from_struct_expression(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| SELECT first.a, first.c
"#,
    Struct::default()
        .with_str("first", Struct::default()
            .with_str("a", STRING)
            .with_str("c", STRING)
            .into())
)]
// Test SELECT redefining a field in struct expression
#[case::select_redefine_struct_field(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| SELECT first.a = "new_value", first.c
"#,
    Struct::default()
        .with_str("first", Struct::default()
            .with_str("a", STRING)  // Modified value
            .with_str("c", STRING)
            .into())
)]
// Test SELECT adding a new field to struct expression
#[case::select_add_struct_field(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| SELECT first.a, first.c, first.d = "new_field"
"#,
    Struct::default()
        .with_str("first", Struct::default()
            .with_str("a", STRING)
            .with_str("c", STRING)
            .with_str("d", STRING)  // New field
            .into())
)]
// Test WINDOW adding field to struct expression
#[case::window_to_struct_expression(
    vec![(
        "events",
        vec![
            ("id", INT),
            ("data", Type::Struct(
                Struct::default()
                    .with_str("a", STRING)
                    .with_str("b", STRING)
                    .with_str("c", STRING)
            ))
        ]
    )],
    r#"
FROM events
| AGG all_data = array_agg(data)
| LET first = all_data[0]
| WINDOW first.total = count()
"#,
    Struct::default()
        .with_str("first", Struct::default()
            .with_str("total", INT)
            .with_str("a", STRING)
            .with_str("b", STRING)
            .with_str("c", STRING)
            .into())
        .with_str("all_data", Array::new(Type::Struct(
            Struct::default()
                .with_str("a", STRING)
                .with_str("b", STRING)
                .with_str("c", STRING)
        )).into())
)]
fn test_struct_expressions(
    #[case] tables: Vec<(&str, Vec<(&str, Type)>)>,
    #[case] query_str: &str,
    #[case] expected_schema: Struct,
) -> Result<(), TranslationErrors> {
    let query = Query::parse_result(query_str)?;
    let catalog = catalog_with_tables(tables);
    let typed = query.typed_with().with_provider(catalog).typed_result()?;

    let actual_schema = typed.schema();

    assert_eq!(actual_schema, expected_schema);
    Ok(())
}
