// tests/table_script/mathematical_operations_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 all mathematical operations that should work with steel_decimal
#[fixture]
#[once]
pub fn steel_decimal_operations() -> Vec<(&'static str, &'static str, &'static str)> {
    vec![
        // Basic arithmetic
        ("+", "addition", "binary"),
        ("-", "subtraction", "binary"),
        ("*", "multiplication", "binary"),
        ("/", "division", "binary"),

        // Advanced math
        ("sqrt", "square_root", "unary"),
        ("abs", "absolute_value", "unary"),
        ("min", "minimum", "binary"),
        ("max", "maximum", "binary"),
        ("pow", "power", "binary"),

        // Comparison operations
        (">", "greater_than", "binary"),
        ("<", "less_than", "binary"),
        ("=", "equals", "binary"),
        (">=", "greater_equal", "binary"),
        ("<=", "less_equal", "binary"),
    ]
}

/// Test fixture providing precision test scenarios
#[fixture]
#[once]
pub fn precision_scenarios() -> Vec<(&'static str, &'static str, &'static str)> {
    vec![
        ("NUMERIC(5, 2)", "999.99", "Low precision"),
        ("NUMERIC(10, 4)", "999999.9999", "Medium precision"),
        ("NUMERIC(28, 15)", "9999999999999.999999999999999", "High precision"),
        ("NUMERIC(14, 4)", "9999999999.9999", "Currency precision"),
        ("NUMERIC(5, 4)", "9.9999", "Percentage precision"),
    ]
}

#[rstest]
#[case::basic_addition("+", "10", "20")]
#[case::decimal_multiplication("*", "100.50", "2.5")]
#[case::high_precision_division("/", "123.456789012345", "3")]
#[case::scientific_notation("+", "1.5e-10", "2.3e-8")]
#[tokio::test]
async fn test_steel_decimal_literal_operations(
    #[case] operation: &str,
    #[case] value1: &str,
    #[case] value2: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![("result", "NUMERIC(30, 15)")];
    let table_id = create_test_table(&pool, schema_id, "literal_test", columns).await;

    let script = format!(r#"({} "{}" "{}")"#, operation, value1, value2);

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script,
        description: format!("Steel decimal {} with literals", operation),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(
        result.is_ok(),
        "Steel decimal {} with literals should succeed",
        operation
    );
}

#[rstest]
#[case::integer_basic("INTEGER", "+")]
#[case::integer_multiplication("INTEGER", "*")]
#[case::integer_sqrt("INTEGER", "sqrt")]
#[case::numeric_basic("NUMERIC(10, 2)", "+")]
#[case::numeric_division("NUMERIC(15, 6)", "/")]
#[case::numeric_power("NUMERIC(8, 4)", "pow")]
#[case::high_precision("NUMERIC(28, 15)", "*")]
#[tokio::test]
async fn test_steel_decimal_column_operations(
    #[case] column_type: &str,
    #[case] operation: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("test_value", column_type),
        ("result", "NUMERIC(30, 15)"),
    ];
    let table_id = create_test_table(&pool, schema_id, "column_test", columns).await;

    let script = match operation {
        "sqrt" | "abs" => {
            format!(
                r#"({} (steel_get_column "column_test" "test_value"))"#,
                operation
            )
        }
        _ => {
            format!(
                r#"({} (steel_get_column "column_test" "test_value") "10")"#,
                operation
            )
        }
    };

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script,
        description: format!("Steel decimal {} with {} column", operation, column_type),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(
        result.is_ok(),
        "Steel decimal {} with {} column should succeed",
        operation,
        column_type
    );
}

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

    // Create a realistic financial calculation table
    let columns = vec![
        ("principal", "NUMERIC(16, 2)"),        // Principal amount
        ("annual_rate", "NUMERIC(6, 5)"),       // Interest rate
        ("years", "INTEGER"),                   // Time period
        ("compounding_periods", "INTEGER"),     // Compounding frequency
        ("compound_interest", "NUMERIC(20, 8)"), // Result
    ];

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

    // Complex compound interest formula: P * (1 + r/n)^(n*t)
    let compound_script = r#"
        (*
            (steel_get_column "financial_calc" "principal")
            (pow
                (+ "1"
                   (/ (steel_get_column "financial_calc" "annual_rate")
                      (steel_get_column "financial_calc" "compounding_periods")))
                (* (steel_get_column "financial_calc" "years")
                   (steel_get_column "financial_calc" "compounding_periods"))))
    "#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "compound_interest".to_string(),
        script: compound_script.to_string(),
        description: "Complex compound interest calculation".to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_ok(), "Complex financial calculation should succeed");
}

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

    let columns = vec![
        ("measurement_a", "NUMERIC(25, 15)"),
        ("measurement_b", "NUMERIC(25, 15)"),
        ("coefficient", "NUMERIC(10, 8)"),
        ("scientific_result", "NUMERIC(30, 18)"),
    ];

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

    // Complex scientific calculation with high precision
    let scientific_script = r#"
        (+
            (sqrt (pow (steel_get_column "scientific_data" "measurement_a") "2"))
            (* (steel_get_column "scientific_data" "coefficient")
               (abs (- (steel_get_column "scientific_data" "measurement_b")
                       (steel_get_column "scientific_data" "measurement_a")))))
    "#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "scientific_result".to_string(),
        script: scientific_script.to_string(),
        description: "High precision scientific calculation".to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_ok(), "High precision scientific calculation should succeed");
}

#[rstest]
#[case::min_precision("NUMERIC(1, 0)", "Minimum precision")]
#[case::max_scale("NUMERIC(10, 10)", "Maximum relative scale")]
#[case::currency_standard("NUMERIC(14, 4)", "Standard currency precision")]
#[case::percentage_precision("NUMERIC(5, 4)", "Percentage precision")]
#[tokio::test]
async fn test_precision_boundary_conditions(
    #[case] numeric_type: &str,
    #[case] description: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("boundary_value", numeric_type),
        ("result", "NUMERIC(30, 15)"),
    ];

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

    let script = r#"(+ (steel_get_column "boundary_test" "boundary_value") "0.00001")"#;

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

    let result = post_table_script(&pool, request).await;
    assert!(result.is_ok(), "{} should work with steel_decimal", description);
}

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

    let columns = vec![
        ("integer_quantity", "INTEGER"),
        ("numeric_price", "NUMERIC(10, 4)"),
        ("numeric_tax_rate", "NUMERIC(5, 4)"),
        ("total_with_tax", "NUMERIC(15, 4)"),
    ];

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

    // Calculate total with tax: (quantity * price) * (1 + tax_rate)
    let mixed_script = r#"
        (*
            (* (steel_get_column "mixed_types_calc" "integer_quantity")
               (steel_get_column "mixed_types_calc" "numeric_price"))
            (+ "1" (steel_get_column "mixed_types_calc" "numeric_tax_rate")))
    "#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "total_with_tax".to_string(),
        script: mixed_script.to_string(),
        description: "Mixed INTEGER and NUMERIC calculation".to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_ok(), "Mixed INTEGER and NUMERIC operations should succeed");
}

#[rstest]
#[case::zero_division("/", "0", "Division by zero should be handled")]
#[case::negative_sqrt("sqrt", "-1", "Square root of negative should be handled")]
#[case::large_power("pow", "999999999", "Very large power should be handled")]
#[tokio::test]
async fn test_mathematical_edge_cases(
    #[case] operation: &str,
    #[case] problematic_value: &str,
    #[case] description: &str,
) {
    let pool = setup_isolated_db().await;
    let schema_id = get_default_schema_id(&pool).await;

    let columns = vec![
        ("test_value", "NUMERIC(15, 6)"),
        ("result", "NUMERIC(20, 8)"),
    ];

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

    let script = match operation {
        "sqrt" => {
            format!(r#"(sqrt "{}")"#, problematic_value)
        }
        "/" => {
            format!(r#"(/ "10" "{}")"#, problematic_value)
        }
        "pow" => {
            format!(r#"(pow "2" "{}")"#, problematic_value)
        }
        _ => {
            format!(r#"({} "10" "{}")"#, operation, problematic_value)
        }
    };

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

    // Note: These operations should either succeed (if steel_decimal handles them gracefully)
    // or fail with appropriate error messages (if they're genuinely problematic)
    let result = post_table_script(&pool, request).await;

    // For now, we just ensure the validation doesn't crash
    // The specific behavior (success vs failure) depends on steel_decimal implementation
    match result {
        Ok(_) => {
            // Steel decimal handled the edge case gracefully
        }
        Err(error) => {
            // Should fail with meaningful error message, not a crash
            let error_msg = error.to_string();
            assert!(
                !error_msg.contains("panic") && !error_msg.contains("internal error"),
                "Should fail gracefully, not crash: {}",
                error_msg
            );
        }
    }
}

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

    let columns = vec![
        ("value_a", "NUMERIC(10, 2)"),
        ("value_b", "INTEGER"),
        ("comparison_result", "BOOLEAN"),
    ];

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

    let comparison_operations = vec![">", "<", "=", ">=", "<="];

    for operation in comparison_operations {
        let script = format!(
            r#"({} (steel_get_column "comparison_test" "value_a")
                  (steel_get_column "comparison_test" "value_b"))"#,
            operation
        );

        let request = PostTableScriptRequest {
            table_definition_id: table_id,
            target_column: "comparison_result".to_string(),
            script,
            description: format!("Comparison operation: {}", operation),  // Fixed: removed Some()
        };

        let result = post_table_script(&pool, request).await;
        assert!(
            result.is_ok(),
            "Comparison operation {} should succeed with allowed types",
            operation
        );
    }
}

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

    let columns = vec![
        ("x", "NUMERIC(15, 8)"),
        ("y", "NUMERIC(15, 8)"),
        ("z", "INTEGER"),
        ("nested_result", "NUMERIC(25, 12)"),
    ];

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

    // Deeply nested expression: sqrt((x^2 + y^2) / z) + abs(x - y)
    let nested_script = r#"
        (+
            (sqrt
                (/
                    (+ (pow (steel_get_column "nested_calc" "x") "2")
                       (pow (steel_get_column "nested_calc" "y") "2"))
                    (steel_get_column "nested_calc" "z")))
            (abs
                (- (steel_get_column "nested_calc" "x")
                   (steel_get_column "nested_calc" "y"))))
    "#;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "nested_result".to_string(),
        script: nested_script.to_string(),
        description: "Deeply nested mathematical expression".to_string(),  // Fixed: removed Some()
    };

    let result = post_table_script(&pool, request).await;
    assert!(result.is_ok(), "Deeply nested mathematical expressions should succeed");
}
