// tests/tables_data/handlers/post_table_data_test2.rs

// ========= Additional helper functions for test2 =========

async fn create_test_indexer_channel() -> mpsc::Sender<IndexCommand> {
    let (tx, mut rx) = mpsc::channel(100);

    // Spawn a task to consume indexer messages to prevent blocking
    tokio::spawn(async move {
        while let Some(_) = rx.recv().await {
            // Just consume the messages
        }
    });

    tx
}

// ========= Extended Data Type Validation Tests =========

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

    // Test setting the deleted flag with string (should fail)
    let mut data = HashMap::new();
    data.insert("firma".into(), "System Test Company".to_string());
    data.insert("deleted".into(), "true".to_string()); // String instead of boolean

    let result = post_table_data(&context.pool, create_table_request(&context, data), &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 'deleted'"));
    }
}

// ========= String Processing and Edge Cases =========

#[rstest]
#[tokio::test]
async fn test_unicode_special_characters_comprehensive(#[future] test_context: TestContext) {
    let context = test_context.await;
    let indexer_tx = create_test_indexer_channel().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 mut data = HashMap::new();
        data.insert("firma".into(), test_string.to_string());
        data.insert("kz".into(), format!("TEST{}", i));

        let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
        assert!(response.success, "Failed for string: '{}'", test_string);

        // Verify the data was stored correctly
        let query = format!(
            r#"SELECT firma FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );

        let stored_firma: Option<String> = sqlx::query_scalar::<_, Option<String>>(&query)
            .bind(response.inserted_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        assert_eq!(stored_firma.unwrap(), test_string.trim());
    }
}

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

    // Test telefon field length validation (should reject >15 chars)
    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 mut data = HashMap::new();
        data.insert("firma".into(), "Length Test Company".to_string());
        data.insert("telefon".into(), test_string.to_string());

        let result = post_table_data(&context.pool, create_table_request(&context, data), &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());
        }
    }
}

// ========= NULL vs Empty String Handling =========

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

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

    for (input, test_name) in test_cases {
        let mut data = HashMap::new();
        data.insert("firma".into(), format!("Test {}", test_name));
        data.insert("ulica".into(), input.to_string());

        let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
        assert!(response.success, "Failed for test case: {}", test_name);

        // Check what was actually stored
        let query = format!(
            r#"SELECT ulica FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );

        let stored_ulica: Option<String> = sqlx::query_scalar::<_, Option<String>>(&query)
            .bind(response.inserted_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

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

// ========= Concurrent Operations Testing =========

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

    use futures::future::join_all;

    // Create multiple concurrent insert operations
    let futures = (0..10).map(|i| {
        let context = context.clone();
        async move {
            let indexer_tx = create_test_indexer_channel().await;
            let mut data = HashMap::new();
            data.insert("firma".into(), format!("Concurrent Company {}", i));
            data.insert("kz".into(), format!("CONC{}", i));
            data.insert("mesto".into(), format!("City {}", i));

            post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await
        }
    });

    let results = join_all(futures).await;

    // All inserts should succeed
    for (i, result) in results.into_iter().enumerate() {
        assert!(result.is_ok(), "Concurrent insert {} should succeed", i);
    }

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

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

// ========= Error Scenarios =========

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

    let mut data = HashMap::new();
    data.insert("firma".into(), "Valid Company".to_string());
    data.insert("nonexistent_column".into(), "Invalid".to_string());

    let result = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await;
    assert!(result.is_err());

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

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

    // Try to insert completely empty data
    let data = HashMap::new();

    let result = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await;
    assert!(result.is_err());

    if let Err(err) = result {
        assert_eq!(err.code(), tonic::Code::InvalidArgument);
        assert!(err.message().contains("No valid columns to insert"));
    }
}

// ========= Performance and Stress Testing =========

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

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

    // Perform rapid sequential inserts
    for i in 0..50 {
        let mut data = HashMap::new();
        data.insert("firma".into(), format!("Rapid Company {}", i));
        data.insert("kz".into(), format!("RAP{}", i));
        data.insert("telefon".into(), format!("+421{:09}", i));

        let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
        assert!(response.success, "Rapid insert {} should succeed", i);
    }

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

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

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

// ========= SQL Injection Protection =========

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

    let injection_attempts = vec![
        "'; DROP TABLE users; --",
        "1; DELETE FROM adresar; --",
        "admin'; UPDATE adresar SET firma='hacked' WHERE '1'='1",
        "' OR '1'='1",
        "'; INSERT INTO adresar (firma) VALUES ('injected'); --",
        "Robert'); DROP TABLE students; --", // Classic Bobby Tables
    ];

    let injection_count = injection_attempts.len();

    for (i, injection) in injection_attempts.into_iter().enumerate() {
        let mut data = HashMap::new();
        data.insert("firma".into(), injection.to_string());
        data.insert("kz".into(), format!("INJ{}", i));

        // These should all succeed because values are properly parameterized
        let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
        assert!(response.success, "SQL injection attempt should be safely handled: {}", injection);

        // Verify the injection attempt was stored as literal text
        let query = format!(
            r#"SELECT firma FROM "{}"."{}" WHERE id = $1"#,
            context.profile_name, context.table_name
        );

        let stored_firma: String = sqlx::query_scalar::<_, String>(&query)
            .bind(response.inserted_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        assert_eq!(stored_firma, injection);
    }

    // Verify the table still exists and has the expected number of records
    let query = format!(
        r#"SELECT COUNT(*) FROM "{}"."{}" WHERE kz LIKE 'INJ%'"#,
        context.profile_name, context.table_name
    );

    let count: i64 = sqlx::query_scalar::<_, i64>(&query)
        .fetch_one(&context.pool)
        .await
        .unwrap();
    assert_eq!(count, injection_count as i64);
}

// ========= Large Data Testing =========

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

    // Test various large text sizes (except telefon which has length limits)
    let sizes = vec![1000, 5000, 10000];

    for size in sizes {
        let large_text = "A".repeat(size);

        let mut data = HashMap::new();
        data.insert("firma".into(), large_text.clone());
        data.insert("ulica".into(), format!("Street with {} chars", size));

        let response = post_table_data(&context.pool, create_table_request(&context, data), &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::<_, String>(&query)
            .bind(response.inserted_id)
            .fetch_one(&context.pool)
            .await
            .unwrap();

        assert_eq!(stored_firma.len(), size);
        assert_eq!(stored_firma, large_text);
    }
}

// ========= Indexer Integration Testing =========

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

    let mut data = HashMap::new();
    data.insert("firma".into(), "Indexer Test Company".to_string());
    data.insert("kz".into(), "IDX123".to_string());

    let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
    assert!(response.success);

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

    assert!(indexer_command.is_ok());
    let command = indexer_command.unwrap().unwrap();

    match command {
        IndexCommand::AddOrUpdate(data) => {
            assert_eq!(data.table_name, context.table_name);
            assert_eq!(data.row_id, response.inserted_id);
        },
        IndexCommand::Delete(_) => panic!("Expected AddOrUpdate command, got Delete"),
    }
}

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

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

    let mut data = HashMap::new();
    data.insert("firma".into(), "Resilience Test Company".to_string());
    data.insert("kz".into(), "RES123".to_string());

    // Insert should still succeed even if indexer fails
    let response = post_table_data(&context.pool, create_table_request(&context, data), &indexer_tx).await.unwrap();
    assert!(response.success);

    // Verify data was inserted despite indexer failure
    let query = format!(
        r#"SELECT COUNT(*) FROM "{}"."{}" WHERE kz = 'RES123'"#,
        context.profile_name, context.table_name
    );

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

// ========= Profile and Table Validation =========

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

    let mut data = HashMap::new();
    data.insert("firma".into(), "Test Company".to_string());

    let invalid_request = PostTableDataRequest {
        profile_name: "nonexistent_profile".into(),
        table_name: context.table_name.clone(),
        data: convert_to_proto_values(data),
    };

    let result = post_table_data(&context.pool, invalid_request, &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_nonexistent_table_error(#[future] test_context: TestContext) {
    let context = test_context.await;
    let indexer_tx = create_test_indexer_channel().await;

    let mut data = HashMap::new();
    data.insert("firma".into(), "Test Company".to_string());

    let invalid_request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: "nonexistent_table".into(),
        data: convert_to_proto_values(data),
    };

    let result = post_table_data(&context.pool, invalid_request, &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"));
    }
}
