// tests/tables_data/handlers/put_table_data_test2.rs

// ========================================================================
// ADDITIONAL HELPER FUNCTIONS FOR COMPREHENSIVE PUT TESTS
// ========================================================================

// Additional imports needed for these tests
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::str::FromStr;

fn create_string_value(s: &str) -> Value {
    Value { kind: Some(Kind::StringValue(s.to_string())) }
}

fn create_number_value(n: f64) -> Value {
    Value { kind: Some(Kind::NumberValue(n)) }
}

fn create_bool_value(b: bool) -> Value {
    Value { kind: Some(Kind::BoolValue(b)) }
}

fn create_null_value() -> Value {
    Value { kind: Some(Kind::NullValue(0)) }
}

// Helper to create a table with various data types for comprehensive testing
async fn create_data_type_test_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: "timestamp".into() },
            TableColumnDefinition { name: "my_bigint".into(), field_type: "integer".into() },
            TableColumnDefinition { name: "my_money".into(), field_type: "decimal(19,4)".into() },
            TableColumnDefinition { name: "my_decimal".into(), field_type: "decimal(10,2)".into() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, table_def_request).await?;
    Ok(())
}

// Helper to create financial table for decimal tests
async fn create_financial_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() },
        ],
        indexes: vec![],
        links: vec![],
    };
    post_table_definition(pool, table_def_request).await?;
    Ok(())
}

// ========================================================================
// TEST CONTEXTS FOR DIFFERENT TABLE TYPES
// ========================================================================

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

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

#[fixture]
async fn data_type_test_context() -> DataTypeTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("dtype_profile_{}", unique_id);
    let table_name = format!("dtype_table_{}", unique_id);

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

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

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

#[fixture]
async fn financial_test_context() -> FinancialTestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("financial_profile_{}", unique_id);
    let table_name = format!("invoices_{}", unique_id);

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

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

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

// Helper to create initial record for data type tests
async fn create_initial_data_type_record(context: &DataTypeTestContext) -> 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"));

    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
}

// Helper to create initial record for financial tests
async fn create_initial_financial_record(context: &FinancialTestContext) -> 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"));

    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
}

// ========================================================================
// UNICODE AND SPECIAL CHARACTER TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_unicode_special_characters_comprehensive(
    #[future] test_context: TestContext,
) {
    let context = test_context.await;

    let special_strings = vec![
        "José María González",     // Accented characters
        "Москва",                  // Cyrillic
        "北京市",                  // Chinese
        "🚀 Tech Company 🌟",      // Emoji
        "Line\nBreak\tTab",        // Control characters
        "Quote\"Test'Apostrophe",  // Quotes
        "SQL'; DROP TABLE test; --", // SQL injection attempt
        "Price: $1,000.50 (50% off!)", // Special symbols
    ];

    for (i, test_string) in special_strings.into_iter().enumerate() {
        let record_id = create_initial_record(
            &context,
            HashMap::from([("firma".to_string(), string_to_proto_value("Original"))]),
        ).await;

        let mut update_data = HashMap::new();
        update_data.insert("firma".to_string(), string_to_proto_value(test_string));
        update_data.insert("kz".to_string(), string_to_proto_value(&format!("TEST{}", i)));

        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, "Failed for string: '{}'", test_string);

        // Verify the data was updated correctly
        let query = format!(
            r#"SELECT firma FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );
        let stored_firma: String = sqlx::query_scalar(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();
        assert_eq!(stored_firma, test_string.trim());
    }
}

#[rstest]
#[tokio::test]
async fn test_update_field_length_boundaries(#[future] test_context: TestContext) {
    let context = test_context.await;

    let length_test_cases = vec![
        ("1234567890123456", true),   // 16 chars - should fail
        ("123456789012345", false),   // 15 chars - should pass
        ("", false),                  // Empty - should pass (becomes NULL)
        ("1", false),                 // Single char - should pass
    ];

    for (test_string, should_fail) in length_test_cases {
        let record_id = create_initial_record(
            &context,
            HashMap::from([("telefon".to_string(), string_to_proto_value("555-1234"))]),
        ).await;

        let mut update_data = HashMap::new();
        update_data.insert("firma".to_string(), string_to_proto_value("Length Test Company"));
        update_data.insert("telefon".to_string(), string_to_proto_value(test_string));

        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;

        if should_fail {
            assert!(result.is_err(), "Should fail for telefon length: {}", test_string.len());
            if let Err(err) = result {
                assert_eq!(err.code(), tonic::Code::Internal);
                assert!(err.message().contains("Value too long for telefon"));
            }
        } else {
            assert!(result.is_ok(), "Should succeed for telefon length: {}", test_string.len());
        }
    }
}

// ========================================================================
// LARGE TEXT FIELD TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_large_text_fields(#[future] test_context: TestContext) {
    let context = test_context.await;

    let sizes = vec![1000, 5000, 10000];

    for size in sizes {
        let large_text = "A".repeat(size);
        let record_id = create_initial_record(
            &context,
            HashMap::from([("firma".to_string(), string_to_proto_value("Original"))]),
        ).await;

        let mut update_data = HashMap::new();
        update_data.insert("firma".to_string(), string_to_proto_value(&large_text));
        update_data.insert("ulica".to_string(), string_to_proto_value(&format!("Street with {} chars", size)));

        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, "Failed for size: {}", size);

        // Verify the large text was stored correctly
        let query = format!(
            r#"SELECT firma FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );
        let stored_firma: String = sqlx::query_scalar(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();
        assert_eq!(stored_firma.len(), size);
        assert_eq!(stored_firma, large_text);
    }
}

// ========================================================================
// DATA TYPE VALIDATION TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_correct_data_types_success(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;
    let record_id = create_initial_data_type_record(&context).await;

    let mut data = HashMap::new();
    data.insert("my_text".into(), create_string_value("Updated String"));
    data.insert("my_bool".into(), create_bool_value(true));
    data.insert("my_timestamp".into(), create_string_value("2024-06-15T15:30:00Z"));
    data.insert("my_bigint".into(), create_number_value(142.0));
    data.insert("my_money".into(), create_string_value("223.45"));
    data.insert("my_decimal".into(), create_string_value("899.99"));

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

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

    // Verify data was updated correctly
    let query = format!(
        r#"SELECT my_text, my_bool, my_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_bigint: i32 = row.get("my_bigint");

    assert_eq!(stored_text, "Updated String");
    assert_eq!(stored_bool, true);
    assert_eq!(stored_bigint, 142);
}

#[rstest]
#[tokio::test]
async fn test_update_type_mismatch_string_for_boolean(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;
    let record_id = create_initial_data_type_record(&context).await;

    let mut data = HashMap::new();
    data.insert("my_text".into(), create_string_value("Updated field"));
    data.insert("my_bool".into(), create_string_value("true")); // String instead of boolean

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

    let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
    assert!(result.is_err());

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::InvalidArgument);
        assert!(err.message().contains("Expected boolean for column 'my_bool'"));
    }
}

#[rstest]
#[tokio::test]
async fn test_update_invalid_timestamp_format(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;
    let record_id = create_initial_data_type_record(&context).await;

    let mut data = HashMap::new();
    data.insert("my_text".into(), create_string_value("Updated field"));
    data.insert("my_timestamp".into(), create_string_value("not-a-date"));

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

    let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
    assert!(result.is_err());

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::InvalidArgument);
        assert!(err.message().contains("Invalid timestamp for my_timestamp"));
    }
}

#[rstest]
#[tokio::test]
async fn test_update_float_for_integer_field(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;
    let record_id = create_initial_data_type_record(&context).await;

    let mut data = HashMap::new();
    data.insert("my_text".into(), create_string_value("Updated field"));
    data.insert("my_bigint".into(), create_number_value(123.45)); // Float for integer field

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

    let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
    assert!(result.is_err());

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

// ========================================================================
// DECIMAL/NUMERIC DATA TYPE TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_valid_decimal_string(
    #[future] financial_test_context: FinancialTestContext,
) {
    let context = financial_test_context.await;
    let record_id = create_initial_financial_record(&context).await;

    let mut data = HashMap::new();
    data.insert("product_name".into(), create_string_value("Updated Laptop"));
    data.insert("price".into(), create_string_value("1599.99"));
    data.insert("rate".into(), create_string_value("-0.54321"));

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

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

    let query = format!(
        r#"SELECT price, rate 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 price: Decimal = row.get("price");
    let rate: Decimal = row.get("rate");

    assert_eq!(price, Decimal::from_str("1599.99").unwrap());
    assert_eq!(rate, Decimal::from_str("-0.54321").unwrap());
}

#[rstest]
#[tokio::test]
async fn test_update_decimal_from_number_fails(
    #[future] financial_test_context: FinancialTestContext,
) {
    let context = financial_test_context.await;
    let record_id = create_initial_financial_record(&context).await;

    let mut data = HashMap::new();
    data.insert("product_name".into(), create_string_value("Updated Mouse"));
    data.insert("price".into(), create_number_value(85.50)); // Number instead of string

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

    let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
    assert!(result.is_err());

    let status = result.unwrap_err();
    assert_eq!(status.code(), tonic::Code::InvalidArgument);
    assert!(status.message().contains("Expected a string representation for decimal column 'price'"));
}

#[rstest]
#[tokio::test]
async fn test_update_invalid_decimal_string_fails(
    #[future] financial_test_context: FinancialTestContext,
) {
    let context = financial_test_context.await;
    let record_id = create_initial_financial_record(&context).await;

    let mut data = HashMap::new();
    data.insert("product_name".into(), create_string_value("Bad Data Update"));
    data.insert("price".into(), create_string_value("not-a-number"));

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

    let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
    assert!(result.is_err());
    let status = result.unwrap_err();
    assert_eq!(status.code(), tonic::Code::InvalidArgument);
    assert!(status.message().contains("Invalid decimal string format for column 'price'"));
}

// ========================================================================
// INTEGER BOUNDARY TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_integer_boundary_values(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;

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

    for (value, description) in boundary_values {
        let record_id = create_initial_data_type_record(&context).await;

        let mut data = HashMap::new();
        data.insert("my_text".into(), create_string_value(&format!("Boundary test: {}", description)));
        data.insert("my_bigint".into(), create_number_value(value));

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

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

#[rstest]
#[tokio::test]
async fn test_update_integer_overflow_rejection(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;

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

    for (value, description) in overflow_values {
        let record_id = create_initial_data_type_record(&context).await;

        let mut data = HashMap::new();
        data.insert("my_text".into(), create_string_value(&format!("Overflow test: {}", description)));
        data.insert("my_bigint".into(), create_number_value(value));

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

        let result = put_table_data(&context.pool, request, &context.indexer_tx).await;
        assert!(result.is_err(), "Should have failed for 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"));
        }
    }
}

// ========================================================================
// PERFORMANCE AND STRESS TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_rapid_sequential_operations(#[future] test_context: TestContext) {
    let context = test_context.await;

    let start_time = std::time::Instant::now();

    // Create initial records
    let mut record_ids = Vec::new();
    for i in 0..50 {
        let record_id = create_initial_record(
            &context,
            HashMap::from([("firma".to_string(), string_to_proto_value(&format!("Original Company {}", i)))]),
        ).await;
        record_ids.push(record_id);
    }

    // Perform rapid sequential updates
    for (i, record_id) in record_ids.iter().enumerate() {
        let mut update_data = HashMap::new();
        update_data.insert("firma".to_string(), string_to_proto_value(&format!("Updated Rapid Company {}", i)));
        update_data.insert("kz".to_string(), string_to_proto_value(&format!("RAP{}", i)));
        update_data.insert("telefon".to_string(), string_to_proto_value(&format!("+421{:09}", i)));

        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, "Rapid update {} should succeed", i);
    }

    let duration = start_time.elapsed();
    println!("50 rapid updates took: {:?}", duration);

    // Verify all records were updated
    let query = format!(
        r#"SELECT COUNT(*) FROM "{}"."{}" WHERE firma LIKE 'Updated Rapid Company%'"#,
        context.profile_name, context.table_name
    );

    let count: i64 = sqlx::query_scalar(&query)
        .fetch_one(&context.pool)
        .await
        .unwrap();
    assert_eq!(count, 50);
}

// ========================================================================
// ERROR SCENARIO TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_nonexistent_profile_error(#[future] test_context: TestContext) {
    let context = test_context.await;
    let record_id = create_initial_record(
        &context,
        HashMap::from([("firma".to_string(), string_to_proto_value("Test Company"))]),
    ).await;

    let mut update_data = HashMap::new();
    update_data.insert("firma".to_string(), string_to_proto_value("Updated Company"));

    let invalid_request = PutTableDataRequest {
        profile_name: "nonexistent_profile".into(),
        table_name: context.table_name.clone(),
        id: record_id,
        data: update_data,
    };

    let result = put_table_data(&context.pool, invalid_request, &context.indexer_tx).await;
    assert!(result.is_err());

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::NotFound);
        assert!(err.message().contains("Profile not found"));
    }
}

#[rstest]
#[tokio::test]
async fn test_update_nonexistent_table_error(#[future] test_context: TestContext) {
    let context = test_context.await;
    let record_id = create_initial_record(
        &context,
        HashMap::from([("firma".to_string(), string_to_proto_value("Test Company"))]),
    ).await;

    let mut update_data = HashMap::new();
    update_data.insert("firma".to_string(), string_to_proto_value("Updated Company"));

    let invalid_request = PutTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: "nonexistent_table".into(),
        id: record_id,
        data: update_data,
    };

    let result = put_table_data(&context.pool, invalid_request, &context.indexer_tx).await;
    assert!(result.is_err());

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::NotFound);
        assert!(err.message().contains("Table not found"));
    }
}

// ========================================================================
// NULL VALUE HANDLING TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_null_values_for_all_types(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;
    let record_id = create_initial_data_type_record(&context).await;

    let mut data = HashMap::new();
    data.insert("my_text".into(), create_string_value("Updated but keep nulls"));
    data.insert("my_bool".into(), create_null_value());
    data.insert("my_timestamp".into(), create_null_value());
    data.insert("my_bigint".into(), create_null_value());
    data.insert("my_money".into(), create_null_value());

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

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

    // Verify nulls were stored correctly
    let query = format!(
        r#"SELECT my_bool, my_timestamp, my_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_bool: Option<bool> = row.get("my_bool");
    let stored_timestamp: Option<chrono::DateTime<Utc>> = row.get("my_timestamp");
    let stored_bigint: Option<i32> = row.get("my_bigint");

    assert!(stored_bool.is_none());
    assert!(stored_timestamp.is_none());
    assert!(stored_bigint.is_none());
}

// ========================================================================
// VALID TIMESTAMP FORMAT TESTS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_valid_timestamp_formats(
    #[future] data_type_test_context: DataTypeTestContext,
) {
    let context = data_type_test_context.await;

    let valid_timestamps = vec![
        "2024-01-15T10:30:00Z",
        "2024-01-15T10:30:00+00:00",
        "2024-01-15T10:30:00.123Z",
        "2024-12-31T23:59:59Z",
        "1970-01-01T00:00:00Z", // Unix epoch
    ];

    for (i, timestamp) in valid_timestamps.into_iter().enumerate() {
        let record_id = create_initial_data_type_record(&context).await;

        let mut data = HashMap::new();
        data.insert("my_text".into(), create_string_value(&format!("Timestamp test {}", i)));
        data.insert("my_timestamp".into(), create_string_value(timestamp));

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

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

#[rstest]
#[tokio::test]
async fn test_update_indexer_command_generation(#[future] test_context: TestContext) {
    let context = test_context.await;
    let (indexer_tx, mut indexer_rx) = mpsc::channel(100);

    // Create initial record
    let initial_data = HashMap::from([
        ("firma".to_string(), string_to_proto_value("Initial Company")),
        ("kz".to_string(), string_to_proto_value("INIT123")),
    ]);

    let create_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: initial_data,
    };

    let create_response = post_table_data(&context.pool, create_request, &indexer_tx).await.unwrap();
    let record_id = create_response.inserted_id;

    // Consume the create indexer message
    let _create_command = tokio::time::timeout(
        tokio::time::Duration::from_millis(100),
        indexer_rx.recv()
    ).await.unwrap().unwrap();

    // Now perform an update and verify the indexer command
    let mut update_data = HashMap::new();
    update_data.insert("firma".to_string(), string_to_proto_value("Updated Indexer Test Company"));
    update_data.insert("kz".to_string(), string_to_proto_value("UPD123"));

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

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

    // Check that indexer command was sent for the UPDATE
    let indexer_command = tokio::time::timeout(
        tokio::time::Duration::from_millis(100),
        indexer_rx.recv()
    ).await;

    assert!(indexer_command.is_ok(), "Indexer command should be sent for PUT operation");
    let command = indexer_command.unwrap().unwrap();

    match command {
        IndexCommand::AddOrUpdate(data) => {
            assert_eq!(data.table_name, context.table_name);
            assert_eq!(data.row_id, record_id); // Should be the updated record ID
            // Verify it's an update, not an insert (same ID as the original record)
            assert_eq!(data.row_id, update_response.updated_id);
        },
        IndexCommand::Delete(_) => panic!("Expected AddOrUpdate command for PUT operation, got Delete"),
    }
}

#[rstest]
#[tokio::test]
async fn test_update_indexer_failure_resilience(#[future] test_context: TestContext) {
    let context = test_context.await;

    // Create initial record with working indexer
    let (working_indexer_tx, mut _working_rx) = mpsc::channel(100);
    let initial_data = HashMap::from([
        ("firma".to_string(), string_to_proto_value("Resilience Test Company")),
        ("kz".to_string(), string_to_proto_value("RES123")),
    ]);

    let create_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: initial_data,
    };

    let create_response = post_table_data(&context.pool, create_request, &working_indexer_tx).await.unwrap();
    let record_id = create_response.inserted_id;

    // Create a closed channel to simulate indexer failure for the update
    let (failed_indexer_tx, indexer_rx) = mpsc::channel(1);
    drop(indexer_rx); // Close receiver to simulate failure

    let mut update_data = HashMap::new();
    update_data.insert("firma".to_string(), string_to_proto_value("Updated Despite Indexer Failure"));
    update_data.insert("kz".to_string(), string_to_proto_value("FAIL123"));

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

    // Update should still succeed even if indexer fails
    let update_response = put_table_data(&context.pool, update_request, &failed_indexer_tx).await.unwrap();
    assert!(update_response.success);
    assert_eq!(update_response.updated_id, record_id);

    // Verify data was updated despite indexer failure
    let query = format!(
        r#"SELECT COUNT(*) FROM "{}"."{}" WHERE kz = 'FAIL123' AND firma = 'Updated Despite Indexer Failure'"#,
        context.profile_name, context.table_name
    );

    let count: i64 = sqlx::query_scalar(&query)
        .fetch_one(&context.pool)
        .await
        .unwrap();
    assert_eq!(count, 1);
}

// ========================================================================
// EMPTY STRINGS BECOME NULL COMPREHENSIVE TESTING
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_empty_strings_become_null(#[future] test_context: TestContext) {
    let context = test_context.await;

    let test_cases = vec![
        ("", "empty_string"),
        ("   ", "whitespace_only"),
        ("\t\n", "tabs_newlines"),
        ("\r\n  \t", "mixed_whitespace"),
        ("  Normal Value  ", "padded_value"),
        ("Keep This", "normal_value"),
    ];

    for (input, test_name) in test_cases {
        // Create initial record with a value
        let record_id = create_initial_record(
            &context,
            HashMap::from([
                ("firma".to_string(), string_to_proto_value("Test Empty Strings")),
                ("ulica".to_string(), string_to_proto_value("Original Street")),
                ("telefon".to_string(), string_to_proto_value("555-1234")),
            ]),
        ).await;

        let mut update_data = HashMap::new();
        update_data.insert("firma".to_string(), string_to_proto_value(&format!("Test {}", test_name)));
        update_data.insert("ulica".to_string(), string_to_proto_value(input));
        update_data.insert("telefon".to_string(), string_to_proto_value(input));

        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, "Failed for test case: {}", test_name);

        // Check what was actually stored
        let query = format!(
            r#"SELECT ulica, telefon 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_ulica: Option<String> = row.get("ulica");
        let stored_telefon: Option<String> = row.get("telefon");

        let trimmed = input.trim();
        if trimmed.is_empty() {
            assert!(stored_ulica.is_none(), "Empty/whitespace string should be NULL for ulica in: {}", test_name);
            assert!(stored_telefon.is_none(), "Empty/whitespace string should be NULL for telefon in: {}", test_name);
        } else {
            assert_eq!(stored_ulica.unwrap(), trimmed, "String should be trimmed for ulica in: {}", test_name);
            assert_eq!(stored_telefon.unwrap(), trimmed, "String should be trimmed for telefon in: {}", test_name);
        }
    }
}

#[rstest]
#[tokio::test]
async fn test_update_empty_string_to_null_then_to_value(#[future] test_context: TestContext) {
    let context = test_context.await;

    // Create initial record with values
    let record_id = create_initial_record(
        &context,
        HashMap::from([
            ("firma".to_string(), string_to_proto_value("String Transition Test")),
            ("ulica".to_string(), string_to_proto_value("Original Street")),
            ("telefon".to_string(), string_to_proto_value("555-1234")),
        ]),
    ).await;

    // First update: set fields to empty strings (should become NULL)
    let mut update_to_empty = HashMap::new();
    update_to_empty.insert("ulica".to_string(), string_to_proto_value("   ")); // Whitespace only
    update_to_empty.insert("telefon".to_string(), string_to_proto_value("")); // Empty string

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

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

    // Verify fields are now NULL
    let query = format!(
        r#"SELECT ulica, telefon 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 ulica1: Option<String> = row.get("ulica");
    let telefon1: Option<String> = row.get("telefon");

    assert!(ulica1.is_none(), "ulica should be NULL after empty string update");
    assert!(telefon1.is_none(), "telefon should be NULL after empty string update");

    // Second update: set fields back to actual values
    let mut update_to_values = HashMap::new();
    update_to_values.insert("ulica".to_string(), string_to_proto_value("New Street"));
    update_to_values.insert("telefon".to_string(), string_to_proto_value("999-8888"));

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

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

    // Verify fields now have the new values
    let row2 = sqlx::query(&query)
        .bind(record_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let ulica2: Option<String> = row2.get("ulica");
    let telefon2: Option<String> = row2.get("telefon");

    assert_eq!(ulica2.unwrap(), "New Street", "ulica should have new value");
    assert_eq!(telefon2.unwrap(), "999-8888", "telefon should have new value");
}

// ========================================================================
// COMPREHENSIVE NULL HANDLING EDGE CASES
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_mixed_null_transitions_comprehensive(#[future] test_context: TestContext) {
    let context = test_context.await;

    // Create initial record with mixed values and nulls
    let record_id = create_initial_record(
        &context,
        HashMap::from([
            ("firma".to_string(), string_to_proto_value("Null Transition Test")),
            ("ulica".to_string(), string_to_proto_value("Original Street")),
            ("telefon".to_string(), string_to_proto_value("555-1234")),
            ("mesto".to_string(), string_to_proto_value("Original City")),
        ]),
    ).await;

    // Define a sequence of updates that test various NULL transitions
    let update_sequences = vec![
        (
            "Step 1: Mix of values, nulls, and empty strings",
            HashMap::from([
                ("ulica".to_string(), string_to_proto_value("Updated Street")),
                ("telefon".to_string(), Value { kind: Some(Kind::NullValue(0)) }), // Explicit NULL
                ("mesto".to_string(), string_to_proto_value("   ")), // Empty string -> NULL
                ("kz".to_string(), string_to_proto_value("NEW123")), // New value
            ])
        ),
        (
            "Step 2: Convert NULLs back to values",
            HashMap::from([
                ("telefon".to_string(), string_to_proto_value("999-8888")), // NULL -> value
                ("mesto".to_string(), string_to_proto_value("New City")), // NULL -> value
                ("ulica".to_string(), Value { kind: Some(Kind::NullValue(0)) }), // value -> NULL
            ])
        ),
        (
            "Step 3: Mix empty strings and explicit nulls",
            HashMap::from([
                ("ulica".to_string(), string_to_proto_value("")), // NULL -> empty string -> NULL
                ("telefon".to_string(), string_to_proto_value("\t\n ")), // value -> whitespace -> NULL
                ("mesto".to_string(), string_to_proto_value("Final City")), // value -> value
                ("kz".to_string(), Value { kind: Some(Kind::NullValue(0)) }), // value -> NULL
            ])
        ),
    ];

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

        let response = put_table_data(&context.pool, request, &context.indexer_tx).await.unwrap();
        assert!(response.success, "Failed at: {}", step_description);

        // Verify the state after this update
        let query = format!(
            r#"SELECT ulica, telefon, mesto, kz FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );

        let row = sqlx::query(&query)
            .bind(record_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        // Verify each field based on what should have happened
        for (field_name, expected_value) in update_data.iter() {
            let db_value: Option<String> = row.get(field_name.as_str());

            match expected_value.kind.as_ref().unwrap() {
                Kind::StringValue(s) => {
                    let trimmed = s.trim();
                    if trimmed.is_empty() {
                        assert!(db_value.is_none(),
                            "Field {} should be NULL after empty string in {}", field_name, step_description);
                    } else {
                        assert_eq!(db_value.unwrap(), trimmed,
                            "Field {} should have trimmed value in {}", field_name, step_description);
                    }
                },
                Kind::NullValue(_) => {
                    assert!(db_value.is_none(),
                        "Field {} should be NULL after explicit null in {}", field_name, step_description);
                },
                _ => {}
            }
        }
    }
}

// ========================================================================
// ADVANCED INDEXER INTEGRATION SCENARIOS
// ========================================================================

#[rstest]
#[tokio::test]
async fn test_update_indexer_multiple_rapid_updates(#[future] test_context: TestContext) {
    let context = test_context.await;
    let (indexer_tx, mut indexer_rx) = mpsc::channel(1000);

    // FIX: Call post_table_data directly instead of using the helper
    let create_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: HashMap::from([(
            "firma".to_string(),
            string_to_proto_value("Rapid Update Test"),
        )]),
    };
    let response = post_table_data(&context.pool, create_request, &indexer_tx)
        .await
        .unwrap();
    let record_id = response.inserted_id;

    // Consume the initial create command
    let _create_command = tokio::time::timeout(
        tokio::time::Duration::from_millis(100),
        indexer_rx.recv(),
    )
    .await
    .expect("Timeout waiting for create command") // More descriptive panic
    .expect("Channel closed unexpectedly");

    // ... rest of the test remains the same
    let num_updates = 10;
    for i in 0..num_updates {
        let mut update_data = HashMap::new();
        update_data.insert(
            "firma".to_string(),
            string_to_proto_value(&format!("Rapid Update {}", i)),
        );
        update_data.insert(
            "kz".to_string(),
            string_to_proto_value(&format!("RAP{}", i)),
        );

        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, &indexer_tx)
            .await
            .unwrap();
        assert!(response.success, "Rapid update {} should succeed", i);
    }

    // Verify all update commands were sent to indexer
    let mut received_commands = 0;
    while let Ok(command) = tokio::time::timeout(
        tokio::time::Duration::from_millis(50),
        indexer_rx.recv()
    ).await {
        if command.is_none() { break; }
        match command.unwrap() {
            IndexCommand::AddOrUpdate(data) => {
                assert_eq!(data.table_name, context.table_name);
                assert_eq!(data.row_id, record_id);
                received_commands += 1;
            },
            IndexCommand::Delete(_) => panic!("Unexpected Delete command during updates"),
        }
    }

    assert_eq!(received_commands, num_updates, "Should receive indexer command for each update");

    // Verify final state
    let query = format!(
        r#"SELECT firma, kz 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 final_firma: String = row.get("firma");
    let final_kz: String = row.get("kz");

    assert_eq!(final_firma, format!("Rapid Update {}", num_updates - 1));
    assert_eq!(final_kz, format!("RAP{}", num_updates - 1));
}
