// tests/tables_data/handlers/put_table_data_test3.rs

// ========================================================================
// ADDITIONAL HELPER FUNCTIONS FOR COMPREHENSIVE PUT TEST 3
// ========================================================================

// Note: Helper functions like create_string_value, create_number_value, etc. 
// are already defined in the main test file, so we don't redefine them here.

// ========================================================================
// CONTEXTS FOR ADVANCED TESTING
// ========================================================================

#[derive(Clone)]
struct AdvancedDataTypeTestContext {
    pool: PgPool,
    profile_name: String,
    table_name: String,
    indexer_tx: mpsc::Sender<IndexCommand>,
}

#[derive(Clone)]
struct ForeignKeyUpdateTestContext {
    pool: PgPool,
    profile_name: String,
    category_table: String,
    product_table: String,
    order_table: String,
    indexer_tx: mpsc::Sender<IndexCommand>,
}

#[derive(Clone)]
struct IntegerRobustnessTestContext {
    pool: PgPool,
    profile_name: String,
    mixed_integer_table: String,
    bigint_only_table: String,
    integer_only_table: String,
    indexer_tx: mpsc::Sender<IndexCommand>,
}

#[derive(Clone)]
struct FinancialUpdateTestContext {
    pool: PgPool,
    profile_name: String,
    table_name: String,
    indexer_tx: mpsc::Sender<IndexCommand>,
}

// ========================================================================
// TABLE CREATION HELPERS
// ========================================================================

async fn create_advanced_data_type_table(
    pool: &PgPool,
    table_name: &str,
    profile_name: &str
) -> Result<(), tonic::Status> {
    let table_def_request = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: table_name.into(),
        columns: vec![
            TableColumnDefinition { name: "my_text".into(), field_type: "text".into() },
            TableColumnDefinition { name: "my_bool".into(), field_type: "boolean".into() },
            TableColumnDefinition { name: "my_timestamp".into(), field_type: "timestamptz".into() },
            TableColumnDefinition { name: "my_bigint".into(), field_type: "integer".into() },
            TableColumnDefinition { name: "my_money".into(), field_type: "decimal(19,4)".into() },
            TableColumnDefinition { name: "my_date".into(), field_type: "date".into() },
            TableColumnDefinition { name: "my_decimal".into(), field_type: "decimal(10,2)".into() },
            TableColumnDefinition { name: "my_real_bigint".into(), field_type: "biginteger".into() },
        ],
        indexes: vec![],
        links: vec![],
    };

    post_table_definition(pool, table_def_request).await?;
    Ok(())
}

async fn create_foreign_key_test_tables(
    pool: &PgPool,
    profile_name: &str,
    category_table: &str,
    product_table: &str,
    order_table: &str
) -> Result<(), tonic::Status> {
    // Create category table first (no dependencies)
    let category_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: category_table.into(),
        columns: vec![
            TableColumnDefinition { name: "name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "description".into(), field_type: "text".into() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, category_def).await?;

    // Create product table with required link to category
    let product_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: product_table.into(),
        columns: vec![
            TableColumnDefinition { name: "name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "price".into(), field_type: "decimal(10,2)".into() },
        ],
        indexes: vec![],
        links: vec![
            TableLink { linked_table_name: category_table.into(), required: true },
        ],
    };
    post_table_definition(pool, product_def).await?;

    // Create order table with required link to product and optional link to category
    let order_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: order_table.into(),
        columns: vec![
            TableColumnDefinition { name: "quantity".into(), field_type: "integer".into() },
            TableColumnDefinition { name: "notes".into(), field_type: "text".into() },
        ],
        indexes: vec![],
        links: vec![
            TableLink { linked_table_name: product_table.into(), required: true },
            TableLink { linked_table_name: category_table.into(), required: false },
        ],
    };
    post_table_definition(pool, order_def).await?;

    Ok(())
}

async fn create_integer_robustness_tables(
    pool: &PgPool,
    profile_name: &str,
    mixed_table: &str,
    bigint_table: &str,
    integer_table: &str
) -> Result<(), tonic::Status> {
    // Table with both INTEGER and BIGINT columns
    let mixed_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: mixed_table.into(),
        columns: vec![
            TableColumnDefinition { name: "name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "small_int".into(), field_type: "integer".into() },
            TableColumnDefinition { name: "big_int".into(), field_type: "biginteger".into() },
            TableColumnDefinition { name: "another_int".into(), field_type: "int".into() },
            TableColumnDefinition { name: "another_bigint".into(), field_type: "bigint".into() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, mixed_def).await?;

    // Table with only BIGINT columns
    let bigint_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: bigint_table.into(),
        columns: vec![
            TableColumnDefinition { name: "name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "value1".into(), field_type: "biginteger".into() },
            TableColumnDefinition { name: "value2".into(), field_type: "bigint".into() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, bigint_def).await?;

    // Table with only INTEGER columns
    let integer_def = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: integer_table.into(),
        columns: vec![
            TableColumnDefinition { name: "name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "value1".into(), field_type: "integer".into() },
            TableColumnDefinition { name: "value2".into(), field_type: "int".into() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, integer_def).await?;

    Ok(())
}

async fn create_financial_update_table(
    pool: &PgPool,
    table_name: &str,
    profile_name: &str,
) -> Result<(), tonic::Status> {
    let table_def_request = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: table_name.into(),
        columns: vec![
            TableColumnDefinition { name: "product_name".into(), field_type: "text".into() },
            TableColumnDefinition { name: "price".into(), field_type: "decimal(19, 4)".into() },
            TableColumnDefinition { name: "rate".into(), field_type: "decimal(10, 5)".into() },
            TableColumnDefinition { name: "discount".into(), field_type: "decimal(5, 3)".into() },
        ],
        indexes: vec![],
        links: vec![],
    };

    post_table_definition(pool, table_def_request).await?;
    Ok(())
}

// ========================================================================
// FIXTURES
// ========================================================================

#[fixture]
async fn advanced_data_type_test_context() -> AdvancedDataTypeTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("adv_dtype_profile_{}", unique_id);
    let table_name = format!("adv_dtype_table_{}", unique_id);

    create_advanced_data_type_table(&pool, &table_name, &profile_name).await
        .expect("Failed to create advanced data type test table");

    let (tx, mut rx) = mpsc::channel(100);
    tokio::spawn(async move { while rx.recv().await.is_some() {} });

    AdvancedDataTypeTestContext { pool, profile_name, table_name, indexer_tx: tx }
}

#[fixture]
async fn foreign_key_update_test_context() -> ForeignKeyUpdateTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("fk_update_profile_{}", unique_id);
    let category_table = format!("category_upd_{}", unique_id);
    let product_table = format!("product_upd_{}", unique_id);
    let order_table = format!("order_upd_{}", unique_id);

    create_foreign_key_test_tables(&pool, &profile_name, &category_table, &product_table, &order_table).await
        .expect("Failed to create foreign key test tables");

    let (tx, mut rx) = mpsc::channel(100);
    tokio::spawn(async move { while rx.recv().await.is_some() {} });

    ForeignKeyUpdateTestContext {
        pool, profile_name, category_table, product_table, order_table, indexer_tx: tx
    }
}

#[fixture]
async fn integer_robustness_test_context() -> IntegerRobustnessTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("int_robust_profile_{}", unique_id);
    let mixed_table = format!("mixed_int_table_{}", unique_id);
    let bigint_table = format!("bigint_table_{}", unique_id);
    let integer_table = format!("integer_table_{}", unique_id);

    create_integer_robustness_tables(&pool, &profile_name, &mixed_table, &bigint_table, &integer_table).await
        .expect("Failed to create integer robustness test tables");

    let (tx, mut rx) = mpsc::channel(100);
    tokio::spawn(async move { while rx.recv().await.is_some() {} });

    IntegerRobustnessTestContext {
        pool, profile_name, mixed_integer_table: mixed_table,
        bigint_only_table: bigint_table, integer_only_table: integer_table, indexer_tx: tx
    }
}

#[fixture]
async fn financial_update_test_context() -> FinancialUpdateTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("financial_upd_profile_{}", unique_id);
    let table_name = format!("invoices_upd_{}", unique_id);

    create_financial_update_table(&pool, &table_name, &profile_name).await
        .expect("Failed to create financial update test table");

    let (tx, mut rx) = mpsc::channel(100);
    tokio::spawn(async move { while rx.recv().await.is_some() {} });

    FinancialUpdateTestContext { pool, profile_name, table_name, indexer_tx: tx }
}

// ========================================================================
// HELPER FUNCTIONS FOR CREATING INITIAL RECORDS
// ========================================================================

async fn create_initial_advanced_record(context: &AdvancedDataTypeTestContext) -> i64 {
    let mut initial_data = HashMap::new();
    initial_data.insert("my_text".to_string(), string_to_proto_value("Initial Text"));
    initial_data.insert("my_bool".to_string(), bool_to_proto_value(false));
    initial_data.insert("my_timestamp".to_string(), string_to_proto_value("2024-01-01T12:00:00Z"));
    initial_data.insert("my_bigint".to_string(), Value { kind: Some(Kind::NumberValue(100.0)) });
    initial_data.insert("my_money".to_string(), string_to_proto_value("100.0000"));
    initial_data.insert("my_decimal".to_string(), string_to_proto_value("50.00"));
    initial_data.insert("my_real_bigint".to_string(), Value { kind: Some(Kind::NumberValue(1000000000000.0)) });

    let request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: initial_data,
    };
    let response = post_table_data(&context.pool, request, &context.indexer_tx)
        .await
        .expect("Setup: Failed to create initial record");
    response.inserted_id
}

// Fixed function name to avoid conflict - this version is for FinancialUpdateTestContext
async fn create_initial_financial_update_record(context: &FinancialUpdateTestContext) -> i64 {
    let mut initial_data = HashMap::new();
    initial_data.insert("product_name".to_string(), string_to_proto_value("Initial Product"));
    initial_data.insert("price".to_string(), string_to_proto_value("100.0000"));
    initial_data.insert("rate".to_string(), string_to_proto_value("1.00000"));
    initial_data.insert("discount".to_string(), string_to_proto_value("0.100"));

    let request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: initial_data,
    };
    let response = post_table_data(&context.pool, request, &context.indexer_tx)
        .await
        .expect("Setup: Failed to create initial financial record");
    response.inserted_id
}

async fn create_initial_integer_record(context: &IntegerRobustnessTestContext, table_name: &str) -> i64 {
    let mut initial_data = HashMap::new();
    initial_data.insert("name".to_string(), string_to_proto_value("Initial Record"));

    match table_name {
        table if table.contains("mixed") => {
            initial_data.insert("small_int".to_string(), Value { kind: Some(Kind::NumberValue(100.0)) });
            initial_data.insert("big_int".to_string(), Value { kind: Some(Kind::NumberValue(1000000000000.0)) });
            initial_data.insert("another_int".to_string(), Value { kind: Some(Kind::NumberValue(200.0)) });
            initial_data.insert("another_bigint".to_string(), Value { kind: Some(Kind::NumberValue(2000000000000.0)) });
        },
        table if table.contains("bigint") => {
            initial_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(1000000000000.0)) });
            initial_data.insert("value2".to_string(), Value { kind: Some(Kind::NumberValue(2000000000000.0)) });
        },
        table if table.contains("integer") => {
            initial_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(100.0)) });
            initial_data.insert("value2".to_string(), Value { kind: Some(Kind::NumberValue(200.0)) });
        },
        _ => {}
    }

    let request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: table_name.to_string(),
        data: initial_data,
    };
    let response = post_table_data(&context.pool, request, &context.indexer_tx)
        .await
        .expect("Setup: Failed to create initial integer record");
    response.inserted_id
}

// ========================================================================
// FOREIGN KEY UPDATE TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_valid_foreign_key_reference(
    #[future] foreign_key_update_test_context: ForeignKeyUpdateTestContext,
) {
    let context = foreign_key_update_test_context.await;

    // Create categories
    let mut category1_data = HashMap::new();
    category1_data.insert("name".to_string(), string_to_proto_value("Electronics"));
    let category1_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.category_table.clone(),
        data: category1_data,
    };
    let category1_response = post_table_data(&context.pool, category1_request, &context.indexer_tx).await.unwrap();

    let mut category2_data = HashMap::new();
    category2_data.insert("name".to_string(), string_to_proto_value("Books"));
    let category2_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.category_table.clone(),
        data: category2_data,
    };
    let category2_response = post_table_data(&context.pool, category2_request, &context.indexer_tx).await.unwrap();

    // Create product with category1
    let mut product_data = HashMap::new();
    product_data.insert("name".to_string(), string_to_proto_value("Laptop"));
    product_data.insert("price".to_string(), string_to_proto_value("999.99"));
    product_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(category1_response.inserted_id as f64)) });

    let product_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.product_table.clone(),
        data: product_data,
    };
    let product_response = post_table_data(&context.pool, product_request, &context.indexer_tx).await.unwrap();

    // Update product to reference category2
    let mut update_data = HashMap::new();
    update_data.insert("name".to_string(), string_to_proto_value("Programming Book"));
    update_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(category2_response.inserted_id as f64)) });

    let update_request = PutTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.product_table.clone(),
        id: product_response.inserted_id,
        data: update_data,
    };

    let result = put_table_data(&context.pool, update_request, &context.indexer_tx).await;
    assert!(result.is_ok(), "Update with valid foreign key should succeed");

    // Verify the foreign key was updated
    let query = format!(
        r#"SELECT name, "{}_id" FROM "{}"."{}" WHERE id = $1"#,
        context.category_table, context.profile_name, context.product_table
    );
    let row = sqlx::query(&query)
        .bind(product_response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let name: String = row.get("name");
    let category_id: i64 = row.get(format!("{}_id", context.category_table).as_str());

    assert_eq!(name, "Programming Book");
    assert_eq!(category_id, category2_response.inserted_id);
}

#[rstest]
#[tokio::test]
async fn test_update_nonexistent_foreign_key_reference(
    #[future] foreign_key_update_test_context: ForeignKeyUpdateTestContext,
) {
    let context = foreign_key_update_test_context.await;

    // Create category and product
    let mut category_data = HashMap::new();
    category_data.insert("name".to_string(), string_to_proto_value("Electronics"));
    let category_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.category_table.clone(),
        data: category_data,
    };
    let category_response = post_table_data(&context.pool, category_request, &context.indexer_tx).await.unwrap();

    let mut product_data = HashMap::new();
    product_data.insert("name".to_string(), string_to_proto_value("Laptop"));
    product_data.insert("price".to_string(), string_to_proto_value("999.99"));
    product_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(category_response.inserted_id as f64)) });

    let product_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.product_table.clone(),
        data: product_data,
    };
    let product_response = post_table_data(&context.pool, product_request, &context.indexer_tx).await.unwrap();

    // Try to update product to reference non-existent category
    let mut update_data = HashMap::new();
    update_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(99999.0)) });

    let update_request = PutTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.product_table.clone(),
        id: product_response.inserted_id,
        data: update_data,
    };

    let result = put_table_data(&context.pool, update_request, &context.indexer_tx).await;
    assert!(result.is_err(), "Update with non-existent foreign key should fail");

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::Internal);
        assert!(err.message().contains("Update failed"));
    }
}

#[rstest]
#[tokio::test]
async fn test_update_optional_foreign_key_to_null(
    #[future] foreign_key_update_test_context: ForeignKeyUpdateTestContext,
) {
    let context = foreign_key_update_test_context.await;

    // Create required entities
    let mut category_data = HashMap::new();
    category_data.insert("name".to_string(), string_to_proto_value("Books"));
    let category_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.category_table.clone(),
        data: category_data,
    };
    let category_response = post_table_data(&context.pool, category_request, &context.indexer_tx).await.unwrap();

    let mut product_data = HashMap::new();
    product_data.insert("name".to_string(), string_to_proto_value("Book"));
    product_data.insert("price".to_string(), string_to_proto_value("29.99"));
    product_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(category_response.inserted_id as f64)) });

    let product_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.product_table.clone(),
        data: product_data,
    };
    let product_response = post_table_data(&context.pool, product_request, &context.indexer_tx).await.unwrap();

    // Create order with both required and optional foreign keys
    let mut order_data = HashMap::new();
    order_data.insert("quantity".to_string(), Value { kind: Some(Kind::NumberValue(2.0)) });
    order_data.insert("notes".to_string(), string_to_proto_value("Initial order"));
    order_data.insert(format!("{}_id", context.product_table), Value { kind: Some(Kind::NumberValue(product_response.inserted_id as f64)) });
    order_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NumberValue(category_response.inserted_id as f64)) });

    let order_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.order_table.clone(),
        data: order_data,
    };
    let order_response = post_table_data(&context.pool, order_request, &context.indexer_tx).await.unwrap();

    // Update to set optional foreign key to null
    let mut update_data = HashMap::new();
    update_data.insert("notes".to_string(), string_to_proto_value("Updated order - no category"));
    update_data.insert(format!("{}_id", context.category_table), Value { kind: Some(Kind::NullValue(0)) });

    let update_request = PutTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.order_table.clone(),
        id: order_response.inserted_id,
        data: update_data,
    };

    let result = put_table_data(&context.pool, update_request, &context.indexer_tx).await;
    assert!(result.is_ok(), "Update optional foreign key to null should succeed");

    // Verify the optional foreign key is now null
    let query = format!(
        r#"SELECT notes, "{}_id" FROM "{}"."{}" WHERE id = $1"#,
        context.category_table, context.profile_name, context.order_table
    );
    let row = sqlx::query(&query)
        .bind(order_response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let notes: String = row.get("notes");
    let category_id: Option<i64> = row.get(format!("{}_id", context.category_table).as_str());

    assert_eq!(notes, "Updated order - no category");
    assert!(category_id.is_none());
}

// ========================================================================
// ADVANCED DECIMAL UPDATE TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_decimal_precision_comprehensive(
    #[future] financial_update_test_context: FinancialUpdateTestContext,
) {
    let context = financial_update_test_context.await;
    let record_id = create_initial_financial_update_record(&context).await;

    let decimal_test_cases = vec![
        ("price", "1599.9999", dec!(1599.9999)),
        ("rate", "0.12345", dec!(0.12345)),
        ("discount", "99.999", dec!(99.999)),
        ("price", "-1234.5678", dec!(-1234.5678)),
        ("rate", "-0.99999", dec!(-0.99999)),
    ];

    for (i, (field, value_str, expected_decimal)) in decimal_test_cases.into_iter().enumerate() {
        let mut update_data = HashMap::new();
        update_data.insert("product_name".to_string(), string_to_proto_value(&format!("Precision Test {}", i)));
        update_data.insert(field.to_string(), string_to_proto_value(value_str));

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.table_name.clone(),
            id: record_id,
            data: update_data,
        };

        let response = put_table_data(&context.pool, request, &context.indexer_tx)
            .await
            .unwrap();
        assert!(response.success);

        // Verify precision was preserved
        let query = format!(
            r#"SELECT {} FROM "{}"."{}" WHERE id = $1"#,
            field, context.profile_name, context.table_name
        );
        let stored_value: rust_decimal::Decimal = sqlx::query_scalar(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        assert_eq!(stored_value, expected_decimal, "Precision mismatch for field {}", field);
    }
}

#[rstest]
#[tokio::test]
async fn test_update_decimal_rounding_behavior(
    #[future] financial_update_test_context: FinancialUpdateTestContext,
) {
    let context = financial_update_test_context.await;
    let record_id = create_initial_financial_update_record(&context).await;

    // Test values that require rounding based on column precision
    let rounding_test_cases = vec![
        ("price", "99.123456", dec!(99.1235)), // decimal(19,4) rounds to 4 places
        ("rate", "0.123456", dec!(0.12346)),   // decimal(10,5) rounds to 5 places
        ("discount", "12.3456", dec!(12.346)), // decimal(5,3) rounds to 3 places
    ];

    for (field, input_value, expected_rounded) in rounding_test_cases {
        let mut update_data = HashMap::new();
        update_data.insert(field.to_string(), string_to_proto_value(input_value));

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.table_name.clone(),
            id: record_id,
            data: update_data,
        };

        let response = put_table_data(&context.pool, request, &context.indexer_tx)
            .await
            .unwrap();
        assert!(response.success);

        // Verify rounding was applied correctly
        let query = format!(
            r#"SELECT {} FROM "{}"."{}" WHERE id = $1"#,
            field, context.profile_name, context.table_name
        );
        let stored_value: rust_decimal::Decimal = sqlx::query_scalar(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        assert_eq!(stored_value, expected_rounded, "Rounding mismatch for field {}", field);
    }
}

#[rstest]
#[tokio::test]
async fn test_update_decimal_overflow_handling(
    #[future] financial_update_test_context: FinancialUpdateTestContext,
) {
    let context = financial_update_test_context.await;
    let record_id = create_initial_financial_update_record(&context).await;

    // Test values that exceed precision limits
    let overflow_test_cases = vec![
        ("discount", "1000.123"), // discount is decimal(5,3) - max is 99.999
        ("rate", "123456.12345"), // rate is decimal(10,5) - too many digits before decimal
    ];

    for (field, overflow_value) in overflow_test_cases {
        let mut update_data = HashMap::new();
        update_data.insert(field.to_string(), string_to_proto_value(overflow_value));

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.table_name.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_err(), "Should fail for decimal overflow: {} = {}", field, overflow_value);

        if let Err(err) = result {
            assert_eq!(err.code(), tonic::Code::InvalidArgument);
            assert!(err.message().contains("Numeric field overflow"));
        }
    }
}

// ========================================================================
// INTEGER ROBUSTNESS UPDATE TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_integer_boundary_values_comprehensive(
    #[future] integer_robustness_test_context: IntegerRobustnessTestContext,
) {
    let context = integer_robustness_test_context.await;

    // Test i32 boundaries on INTEGER columns
    let record_id = create_initial_integer_record(&context, &context.integer_only_table).await;

    let i32_boundary_tests = vec![
        (2147483647.0, "i32::MAX"),
        (-2147483648.0, "i32::MIN"),
        (2147483646.0, "i32::MAX - 1"),
        (-2147483647.0, "i32::MIN + 1"),
        (0.0, "zero"),
        (1.0, "one"),
        (-1.0, "negative one"),
    ];

    for (value, description) in i32_boundary_tests {
        let mut update_data = HashMap::new();
        update_data.insert("name".to_string(), string_to_proto_value(&format!("i32 boundary test: {}", description)));
        update_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(value)) });
        update_data.insert("value2".to_string(), Value { kind: Some(Kind::NumberValue(value)) });

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.integer_only_table.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_ok(), "Failed for i32 value {}: {}", value, description);

        // Verify correct storage
        let query = format!(
            r#"SELECT value1, value2 FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.integer_only_table
        );
        let row = sqlx::query(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        let stored_val1: i32 = row.get("value1");
        let stored_val2: i32 = row.get("value2");
        assert_eq!(stored_val1, value as i32);
        assert_eq!(stored_val2, value as i32);
    }
}

#[rstest]
#[tokio::test]
async fn test_update_bigint_boundary_values_comprehensive(
    #[future] integer_robustness_test_context: IntegerRobustnessTestContext,
) {
    let context = integer_robustness_test_context.await;
    let record_id = create_initial_integer_record(&context, &context.bigint_only_table).await;

    let i64_boundary_tests = vec![
        (9223372036854774784.0, "Close to i64::MAX (precisely representable)"),
        (-9223372036854774784.0, "Close to i64::MIN (precisely representable)"),
        (4611686018427387904.0, "i64::MAX / 2"),
        (-4611686018427387904.0, "i64::MIN / 2"),
        (1000000000000.0, "One trillion"),
        (-1000000000000.0, "Negative one trillion"),
    ];

    for (value, description) in i64_boundary_tests {
        let mut update_data = HashMap::new();
        update_data.insert("name".to_string(), string_to_proto_value(&format!("i64 test: {}", description)));
        update_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(value)) });
        update_data.insert("value2".to_string(), Value { kind: Some(Kind::NumberValue(value)) });

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.bigint_only_table.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_ok(), "Failed for i64 value {}: {}", value, description);

        // Verify correct storage
        let query = format!(
            r#"SELECT value1, value2 FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.bigint_only_table
        );
        let row = sqlx::query(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        let stored_val1: i64 = row.get("value1");
        let stored_val2: i64 = row.get("value2");
        assert_eq!(stored_val1, value as i64);
        assert_eq!(stored_val2, value as i64);
    }
}

#[rstest]
#[tokio::test]
async fn test_update_integer_overflow_rejection_i32(
    #[future] integer_robustness_test_context: IntegerRobustnessTestContext,
) {
    let context = integer_robustness_test_context.await;
    let record_id = create_initial_integer_record(&context, &context.integer_only_table).await;

    let overflow_values = vec![
        (2147483648.0, "i32::MAX + 1"),
        (-2147483649.0, "i32::MIN - 1"),
        (3000000000.0, "3 billion"),
        (-3000000000.0, "negative 3 billion"),
        (4294967296.0, "2^32"),
    ];

    for (value, description) in overflow_values {
        let mut update_data = HashMap::new();
        update_data.insert("name".to_string(), string_to_proto_value(&format!("Overflow test: {}", description)));
        update_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(value)) });

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.integer_only_table.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_err(), "Should have failed for i32 overflow value {}: {}", value, description);

        if let Err(err) = result {
            assert_eq!(err.code(), tonic::Code::InvalidArgument);
            assert!(err.message().contains("Integer value out of range for INTEGER column"));
        }
    }
}

#[rstest]
#[tokio::test]
async fn test_update_float_precision_edge_cases(
    #[future] integer_robustness_test_context: IntegerRobustnessTestContext,
) {
    let context = integer_robustness_test_context.await;
    let record_id = create_initial_integer_record(&context, &context.integer_only_table).await;

    // Test values that have fractional parts (should be rejected)
    let fractional_values = vec![
        (42.1, "42.1"),
        (42.9, "42.9"),
        (42.000001, "42.000001"),
        (-42.5, "-42.5"),
        (0.1, "0.1"),
        (2147483646.5, "Near i32::MAX with fraction"),
    ];

    for (value, description) in fractional_values {
        let mut update_data = HashMap::new();
        update_data.insert("name".to_string(), string_to_proto_value(&format!("Float test: {}", description)));
        update_data.insert("value1".to_string(), Value { kind: Some(Kind::NumberValue(value)) });

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.integer_only_table.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_err(), "Should fail for fractional value {}: {}", value, description);

        if let Err(err) = result {
            assert_eq!(err.code(), tonic::Code::InvalidArgument);
            assert!(err.message().contains("Expected integer for column") && err.message().contains("but got a float"));
        }
    }
}

#[rstest]
#[tokio::test]
async fn test_update_mixed_integer_types_same_record(
    #[future] integer_robustness_test_context: IntegerRobustnessTestContext,
) {
    let context = integer_robustness_test_context.await;
    let record_id = create_initial_integer_record(&context, &context.mixed_integer_table).await;

    // Test updating different values into different integer types in the same record
    let test_cases = vec![
        (42.0, 1000000000000.0, "Small i32, large i64"),
        (2147483647.0, 9223372036854774784.0, "i32::MAX, near i64::MAX"),
        (-2147483648.0, -9223372036854774784.0, "i32::MIN, near i64::MIN"),
        (0.0, 0.0, "Both zero"),
        (-1.0, -1.0, "Both negative one"),
    ];

    for (i32_val, i64_val, description) in test_cases {
        let mut update_data = HashMap::new();
        update_data.insert("name".to_string(), string_to_proto_value(&format!("Mixed update test: {}", description)));
        update_data.insert("small_int".to_string(), Value { kind: Some(Kind::NumberValue(i32_val)) });
        update_data.insert("big_int".to_string(), Value { kind: Some(Kind::NumberValue(i64_val)) });
        update_data.insert("another_int".to_string(), Value { kind: Some(Kind::NumberValue(i32_val)) });
        update_data.insert("another_bigint".to_string(), Value { kind: Some(Kind::NumberValue(i64_val)) });

        let request = PutTableDataRequest {
            profile_name: context.profile_name.clone(),
            table_name: context.mixed_integer_table.clone(),
            id: record_id,
            data: update_data,
        };

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_ok(), "Failed for mixed integer update test: {}", description);

        // Verify correct storage with correct types
        let query = format!(
            r#"SELECT small_int, big_int, another_int, another_bigint FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.mixed_integer_table
        );
        let row = sqlx::query(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        let stored_small_int: i32 = row.get("small_int");
        let stored_big_int: i64 = row.get("big_int");
        let stored_another_int: i32 = row.get("another_int");
        let stored_another_bigint: i64 = row.get("another_bigint");

        assert_eq!(stored_small_int, i32_val as i32);
        assert_eq!(stored_big_int, i64_val as i64);
        assert_eq!(stored_another_int, i32_val as i32);
        assert_eq!(stored_another_bigint, i64_val as i64);
    }
}

// ========================================================================
// ADVANCED MIXED DATA TYPE UPDATE TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_mixed_null_and_valid_data_comprehensive(
    #[future] advanced_data_type_test_context: AdvancedDataTypeTestContext,
) {
    let context = advanced_data_type_test_context.await;
    let record_id = create_initial_advanced_record(&context).await;

    // Update with mix of nulls and valid values
    let mut update_data = HashMap::new();
    update_data.insert("my_text".to_string(), string_to_proto_value("Updated with mixed data"));
    update_data.insert("my_bool".to_string(), bool_to_proto_value(true));
    update_data.insert("my_timestamp".to_string(), Value { kind: Some(Kind::NullValue(0)) });
    update_data.insert("my_bigint".to_string(), Value { kind: Some(Kind::NumberValue(42.0)) });
    update_data.insert("my_money".to_string(), Value { kind: Some(Kind::NullValue(0)) });
    update_data.insert("my_decimal".to_string(), string_to_proto_value("999.99"));
    update_data.insert("my_real_bigint".to_string(), Value { kind: Some(Kind::NumberValue(9223372036854774784.0)) });

    let request = PutTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        id: record_id,
        data: update_data,
    };

    let response = put_table_data(&context.pool, request, &context.indexer_tx).await.unwrap();
    assert!(response.success);

    // Verify mixed null and valid data was stored correctly
    let query = format!(
        r#"SELECT my_text, my_bool, my_timestamp, my_bigint, my_money, my_decimal, my_real_bigint FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    let row = sqlx::query(&query)
        .bind(record_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let stored_text: String = row.get("my_text");
    let stored_bool: bool = row.get("my_bool");
    let stored_timestamp: Option<chrono::DateTime<chrono::Utc>> = row.get("my_timestamp");
    let stored_bigint: i32 = row.get("my_bigint");
    let stored_money: Option<rust_decimal::Decimal> = row.get("my_money");
    let stored_decimal: rust_decimal::Decimal = row.get("my_decimal");
    let stored_real_bigint: i64 = row.get("my_real_bigint");

    assert_eq!(stored_text, "Updated with mixed data");
    assert_eq!(stored_bool, true);
    assert!(stored_timestamp.is_none());
    assert_eq!(stored_bigint, 42);
    assert!(stored_money.is_none());
    assert_eq!(stored_decimal, dec!(999.99));
    assert_eq!(stored_real_bigint, 9223372036854774784);
}
