// tests/table_script/comprehensive_error_scenarios_tests.rs

use crate::common::setup_isolated_db;
use server::table_script::handlers::post_table_script::post_table_script;  // Fixed import
use common::proto::komp_ac::table_script::PostTableScriptRequest;
use rstest::*;
use serde_json::json;
use sqlx::PgPool;

/// Helper function to create a test table with specified columns
async fn create_test_table(
    pool: &PgPool,
    schema_id: i64,
    table_name: &str,
    columns: Vec<(&str, &str)>,
) -> i64 {
    let column_definitions: Vec<String> = columns
        .iter()
        .map(|(name, type_def)| format!("\"{}\" {}", name, type_def))
        .collect();

    let columns_json = json!(column_definitions);
    let indexes_json = json!([]);

    sqlx::query_scalar!(
        r#"INSERT INTO table_definitions (schema_id, table_name, columns, indexes)
           VALUES ($1, $2, $3, $4) RETURNING id"#,
        schema_id,
        table_name,
        columns_json,
        indexes_json
    )
    .fetch_one(pool)
    .await
    .expect("Failed to create test table")
}

/// Helper function to get default schema ID
async fn get_default_schema_id(pool: &PgPool) -> i64 {
    sqlx::query_scalar!("SELECT id FROM schemas WHERE name = 'default'")
        .fetch_one(pool)
        .await
        .expect("Failed to get default schema ID")
}

/// Test fixture providing detailed error scenarios
#[fixture]
#[once]
pub fn error_scenarios() -> Vec<(&'static str, &'static str, Vec<&'static str>, &'static str)> {
    vec![
        // [target_column, script, expected_error_keywords, description]
        (
            "bigint_target",
            r#"(+ "10" "20")"#,
            vec!["cannot", "create", "script", "bigint"],
            "BIGINT target column should be rejected"
        ),
        (
            "date_target",
            r#"(+ "10" "20")"#,
            vec!["cannot", "create", "script", "date"],
            "DATE target column should be rejected"
        ),
        (
            "timestamp_target",
            r#"(+ "10" "20")"#,
            vec!["cannot", "create", "script", "timestamptz"],
            "TIMESTAMPTZ target column should be rejected"
        ),
        (
            "valid_numeric",
            r#"(+ (steel_get_column "error_table" "text_col") "10")"#,
            vec!["mathematical", "text", "operations"],
            "TEXT in mathematical operations should be rejected"
        ),
        (
            "valid_numeric",
            r#"(* (steel_get_column "error_table" "boolean_col") "5")"#,
            vec!["mathematical", "boolean", "operations"],
            "BOOLEAN in mathematical operations should be rejected"
        ),
        (
            "valid_numeric",
            r#"(/ (steel_get_column "error_table" "bigint_col") "2")"#,
            vec!["mathematical", "bigint", "operations"],
            "BIGINT in mathematical operations should be rejected"
        ),
        (
            "valid_numeric",
            r#"(sqrt (steel_get_column "error_table" "date_col"))"#,
            vec!["mathematical", "date", "operations"],
            "DATE in mathematical operations should be rejected"
        ),
        (
            "valid_numeric",
            r#"(+ (steel_get_column "nonexistent_table" "column") "10")"#,
            vec!["table", "does not exist", "nonexistent_table"],
            "Nonexistent table should be rejected"
        ),
        (
            "valid_numeric",
            r#"(+ (steel_get_column "error_table" "nonexistent_column") "10")"#,
            vec!["column", "does not exist", "nonexistent_column"],
            "Nonexistent column should be rejected"
        ),
    ]
}

#[rstest]
#[tokio::test]
async fn test_comprehensive_error_scenarios(
    error_scenarios: &Vec<(&'static str, &'static str, Vec<&'static str>, &'static str)>,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    // Create comprehensive error test table
    let columns = vec![
        // Valid types
        ("valid_numeric", "NUMERIC(10, 2)"),
        ("valid_integer", "INTEGER"),

        // Invalid for math operations
        ("text_col", "TEXT"),
        ("boolean_col", "BOOLEAN"),
        ("bigint_col", "BIGINT"),
        ("date_col", "DATE"),
        ("timestamp_col", "TIMESTAMPTZ"),

        // Invalid target types
        ("bigint_target", "BIGINT"),
        ("date_target", "DATE"),
        ("timestamp_target", "TIMESTAMPTZ"),
    ];

    let table_id = create_test_table(&pool, schema_id, "error_table", columns).await;

    for (target_column, script, expected_keywords, description) in error_scenarios {
        let request = PostTableScriptRequest {
            table_definition_id: table_id,
            target_column: target_column.to_string(),
            script: script.to_string(),
            description: description.to_string(),  // Fixed: removed Some()
        };

        let result = post_table_script(&pool, request).await;
        assert!(result.is_err(), "{}", description);

        let error_message = result.unwrap_err().to_string().to_lowercase();

        for keyword in expected_keywords {
            assert!(
                error_message.contains(&keyword.to_lowercase()),
                "Error message should contain '{}' for: {}. Got: {}",
                keyword, description, error_message
            );
        }
    }
}

#[rstest]
#[case::empty_script("", "Empty script should be rejected")]
#[case::malformed_parentheses("(+ 10 20", "Unbalanced parentheses should be rejected")]
#[tokio::test]
async fn test_malformed_script_scenarios(
    #[case] script: &str,
    #[case] description: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![("result", "NUMERIC(10, 2)")];
    let table_id = create_test_table(&pool, schema_id, "malformed_test", columns).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: script.to_string(),
        description: description.to_string(),
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_err(), "{}", description);
}

#[rstest]
#[case::invalid_function("(invalid_function 10 20)", "Invalid function should pass basic validation")]
#[case::mixed_quotes(r#"(' "10" 20)"#, "Mixed quotes should pass basic validation")]
#[tokio::test]
async fn test_advanced_validation_scenarios(
    #[case] script: &str,
    #[case] description: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![("result", "NUMERIC(10, 2)")];
    let table_id = create_test_table(&pool, schema_id, "advanced_test", columns).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: script.to_string(),
        description: description.to_string(),
    };

    let result = post_table_script(&pool, request).await;
    // These might pass basic validation but fail later in Steel engine
    // That's expected behavior - basic validation only catches structural errors
    match result {
        Ok(_) => {
            println!("Note: {} passed validation (will be caught by Steel engine at runtime)", description);
        }
        Err(_) => {
            println!("Note: {} was caught by validation", description);
        }
    }
    // Don't assert failure here - these are edge cases that might be handled differently
}

async fn create_table_link(pool: &PgPool, source_table_id: i64, linked_table_id: i64) {
    sqlx::query!(
        "INSERT INTO table_definition_links (source_table_id, linked_table_id, is_required)
         VALUES ($1, $2, false)",
        source_table_id,
        linked_table_id
    )
    .execute(pool)
    .await
    .expect("Failed to create table link");
}

#[tokio::test]
async fn test_dependency_cycle_detection() {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    // Create table_b first
    let table_b_columns = vec![
        ("value_b", "NUMERIC(10, 2)"),
        ("result_b", "NUMERIC(10, 2)"),
    ];
    let table_b_id = create_test_table(&pool, schema_id, "table_b", table_b_columns).await;

    // Create table_a
    let table_a_columns = vec![
        ("value_a", "NUMERIC(10, 2)"),
        ("result_a", "NUMERIC(10, 2)"),
    ];
    let table_a_id = create_test_table(&pool, schema_id, "table_a", table_a_columns).await;

    // CREATE BOTH LINKS for circular dependency testing
    create_table_link(&pool, table_a_id, table_b_id).await; // table_a -> table_b
    create_table_link(&pool, table_b_id, table_a_id).await; // table_b -> table_a

    // First dependency should work
    let script_a = r#"(+ (steel_get_column "table_b" "value_b") "10")"#;
    let request_a = PostTableScriptRequest {
        table_definition_id: table_a_id,
        target_column: "result_a".to_string(),
        script: script_a.to_string(),
        description: "First dependency".to_string(),
    };

    let result_a = post_table_script(&pool, request_a).await;
    assert!(result_a.is_ok(), "First dependency should succeed");

    // Try circular dependency - should now work since links exist both ways
    let script_b = r#"(* (steel_get_column "table_a" "result_a") "2")"#;
    let request_b = PostTableScriptRequest {
        table_definition_id: table_b_id,
        target_column: "result_b".to_string(),
        script: script_b.to_string(),
        description: "Circular dependency attempt".to_string(),
    };

    let result_b = post_table_script(&pool, request_b).await;

    // This should either succeed or detect the cycle
    match result_b {
        Ok(_) => {
            // Implementation allows this pattern
        }
        Err(error) => {
            // Implementation detects circular dependencies
            let error_msg = error.to_string().to_lowercase();
            assert!(
                error_msg.contains("cycle") || error_msg.contains("circular"),
                "Circular dependency should be detected properly: {}",
                error.to_string()
            );
        }
    }
}

#[rstest]
#[case::extremely_long_column_name("a".repeat(100), "Extremely long column name")]
#[case::special_chars_table("test-table", "Table with special characters")]
#[case::unicode_column("测试列", "Unicode column name")]
#[tokio::test]
async fn test_edge_case_identifiers(
    #[case] identifier: String,
    #[case] description: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![("result", "NUMERIC(10, 2)")];
    let table_id = create_test_table(&pool, schema_id, "identifier_test", columns).await;

    // Test with edge case identifier in script
    let script = format!(r#"(+ "10" "20")"#); // Simple script, focus on identifier handling

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script,
        description: description.to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;

    // The behavior may vary based on identifier validation rules
    match result {
        Ok(_) => {
            // Identifier was accepted
        }
        Err(error) => {
            // Should provide meaningful error message
            let error_msg = error.to_string();
            assert!(
                !error_msg.contains("panic") && !error_msg.contains("internal error"),
                "Should provide meaningful error for edge case identifier: {}",
                error_msg
            );
        }
    }
}

#[tokio::test]
async fn test_sql_injection_prevention() {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![("result", "NUMERIC(10, 2)")];
    let table_id = create_test_table(&pool, schema_id, "injection_test", columns).await;

    // Attempt SQL injection through script content
    let malicious_scripts = vec![
        r#"(+ "10"; DROP TABLE injection_test; --" "20")"#,
        r#"(steel_query_sql "SELECT * FROM schemas; DROP TABLE injection_test;")"#,
        r#"(steel_get_column "injection_test\"; DROP TABLE schemas; --" "result")"#,
    ];

    for script in malicious_scripts {
        let request = PostTableScriptRequest {
            table_definition_id: table_id,
            target_column: "result".to_string(),
            script: script.to_string(),
            description: "SQL injection attempt".to_string(),  // Fixed: removed Some()
        };

        let result = post_table_script(&pool, request).await;

        // Should either reject the script or handle it safely
        match result {
            Ok(_) => {
                // If accepted, verify that no injection occurred by checking table still exists
                let table_exists = sqlx::query_scalar!(
                    "SELECT COUNT(*) FROM table_definitions WHERE id = $1",
                    table_id
                )
                .fetch_one(&pool)
                .await
                .expect("Should be able to check table existence");

                assert_eq!(table_exists, Some(1), "Table should still exist after potential injection attempt");
            }
            Err(_) => {
                // Script was rejected, which is also acceptable
            }
        }
    }
}

#[tokio::test]
async fn test_performance_with_deeply_nested_expressions() {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("x", "NUMERIC(15, 8)"),
        ("performance_result", "NUMERIC(25, 12)"),
    ];
    let table_id = create_test_table(&pool, schema_id, "performance_test", columns).await;

    // Create a very deeply nested expression
    let deeply_nested_script = r#"
        (+
            (+
                (+
                    (+ (steel_get_column "performance_test" "x") "1")
                    (+ (steel_get_column "performance_test" "x") "2"))
                (+
                    (+ (steel_get_column "performance_test" "x") "3")
                    (+ (steel_get_column "performance_test" "x") "4")))
            (+
                (+
                    (+ (steel_get_column "performance_test" "x") "5")
                    (+ (steel_get_column "performance_test" "x") "6"))
                (+
                    (+ (steel_get_column "performance_test" "x") "7")
                    (+ (steel_get_column "performance_test" "x") "8"))))
    "#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "performance_result".to_string(),
        script: deeply_nested_script.to_string(),
        description: "Performance test with deeply nested expressions".to_string(),  // Fixed: removed Some()
    };

    let start_time = std::time::Instant::now();
    let result = post_table_script(&pool, request).await;
    let duration = start_time.elapsed();

    assert!(result.is_ok(), "Deeply nested expression should succeed");
    assert!(
        duration.as_millis() < 5000,
        "Script validation should complete within reasonable time (5s), took: {:?}",
        duration
    );
}

#[tokio::test]
async fn test_concurrent_script_creation() {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("value", "NUMERIC(10, 2)"),
        ("result1", "NUMERIC(10, 2)"),
        ("result2", "NUMERIC(10, 2)"),
        ("result3", "NUMERIC(10, 2)"),
    ];
    let table_id = create_test_table(&pool, schema_id, "concurrent_test", columns).await;

    // Create multiple scripts concurrently on different columns
    let handles = vec![
        tokio::spawn({
            let pool = pool.clone();
            async move {
                let request = PostTableScriptRequest {
                    table_definition_id: table_id,
                    target_column: "result1".to_string(),
                    script: r#"(+ (steel_get_column "concurrent_test" "value") "10")"#.to_string(),
                    description: "Concurrent script 1".to_string(),  // Fixed: removed Some()
                };
                post_table_script(&pool, request).await
            }
        }),
        tokio::spawn({
            let pool = pool.clone();
            async move {
                let request = PostTableScriptRequest {
                    table_definition_id: table_id,
                    target_column: "result2".to_string(),
                    script: r#"(* (steel_get_column "concurrent_test" "value") "2")"#.to_string(),
                    description: "Concurrent script 2".to_string(),  // Fixed: removed Some()
                };
                post_table_script(&pool, request).await
            }
        }),
        tokio::spawn({
            let pool = pool.clone();
            async move {
                let request = PostTableScriptRequest {
                    table_definition_id: table_id,
                    target_column: "result3".to_string(),
                    script: r#"(/ (steel_get_column "concurrent_test" "value") "3")"#.to_string(),
                    description: "Concurrent script 3".to_string(),  // Fixed: removed Some()
                };
                post_table_script(&pool, request).await
            }
        }),
    ];

    // Wait for all concurrent operations to complete
    let results = futures::future::join_all(handles).await;

    // All should succeed
    for (i, result) in results.into_iter().enumerate() {
        let script_result = result.expect("Task should not panic");
        assert!(script_result.is_ok(), "Concurrent script {} should succeed", i + 1);
    }
}

#[tokio::test]
async fn test_error_message_localization_and_clarity() {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("text_col", "TEXT"),
        ("result", "NUMERIC(10, 2)"),
    ];
    let table_id = create_test_table(&pool, schema_id, "error_clarity_test", columns).await;

    // Test that error messages are clear and specific
    let script = r#"(+ (steel_get_column "error_clarity_test" "text_col") "10")"#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: script.to_string(),
        description: "Error message clarity test".to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_err(), "Should reject TEXT in mathematical operations");

    let error_message = result.unwrap_err().to_string();

    // Verify error message quality
    assert!(
        error_message.len() > 20,
        "Error message should be descriptive, got: {}",
        error_message
    );

    assert!(
        !error_message.contains("panic") && !error_message.contains("unwrap"),
        "Error message should not expose internal implementation details: {}",
        error_message
    );

    // Should mention specific problematic elements
    let error_lower = error_message.to_lowercase();
    let relevant_keywords = vec!["text", "mathematical", "operation", "column"];
    let keyword_count = relevant_keywords.iter()
        .filter(|&&keyword| error_lower.contains(keyword))
        .count();

    assert!(
        keyword_count >= 2,
        "Error message should contain relevant keywords. Got: {}",
        error_message
    );
}
