// tests/tables_data/post/post_table_data_steel_decimal_test.rs

use sqlx::PgPool;
use common::proto::komp_ac::{
    table_definition::{PostTableDefinitionRequest, ColumnDefinition},
    table_script::PostTableScriptRequest,
    tables_data::PostTableDataRequest,
};
use prost_types::value::Kind;
use prost_types::Value as ProtoValue;
use std::collections::HashMap;
use tokio::sync::mpsc;
use std::sync::Arc;

use server::indexer::IndexCommand;
use server::table_definition::handlers::post_table_definition::post_table_definition;
use server::table_script::handlers::post_table_script::post_table_script;
use server::tables_data::handlers::post_table_data::post_table_data;
use server::steel::server::execution::{self, Value};
use server::steel::server::functions::SteelContext;

// Helper function to discover what steel_decimal actually produces
async fn discover_steel_decimal_output(
    pool: &PgPool,
    script: &str, 
    input_data: HashMap<String, String>,
    column_type: &str,
) -> Result<String, String> {
    // Execute steel script to get the calculated value
    let context = SteelContext {
        current_table: "test_table".to_string(),
        schema_id: 1, 
        schema_name: "test_schema".to_string(),
        row_data: input_data.clone(),
        db_pool: Arc::new(pool.clone()),
    };

    let script_result = execution::execute_script(
        script.to_string(),
        "STRINGS",
        Arc::new(pool.clone()),
        1,
        "test_schema".to_string(),
        "test_table".to_string(),
        input_data,
    )
    .await
    .map_err(|e| format!("Script execution failed: {}", e))?;

    let Value::Strings(mut script_output) = script_result else {
        return Err("Script must return string values".to_string());
    };

    let steel_value = script_output.pop()
        .ok_or_else(|| "Script returned no values".to_string())?;

    // See how PostgreSQL formats this value when cast to the target column type
    let stored_value = sqlx::query_scalar::<_, String>(&format!(
        "SELECT ($1::{})::TEXT", column_type
    ))
    .bind(&steel_value)
    .fetch_one(pool)
    .await
    .map_err(|e| format!("Failed to cast value: {}", e))?;

    println!("STEEL_DECIMAL DEBUG: Script '{}' -> Raw: '{}' -> DB Formatted: '{}'", 
             script, steel_value, stored_value);

    Ok(stored_value)
}

async fn setup_test_schema(pool: &PgPool, profile_name: &str, table_name: &str) -> i64 {
    // Create table definition
    let request = PostTableDefinitionRequest {
        profile_name: profile_name.to_string(),
        table_name: table_name.to_string(),
        columns: vec![
            ColumnDefinition {
                name: "price".to_string(),
                field_type: "decimal(10, 2)".to_string(),
            },
            ColumnDefinition {
                name: "quantity".to_string(),
                field_type: "integer".to_string(),
            },
            ColumnDefinition {
                name: "total".to_string(),
                field_type: "decimal(10, 2)".to_string(),
            },
            ColumnDefinition {
                name: "percentage".to_string(),
                field_type: "decimal(5, 2)".to_string(),
            },
        ],
        indexes: vec![],
        links: vec![],
    };

    let response = post_table_definition(pool, request).await.unwrap();
    assert!(response.success);

    // Get table definition ID
    let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", profile_name)
        .fetch_one(pool)
        .await
        .unwrap();

    let table_row = sqlx::query!(
        "SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
        schema_row.id,
        table_name
    )
    .fetch_one(pool)
    .await
    .unwrap();

    table_row.id
}

async fn create_indexer_channel() -> mpsc::Sender<IndexCommand> {
    let (tx, mut rx) = mpsc::channel(100);
    
    // Spawn a task to consume messages to prevent blocking
    tokio::spawn(async move {
        while let Some(_) = rx.recv().await {
            // Just consume messages for testing
        }
    });
    
    tx
}

#[sqlx::test]
async fn test_basic_arithmetic_validation_success(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_arithmetic", "invoice").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script: total should equal price * quantity
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(* $price $quantity)".to_string(),
        description: "Total = Price × Quantity".to_string(),
    };

    let script_response = post_table_script(&pool, script_request).await.unwrap();
    assert!(script_response.id > 0);

    // Test data insertion with correct calculation: 25.50 * 3 = 76.5
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("25.50".to_string())),
    });
    data.insert("quantity".to_string(), ProtoValue {
        kind: Some(Kind::NumberValue(3.0)), // INTEGER column expects NumberValue
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("76.5".to_string())), // Steel decimal normalizes to "76.5"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_arithmetic".to_string(),
        table_name: "invoice".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
    assert!(response.inserted_id > 0);
}

#[sqlx::test]
async fn test_basic_arithmetic_validation_failure(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_arithmetic_fail", "invoice").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script: total should equal price * quantity
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(* $price $quantity)".to_string(),
        description: "Total = Price × Quantity".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    // Test data insertion with incorrect calculation
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("25.50".to_string())),
    });
    data.insert("quantity".to_string(), ProtoValue {
        kind: Some(Kind::NumberValue(3.0)),
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("70.00".to_string())), // Wrong! Should be 76.50
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_arithmetic_fail".to_string(),
        table_name: "invoice".to_string(),
        data,
    };

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

    let error = result.unwrap_err();
    assert_eq!(error.code(), tonic::Code::InvalidArgument);
    
    // --- START OF FIX ---
    // The old assertion was looking for "Expected '...'".
    // The new error message is "Script calculated '...' but user provided '...'"
    // We update the assertion to match the new format.
    let msg = error.message();
    assert!(msg.contains("Validation failed for column 'total'"));
    assert!(msg.contains("Script calculated '76.50'"));
    assert!(msg.contains("but user provided '70.00'"));
    // --- END OF FIX ---
}

#[sqlx::test]
async fn test_division_with_precision(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_division", "calculation").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script: percentage should equal (total / price) * 100
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "percentage".to_string(),
        script: "(* (/ $total $price) 100.0)".to_string(),
        description: "Percentage = (Total / Price) × 100".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    // Test with precise division
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("33.33".to_string())),
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("100.00".to_string())),
    });
    data.insert("percentage".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("300.03000300030003000300030003".to_string())), // Full precision result from steel_decimal
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_division".to_string(),
        table_name: "calculation".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_complex_formula_validation(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_complex", "order").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script: total = (price * quantity) + (price * quantity * 0.08) [with 8% tax]
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(+ (* $price $quantity) (* (* $price $quantity) 0.08))".to_string(),
        description: "Total with 8% tax".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    // Test complex calculation: (100 * 2) + (100 * 2 * 0.08) = 200 + 16 = 216
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("100.00".to_string())),
    });
    data.insert("quantity".to_string(), ProtoValue {
        kind: Some(Kind::NumberValue(2.0)), // INTEGER column expects NumberValue
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("216".to_string())), // Steel decimal normalizes to "216"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_complex".to_string(),
        table_name: "order".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_steel_decimal_function_calls_directly(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_direct", "test").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script using direct steel decimal function syntax
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: r#"(decimal-add (decimal-mul $price $quantity) "5.00")"#.to_string(),
        description: "Direct steel decimal functions".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    // Test: (10.50 * 2) + 5.00 = 21 + 5 = 26
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("10.50".to_string())),
    });
    data.insert("quantity".to_string(), ProtoValue {
        kind: Some(Kind::NumberValue(2.0)), // INTEGER column expects NumberValue
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("26".to_string())), // Steel decimal normalizes to "26"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_direct".to_string(),
        table_name: "test".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_scientific_notation_support(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_scientific", "scientific").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script using scientific notation
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(+ $price 1e2)".to_string(), // Add 100 (1e2) to price
        description: "Scientific notation test".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    // Test: 25.50 + 100 = 125.5
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("25.50".to_string())),
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("125.5".to_string())), // Steel decimal normalizes to "125.5"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_scientific".to_string(),
        table_name: "scientific".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_advanced_math_functions(pool: PgPool) {
    // Create a table with advanced math requirements
    let request = PostTableDefinitionRequest {
        profile_name: "test_advanced_math".to_string(),
        table_name: "calculations".to_string(),
        columns: vec![
            ColumnDefinition {
                name: "input".to_string(),
                field_type: "decimal(10, 4)".to_string(),
            },
            ColumnDefinition {
                name: "square_root".to_string(),
                field_type: "decimal(10, 4)".to_string(),
            },
            ColumnDefinition {
                name: "power_result".to_string(),
                field_type: "decimal(10, 4)".to_string(),
            },
        ],
        indexes: vec![],
        links: vec![],
    };

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

    let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", "test_advanced_math")
        .fetch_one(&pool)
        .await
        .unwrap();

    let table_row = sqlx::query!(
        "SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
        schema_row.id,
        "calculations"
    )
    .fetch_one(&pool)
    .await
    .unwrap();

    let indexer_tx = create_indexer_channel().await;

    // Test square root
    let sqrt_script = PostTableScriptRequest {
        table_definition_id: table_row.id,
        target_column: "square_root".to_string(),
        script: "(sqrt $input)".to_string(),
        description: "Square root validation".to_string(),
    };

    post_table_script(&pool, sqrt_script).await.unwrap();

    // Test power function
    let power_script = PostTableScriptRequest {
        table_definition_id: table_row.id,
        target_column: "power_result".to_string(),
        script: "(^ $input 2.0)".to_string(),
        description: "Power function validation".to_string(),
    };

    post_table_script(&pool, power_script).await.unwrap();

    // Insert test data: sqrt(16) = 4, 16^2 = 256
    let mut data = HashMap::new();
    data.insert("input".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("16".to_string())), // Steel decimal format
    });
    data.insert("square_root".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("4".to_string())), // Steel decimal normalizes: sqrt(16) = "4"
    });
    data.insert("power_result".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("256".to_string())), // Steel decimal normalizes: 16^2 = "256"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_advanced_math".to_string(),
        table_name: "calculations".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_financial_calculations(pool: PgPool) {
    // Create financial calculation table
    let request = PostTableDefinitionRequest {
        profile_name: "test_financial".to_string(),
        table_name: "finance".to_string(),
        columns: vec![
            ColumnDefinition {
                name: "principal".to_string(),
                field_type: "decimal(12, 2)".to_string(),
            },
            ColumnDefinition {
                name: "rate".to_string(),
                field_type: "decimal(6, 4)".to_string(),
            },
            ColumnDefinition {
                name: "time".to_string(),
                field_type: "decimal(4, 1)".to_string(),
            },
            ColumnDefinition {
                name: "compound_result".to_string(),
                field_type: "decimal(12, 2)".to_string(),
            },
            ColumnDefinition {
                name: "percentage_result".to_string(),
                field_type: "decimal(10, 2)".to_string(),
            },
        ],
        indexes: vec![],
        links: vec![],
    };

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

    let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", "test_financial")
        .fetch_one(&pool)
        .await
        .unwrap();

    let table_row = sqlx::query!(
        "SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
        schema_row.id,
        "finance"
    )
    .fetch_one(&pool)
    .await
    .unwrap();

    let indexer_tx = create_indexer_channel().await;

    // Test using steel decimal's advanced functions (if available)
    // Using basic operations to simulate compound interest: P * (1 + r)^t
    let compound_script = PostTableScriptRequest {
        table_definition_id: table_row.id,
        target_column: "compound_result".to_string(),
        script: "(* $principal (^ (+ 1.0 $rate) $time))".to_string(),
        description: "Compound interest calculation".to_string(),
    };

    post_table_script(&pool, compound_script).await.unwrap();

    // Test percentage calculation: principal * rate
    let percentage_script = PostTableScriptRequest {
        table_definition_id: table_row.id,
        target_column: "percentage_result".to_string(),
        script: "(* $principal $rate)".to_string(),
        description: "Percentage calculation".to_string(),
    };

    post_table_script(&pool, percentage_script).await.unwrap();

    // Test: 1000 * (1.05)^2 = 1102.5 and 1000 * 0.05 = 50
    let mut data = HashMap::new();
    data.insert("principal".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("1000".to_string())),
    });
    data.insert("rate".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("0.05".to_string())),
    });
    data.insert("time".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("2".to_string())),
    });
    data.insert("compound_result".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("1102.5".to_string())), // Steel decimal normalizes to "1102.5"
    });
    data.insert("percentage_result".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("50".to_string())), // Steel decimal normalizes to "50"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_financial".to_string(),
        table_name: "finance".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_precision_handling(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_precision", "precision_test").await;
    let indexer_tx = create_indexer_channel().await;

    // Test that steel decimal handles precision correctly
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(/ 1.0 3.0)".to_string(), // 1/3 should be handled with proper precision
        description: "Precision test".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    let mut data = HashMap::new();
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("0.3333333333333333333333333333".to_string())),
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_precision".to_string(),
        table_name: "precision_test".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_multiple_script_validations(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_multiple", "multi_validation").await;
    let indexer_tx = create_indexer_channel().await;

    // Create multiple validation scripts
    let script1 = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(* $price $quantity)".to_string(),
        description: "Basic multiplication".to_string(),
    };

    let script2 = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "percentage".to_string(),
        script: "(/ $total $price)".to_string(),
        description: "Percentage calculation".to_string(),
    };

    post_table_script(&pool, script1).await.unwrap();
    post_table_script(&pool, script2).await.unwrap();

    // Test: 50 * 4 = 200 and 200 / 50 = 4
    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("50.00".to_string())),
    });
    data.insert("quantity".to_string(), ProtoValue {
        kind: Some(Kind::NumberValue(4.0)), // INTEGER column expects NumberValue
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("200".to_string())), // Steel decimal normalizes to "200"
    });
    data.insert("percentage".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("4".to_string())), // Steel decimal normalizes to "4"
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_multiple".to_string(),
        table_name: "multi_validation".to_string(),
        data,
    };

    let response = post_table_data(&pool, data_request, &indexer_tx).await.unwrap();
    assert!(response.success);
}

#[sqlx::test]
async fn test_steel_script_error_handling(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_errors", "error_test").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script with division by zero
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(/ $price 0.0)".to_string(), // Division by zero
        description: "Error test".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("100.00".to_string())),
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("999.99".to_string())),
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_errors".to_string(),
        table_name: "error_test".to_string(),
        data,
    };

    let result = post_table_data(&pool, data_request, &indexer_tx).await;
    assert!(result.is_err());
    
    let error = result.unwrap_err();
    assert_eq!(error.code(), tonic::Code::InvalidArgument);
    assert!(error.message().contains("Script execution failed"));
}

#[sqlx::test]
async fn test_decimal_overflow_protection(pool: PgPool) {
    let table_def_id = setup_test_schema(&pool, "test_overflow", "overflow_test").await;
    let indexer_tx = create_indexer_channel().await;

    // Create script that might cause overflow
    let script_request = PostTableScriptRequest {
        table_definition_id: table_def_id,
        target_column: "total".to_string(),
        script: "(^ $price 100.0)".to_string(), // Large power operation
        description: "Overflow test".to_string(),
    };

    post_table_script(&pool, script_request).await.unwrap();

    let mut data = HashMap::new();
    data.insert("price".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("999.99".to_string())),
    });
    data.insert("total".to_string(), ProtoValue {
        kind: Some(Kind::StringValue("1.0".to_string())),
    });

    let data_request = PostTableDataRequest {
        profile_name: "test_overflow".to_string(),
        table_name: "overflow_test".to_string(),
        data,
    };

    // This should either handle the overflow gracefully or fail with appropriate error
    let result = post_table_data(&pool, data_request, &indexer_tx).await;
    // The behavior depends on steel_decimal implementation - could succeed or fail
    // but should not crash the system
    match result {
        Ok(_) => println!("Overflow handled successfully"),
        Err(e) => {
            assert_eq!(e.code(), tonic::Code::InvalidArgument);
            println!("Overflow properly caught: {}", e.message());
        }
    }
}
