//! Integration tests for SeaORM 2.0 features
//!
//! This file tests the following features:
//! - Strongly-typed columns
//! - Nested ActiveModel (cascade save)
//! - Self-referencing relations
//! - Linked partial select
//! - Join result consolidation

use tideorm::columns::{
    Column, ColumnEq, ColumnIn, ColumnLike, ColumnNullable, ColumnOperator, ColumnOrd,
};

// =============================================================================
// STRONGLY-TYPED COLUMNS TESTS
// =============================================================================

mod typed_columns {
    use super::*;

    /// Define typed columns for testing
    mod user_cols {
        use tideorm::columns::Column;

        pub const ID: Column<i64> = Column::new("id");
        pub const NAME: Column<String> = Column::new("name");
        pub const AGE: Column<Option<i32>> = Column::new("age");
        pub const SCORE: Column<f64> = Column::new("score");
        pub const ACTIVE: Column<bool> = Column::new("active");
    }

    #[test]
    fn test_column_creation() {
        let col: Column<i64> = Column::new("id");
        assert_eq!(col.name(), "id");

        let col: Column<String> = Column::new("name");
        assert_eq!(col.name(), "name");
    }

    #[test]
    fn test_integer_column_eq() {
        let cond = user_cols::ID.eq(42i64);
        assert_eq!(cond.column, "id");
        assert_eq!(cond.operator, ColumnOperator::Eq);
        assert_eq!(cond.value, serde_json::json!(42));
    }

    #[test]
    fn test_integer_column_ne() {
        let cond = user_cols::ID.ne(99i64);
        assert_eq!(cond.column, "id");
        assert_eq!(cond.operator, ColumnOperator::NotEq);
        assert_eq!(cond.value, serde_json::json!(99));
    }

    #[test]
    fn test_integer_column_comparisons() {
        let gt = user_cols::ID.gt(10i64);
        assert_eq!(gt.operator, ColumnOperator::Gt);

        let gte = user_cols::ID.gte(10i64);
        assert_eq!(gte.operator, ColumnOperator::Gte);

        let lt = user_cols::ID.lt(100i64);
        assert_eq!(lt.operator, ColumnOperator::Lt);

        let lte = user_cols::ID.lte(100i64);
        assert_eq!(lte.operator, ColumnOperator::Lte);
    }

    #[test]
    fn test_integer_column_between() {
        let cond = user_cols::ID.between(10i64, 100i64);
        assert_eq!(cond.column, "id");
        assert_eq!(cond.operator, ColumnOperator::Between);
        assert_eq!(cond.value, serde_json::json!([10, 100]));
    }

    #[test]
    fn test_integer_column_in() {
        let cond = user_cols::ID.is_in(vec![1i64, 2, 3, 4, 5]);
        assert_eq!(cond.column, "id");
        assert_eq!(cond.operator, ColumnOperator::In);
        assert_eq!(cond.value, serde_json::json!([1, 2, 3, 4, 5]));
    }

    #[test]
    fn test_integer_column_not_in() {
        let cond = user_cols::ID.not_in(vec![1i64, 2]);
        assert_eq!(cond.column, "id");
        assert_eq!(cond.operator, ColumnOperator::NotIn);
    }

    #[test]
    fn test_string_column_eq() {
        let cond = user_cols::NAME.eq("Alice");
        assert_eq!(cond.column, "name");
        assert_eq!(cond.operator, ColumnOperator::Eq);
        assert_eq!(cond.value, serde_json::json!("Alice"));
    }

    #[test]
    fn test_string_column_like() {
        let cond = user_cols::NAME.like("%test%");
        assert_eq!(cond.column, "name");
        assert_eq!(cond.operator, ColumnOperator::Like);
        assert_eq!(cond.value, serde_json::json!("%test%"));
    }

    #[test]
    fn test_string_column_not_like() {
        let cond = user_cols::NAME.not_like("%spam%");
        assert_eq!(cond.operator, ColumnOperator::NotLike);
    }

    #[test]
    fn test_string_column_contains() {
        let cond = user_cols::NAME.contains("test");
        assert_eq!(cond.operator, ColumnOperator::LikeEscaped);
        assert_eq!(cond.value, serde_json::json!("%test%"));
    }

    #[test]
    fn test_string_column_starts_with() {
        let cond = user_cols::NAME.starts_with("Mr.");
        assert_eq!(cond.operator, ColumnOperator::LikeEscaped);
        assert_eq!(cond.value, serde_json::json!("Mr.%"));
    }

    #[test]
    fn test_string_column_ends_with() {
        let cond = user_cols::NAME.ends_with("son");
        assert_eq!(cond.operator, ColumnOperator::LikeEscaped);
        assert_eq!(cond.value, serde_json::json!("%son"));
    }

    #[test]
    fn test_string_column_in() {
        let cond = user_cols::NAME.is_in(vec!["Alice", "Bob", "Charlie"]);
        assert_eq!(cond.operator, ColumnOperator::In);
        assert_eq!(cond.value, serde_json::json!(["Alice", "Bob", "Charlie"]));
    }

    #[test]
    fn test_nullable_column_comparisons() {
        // Optional<i32> can still use comparisons
        let gt = user_cols::AGE.gt(18);
        assert_eq!(gt.column, "age");
        assert_eq!(gt.operator, ColumnOperator::Gt);

        let between = user_cols::AGE.between(18, 65);
        assert_eq!(between.operator, ColumnOperator::Between);
    }

    #[test]
    fn test_nullable_column_is_null() {
        let cond = user_cols::AGE.is_null();
        assert_eq!(cond.column, "age");
        assert_eq!(cond.operator, ColumnOperator::IsNull);
    }

    #[test]
    fn test_nullable_column_is_not_null() {
        let cond = user_cols::AGE.is_not_null();
        assert_eq!(cond.column, "age");
        assert_eq!(cond.operator, ColumnOperator::IsNotNull);
    }

    #[test]
    fn test_bool_column_eq() {
        let cond = user_cols::ACTIVE.eq(true);
        assert_eq!(cond.column, "active");
        assert_eq!(cond.operator, ColumnOperator::Eq);
        assert_eq!(cond.value, serde_json::json!(true));
    }

    #[test]
    fn test_float_column_comparisons() {
        let gt = user_cols::SCORE.gt(90.5);
        assert_eq!(gt.column, "score");
        assert_eq!(gt.operator, ColumnOperator::Gt);

        let between = user_cols::SCORE.between(0.0, 100.0);
        assert_eq!(between.operator, ColumnOperator::Between);
    }

    #[test]
    fn test_column_operator_to_sql() {
        assert_eq!(ColumnOperator::Eq.to_sql(), "=");
        assert_eq!(ColumnOperator::NotEq.to_sql(), "<>");
        assert_eq!(ColumnOperator::Gt.to_sql(), ">");
        assert_eq!(ColumnOperator::Gte.to_sql(), ">=");
        assert_eq!(ColumnOperator::Lt.to_sql(), "<");
        assert_eq!(ColumnOperator::Lte.to_sql(), "<=");
        assert_eq!(ColumnOperator::Like.to_sql(), "LIKE");
        assert_eq!(ColumnOperator::NotLike.to_sql(), "NOT LIKE");
        assert_eq!(ColumnOperator::In.to_sql(), "IN");
        assert_eq!(ColumnOperator::NotIn.to_sql(), "NOT IN");
        assert_eq!(ColumnOperator::IsNull.to_sql(), "IS NULL");
        assert_eq!(ColumnOperator::IsNotNull.to_sql(), "IS NOT NULL");
        assert_eq!(ColumnOperator::Between.to_sql(), "BETWEEN");
    }

    #[test]
    fn test_multiple_conditions_chain() {
        // Test that we can create multiple conditions and they're independent
        let c1 = user_cols::NAME.eq("Alice");
        let c2 = user_cols::AGE.gt(18);
        let c3 = user_cols::ACTIVE.eq(true);

        assert_eq!(c1.column, "name");
        assert_eq!(c2.column, "age");
        assert_eq!(c3.column, "active");
    }
}

// =============================================================================
// JOIN RESULT CONSOLIDATOR TESTS
// =============================================================================

mod join_consolidation {
    use serde::{Deserialize, Serialize};
    use tideorm::prelude::JoinResultConsolidator;

    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
    struct Order {
        id: i64,
        customer: String,
    }

    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
    struct LineItem {
        id: i64,
        order_id: i64,
        product: String,
        qty: i32,
    }

    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
    struct Product {
        id: i64,
        name: String,
    }

    #[test]
    fn test_consolidate_two_empty() {
        let flat: Vec<(Order, LineItem)> = vec![];
        let result = JoinResultConsolidator::consolidate_two(flat, |o| o.id);
        assert!(result.is_empty());
    }

    #[test]
    fn test_consolidate_two_single_order_single_item() {
        let flat = vec![(
            Order {
                id: 1,
                customer: "Alice".into(),
            },
            LineItem {
                id: 1,
                order_id: 1,
                product: "Widget".into(),
                qty: 2,
            },
        )];

        let result = JoinResultConsolidator::consolidate_two(flat, |o| o.id);

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].0.id, 1);
        assert_eq!(result[0].1.len(), 1);
        assert_eq!(result[0].1[0].product, "Widget");
    }

    #[test]
    fn test_consolidate_two_single_order_multiple_items() {
        let order = Order {
            id: 1,
            customer: "Alice".into(),
        };
        let flat = vec![
            (
                order.clone(),
                LineItem {
                    id: 1,
                    order_id: 1,
                    product: "Widget".into(),
                    qty: 2,
                },
            ),
            (
                order.clone(),
                LineItem {
                    id: 2,
                    order_id: 1,
                    product: "Gadget".into(),
                    qty: 1,
                },
            ),
            (
                order.clone(),
                LineItem {
                    id: 3,
                    order_id: 1,
                    product: "Gizmo".into(),
                    qty: 5,
                },
            ),
        ];

        let result = JoinResultConsolidator::consolidate_two(flat, |o| o.id);

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].0.customer, "Alice");
        assert_eq!(result[0].1.len(), 3);
    }

    #[test]
    fn test_consolidate_two_multiple_orders() {
        let order1 = Order {
            id: 1,
            customer: "Alice".into(),
        };
        let order2 = Order {
            id: 2,
            customer: "Bob".into(),
        };

        let flat = vec![
            (
                order1.clone(),
                LineItem {
                    id: 1,
                    order_id: 1,
                    product: "Widget".into(),
                    qty: 2,
                },
            ),
            (
                order1.clone(),
                LineItem {
                    id: 2,
                    order_id: 1,
                    product: "Gadget".into(),
                    qty: 1,
                },
            ),
            (
                order2.clone(),
                LineItem {
                    id: 3,
                    order_id: 2,
                    product: "Gizmo".into(),
                    qty: 5,
                },
            ),
        ];

        let result = JoinResultConsolidator::consolidate_two(flat, |o| o.id);

        assert_eq!(result.len(), 2);

        // Find Alice's order
        let alice_order = result.iter().find(|(o, _)| o.customer == "Alice").unwrap();
        assert_eq!(alice_order.1.len(), 2);

        // Find Bob's order
        let bob_order = result.iter().find(|(o, _)| o.customer == "Bob").unwrap();
        assert_eq!(bob_order.1.len(), 1);
    }

    #[test]
    fn test_consolidate_two_optional_with_nulls() {
        let order1 = Order {
            id: 1,
            customer: "Alice".into(),
        };
        let order2 = Order {
            id: 2,
            customer: "Bob".into(),
        };

        let flat: Vec<(Order, Option<LineItem>)> = vec![
            (
                order1.clone(),
                Some(LineItem {
                    id: 1,
                    order_id: 1,
                    product: "Widget".into(),
                    qty: 2,
                }),
            ),
            (order2.clone(), None), // Bob has no items (LEFT JOIN result)
        ];

        let result = JoinResultConsolidator::consolidate_two_optional(flat, |o| o.id);

        assert_eq!(result.len(), 2);

        let alice_order = result.iter().find(|(o, _)| o.customer == "Alice").unwrap();
        assert_eq!(alice_order.1.len(), 1);

        let bob_order = result.iter().find(|(o, _)| o.customer == "Bob").unwrap();
        assert_eq!(bob_order.1.len(), 0); // Empty vec, not None
    }

    #[test]
    fn test_consolidate_three_empty() {
        let flat: Vec<(Order, LineItem, Product)> = vec![];
        let result = JoinResultConsolidator::consolidate_three(flat, |o| o.id, |i| i.id);
        assert!(result.is_empty());
    }

    #[test]
    fn test_consolidate_three_nested() {
        let order = Order {
            id: 1,
            customer: "Alice".into(),
        };
        let item1 = LineItem {
            id: 1,
            order_id: 1,
            product: "Widget".into(),
            qty: 2,
        };
        let item2 = LineItem {
            id: 2,
            order_id: 1,
            product: "Gadget".into(),
            qty: 1,
        };
        let prod1 = Product {
            id: 1,
            name: "Super Widget".into(),
        };
        let prod2 = Product {
            id: 2,
            name: "Mega Gadget".into(),
        };

        let flat = vec![
            (order.clone(), item1.clone(), prod1.clone()),
            (order.clone(), item2.clone(), prod2.clone()),
        ];

        let result = JoinResultConsolidator::consolidate_three(flat, |o| o.id, |i| i.id);

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].0.customer, "Alice");
        assert_eq!(result[0].1.len(), 2); // Two line items

        // Each line item should have one product
        for (_item, products) in &result[0].1 {
            assert_eq!(products.len(), 1);
        }
    }

    #[test]
    fn test_consolidate_three_optional() {
        let order = Order {
            id: 1,
            customer: "Alice".into(),
        };
        let item1 = LineItem {
            id: 1,
            order_id: 1,
            product: "Widget".into(),
            qty: 2,
        };
        let item2 = LineItem {
            id: 2,
            order_id: 1,
            product: "Custom".into(),
            qty: 1,
        };
        let prod1 = Product {
            id: 1,
            name: "Super Widget".into(),
        };

        let flat: Vec<(Order, LineItem, Option<Product>)> = vec![
            (order.clone(), item1.clone(), Some(prod1.clone())),
            (order.clone(), item2.clone(), None), // Custom item has no product
        ];

        let result = JoinResultConsolidator::consolidate_three_optional(flat, |o| o.id, |i| i.id);

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].1.len(), 2);

        // Find the Widget item
        let widget_item = result[0]
            .1
            .iter()
            .find(|(i, _)| i.product == "Widget")
            .unwrap();
        assert_eq!(widget_item.1.len(), 1);

        // Find the Custom item
        let custom_item = result[0]
            .1
            .iter()
            .find(|(i, _)| i.product == "Custom")
            .unwrap();
        assert_eq!(custom_item.1.len(), 0);
    }

    #[test]
    fn test_consolidate_preserves_order() {
        // Items should be preserved in insertion order
        let order = Order {
            id: 1,
            customer: "Test".into(),
        };

        let flat = vec![
            (
                order.clone(),
                LineItem {
                    id: 1,
                    order_id: 1,
                    product: "First".into(),
                    qty: 1,
                },
            ),
            (
                order.clone(),
                LineItem {
                    id: 2,
                    order_id: 1,
                    product: "Second".into(),
                    qty: 1,
                },
            ),
            (
                order.clone(),
                LineItem {
                    id: 3,
                    order_id: 1,
                    product: "Third".into(),
                    qty: 1,
                },
            ),
        ];

        let result = JoinResultConsolidator::consolidate_two(flat, |o| o.id);

        assert_eq!(result[0].1[0].product, "First");
        assert_eq!(result[0].1[1].product, "Second");
        assert_eq!(result[0].1[2].product, "Third");
    }
}

// =============================================================================
// NESTED ACTIVE MODEL TESTS
// =============================================================================

mod nested_save {
    #[test]
    fn test_nested_save_builder_serialization() {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
        struct MockUser {
            id: i64,
            name: String,
        }

        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
        struct MockProfile {
            id: i64,
            user_id: i64,
            bio: String,
        }

        let user = MockUser {
            id: 0,
            name: "Test".into(),
        };
        let profile = MockProfile {
            id: 0,
            user_id: 0,
            bio: "Hello".into(),
        };

        let user_json = serde_json::to_value(&user).unwrap();
        let profile_json = serde_json::to_value(&profile).unwrap();

        assert!(user_json.is_object());
        assert!(profile_json.is_object());
    }

    #[test]
    fn test_foreign_key_update_logic() {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
        struct Child {
            id: i64,
            parent_id: i64,
            name: String,
        }

        let child = Child {
            id: 0,
            parent_id: 0,
            name: "Test".into(),
        };
        let mut json = serde_json::to_value(&child).unwrap();

        if let serde_json::Value::Object(ref mut map) = json {
            map.insert("parent_id".to_string(), serde_json::json!(42));
        }

        let updated: Child = serde_json::from_value(json).unwrap();
        assert_eq!(updated.parent_id, 42);
        assert_eq!(updated.name, "Test");
    }

    #[test]
    fn test_multiple_children_update() {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
        struct Post {
            id: i64,
            user_id: i64,
            title: String,
        }

        let posts = vec![
            Post {
                id: 0,
                user_id: 0,
                title: "First".into(),
            },
            Post {
                id: 0,
                user_id: 0,
                title: "Second".into(),
            },
            Post {
                id: 0,
                user_id: 0,
                title: "Third".into(),
            },
        ];

        let parent_id = 99i64;

        let updated: Vec<Post> = posts
            .into_iter()
            .map(|post| {
                let mut json = serde_json::to_value(&post).unwrap();
                if let serde_json::Value::Object(ref mut map) = json {
                    map.insert("user_id".to_string(), serde_json::json!(parent_id));
                }
                serde_json::from_value(json).unwrap()
            })
            .collect();

        assert_eq!(updated.len(), 3);
        assert!(updated.iter().all(|p| p.user_id == 99));
        assert_eq!(updated[0].title, "First");
        assert_eq!(updated[1].title, "Second");
        assert_eq!(updated[2].title, "Third");
    }
}
