// tests/table_script/post_scripts_integration_tests.rs

use crate::common::setup_isolated_db;
use server::table_script::handlers::post_table_script::post_table_script;
use common::proto::komp_ac::table_script::{PostTableScriptRequest, TableScriptResponse};
use serde_json::json;
use sqlx::PgPool;

/// Test utilities for table script integration testing - moved to top level for shared access
pub struct TableScriptTestHelper {
    pub pool: PgPool,
    pub schema_id: i64,
}

impl TableScriptTestHelper {
    pub async fn new() -> Self {
        let pool = setup_isolated_db().await;
        let schema_id = sqlx::query_scalar!("SELECT id FROM schemas WHERE name = 'default'")
            .fetch_one(&pool)
            .await
            .expect("Failed to get default schema ID");

        Self {
            pool,
            schema_id,
        }
    }

    pub async fn create_table_with_types(&self, table_name: &str, column_definitions: Vec<(&str, &str)>) -> i64 {
        let columns: Vec<String> = column_definitions
            .iter()
            .map(|(name, type_def)| format!("\"{}\" {}", name, type_def))
            .collect();

        let columns_json = json!(columns);
        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"#,
            self.schema_id,
            table_name,
            columns_json,
            indexes_json
        )
        .fetch_one(&self.pool)
        .await
        .expect("Failed to create test table")
    }

    pub async fn create_script(&self, table_id: i64, target_column: &str, script: &str) -> Result<TableScriptResponse, tonic::Status> {
        let request = PostTableScriptRequest {
            table_definition_id: table_id,
            target_column: target_column.to_string(),
            script: script.to_string(),
            description: "Test script".to_string(),
        };

        post_table_script(&self.pool, request).await
    }
}

#[cfg(test)]
mod integration_tests {
    use super::*;

    #[tokio::test]
    async fn test_comprehensive_type_validation_matrix() {
        let helper = TableScriptTestHelper::new().await;

        // Create a comprehensive table with all supported and unsupported types
        let table_id = helper.create_table_with_types(
            "comprehensive_table",
            vec![
                // Supported types for math operations
                ("integer_col", "INTEGER"),
                ("numeric_basic", "NUMERIC(10, 2)"),
                ("numeric_high_precision", "NUMERIC(28, 15)"),
                ("numeric_currency", "NUMERIC(14, 4)"),

                // Supported but not for math operations
                ("text_col", "TEXT"),
                ("boolean_col", "BOOLEAN"),

                // Prohibited types entirely
                ("bigint_col", "BIGINT"),
                ("date_col", "DATE"),
                ("timestamp_col", "TIMESTAMPTZ"),

                // Result columns of various types
                ("result_integer", "INTEGER"),
                ("result_numeric", "NUMERIC(15, 5)"),
                ("result_text", "TEXT"),
            ]
        ).await;

        // Test matrix: [source_type, operation, expected_result]
        let test_matrix = vec![
            // Valid mathematical operations
            ("integer_col", "+", "result_numeric", true),
            ("numeric_basic", "*", "result_numeric", true),
            ("numeric_high_precision", "/", "result_numeric", true),
            ("integer_col", "sqrt", "result_numeric", true),

            // Invalid mathematical operations - prohibited types in math
            ("text_col", "+", "result_numeric", false),
            ("boolean_col", "*", "result_numeric", false),
            ("bigint_col", "/", "result_numeric", false),
            ("date_col", "-", "result_numeric", false),
            ("timestamp_col", "+", "result_numeric", false),

            // Invalid target columns - prohibited types as targets
            ("integer_col", "+", "bigint_col", false),
            ("numeric_basic", "*", "date_col", false),
            ("integer_col", "/", "timestamp_col", false),
        ];

        for (source_col, operation, target_col, should_succeed) in test_matrix {
            let script = match operation {
                "sqrt" => format!(r#"(sqrt (steel_get_column "comprehensive_table" "{}"))"#, source_col),
                _ => format!(
                    r#"({} (steel_get_column "comprehensive_table" "{}") "10")"#,
                    operation, source_col
                ),
            };

            let result = helper.create_script(table_id, target_col, &script).await;

            if should_succeed {
                assert!(
                    result.is_ok(),
                    "Should succeed: {} {} -> {}",
                    source_col, operation, target_col
                );
            } else {
                assert!(
                    result.is_err(),
                    "Should fail: {} {} -> {}",
                    source_col, operation, target_col
                );
            }
        }
    }

    #[tokio::test]
    async fn test_steel_decimal_precision_requirements() {
        let helper = TableScriptTestHelper::new().await;

        // Create table with various precision requirements
        let table_id = helper.create_table_with_types(
            "precision_table",
            vec![
                ("low_precision", "NUMERIC(5, 2)"),      // e.g., 999.99
                ("medium_precision", "NUMERIC(10, 4)"),  // e.g., 999999.9999
                ("high_precision", "NUMERIC(28, 15)"),   // Maximum PostgreSQL precision
                ("currency", "NUMERIC(14, 4)"),          // Standard currency precision
                ("percentage", "NUMERIC(5, 4)"),         // e.g., 0.9999 (99.99%)
                ("integer_val", "INTEGER"),
                ("result", "NUMERIC(30, 15)"),
            ]
        ).await;

        // Test that steel_decimal can handle various precision levels
        let precision_tests = vec![
            (
                r#"(+ (steel_get_column "precision_table" "low_precision") "0.01")"#,
                "Low precision addition"
            ),
            (
                r#"(* (steel_get_column "precision_table" "currency") "1.0001")"#,
                "Currency calculation with high precision multiplier"
            ),
            (
                r#"(/ (steel_get_column "precision_table" "high_precision") "3")"#,
                "High precision division"
            ),
            (
                r#"(pow (steel_get_column "precision_table" "percentage") "2")"#,
                "Percentage squared"
            ),
            (
                r#"(sqrt (steel_get_column "precision_table" "medium_precision"))"#,
                "Square root of medium precision number"
            ),
        ];

        for (script, description) in precision_tests {
            let result = helper.create_script(table_id, "result", script).await;
            assert!(
                result.is_ok(),
                "Precision test should succeed: {}",
                description
            );
        }
    }

    #[tokio::test]
    async fn test_complex_financial_calculations() {
        let helper = TableScriptTestHelper::new().await;

        // Create a realistic financial table
        let table_id = helper.create_table_with_types(
            "financial_instruments",
            vec![
                ("principal", "NUMERIC(16, 2)"),      // Principal amount
                ("annual_rate", "NUMERIC(6, 5)"),     // Interest rate (e.g., 0.05250)
                ("years", "INTEGER"),                 // Time period
                ("compounding_periods", "INTEGER"),   // Compounding frequency
                ("fees", "NUMERIC(10, 2)"),          // Transaction fees
                ("compound_interest", "NUMERIC(20, 8)"), // Result column
            ]
        ).await;

        // Complex compound interest calculation
        let compound_interest_script = r#"
            (-
                (*
                    (steel_get_column "financial_instruments" "principal")
                    (pow
                        (+ "1"
                           (/ (steel_get_column "financial_instruments" "annual_rate")
                              (steel_get_column "financial_instruments" "compounding_periods")))
                        (* (steel_get_column "financial_instruments" "years")
                           (steel_get_column "financial_instruments" "compounding_periods"))))
                (+ (steel_get_column "financial_instruments" "principal")
                   (steel_get_column "financial_instruments" "fees")))
        "#;

        let result = helper.create_script(table_id, "compound_interest", compound_interest_script).await;
        assert!(result.is_ok(), "Complex financial calculation should succeed");
    }

    #[tokio::test]
    async fn test_scientific_notation_support() {
        let helper = TableScriptTestHelper::new().await;

        let table_id = helper.create_table_with_types(
            "scientific_data",
            vec![
                ("large_number", "NUMERIC(30, 10)"),
                ("small_number", "NUMERIC(30, 20)"),
                ("result", "NUMERIC(35, 25)"),
            ]
        ).await;

        // Test that steel_decimal can handle scientific notation in scripts
        let scientific_script = r#"
            (+
                (steel_get_column "scientific_data" "large_number")
                (* "1.5e-10" (steel_get_column "scientific_data" "small_number")))
        "#;

        let result = helper.create_script(table_id, "result", scientific_script).await;
        assert!(result.is_ok(), "Scientific notation should be supported");
    }

    #[tokio::test]
    async fn test_script_dependencies_and_cycles() {
        let helper = TableScriptTestHelper::new().await;

        println!("=== DEPENDENCY TEST DEBUG START ===");
        println!("Schema ID: {}", helper.schema_id);

        // Create multiple tables to test dependencies
        let table_a_id = helper.create_table_with_types(
            "table_a",
            vec![
                ("value_a", "NUMERIC(10, 2)"),
                ("result_a", "NUMERIC(10, 2)"),
            ]
        ).await;
        println!("Created table_a with ID: {}", table_a_id);

        let table_b_id = helper.create_table_with_types(
            "table_b",
            vec![
                ("value_b", "NUMERIC(10, 2)"),
                ("result_b", "NUMERIC(10, 2)"),
            ]
        ).await;
        println!("Created table_b with ID: {}", table_b_id);

        // Check what tables exist in the database
        let existing_tables = sqlx::query!(
            "SELECT table_name FROM table_definitions WHERE schema_id = $1",
            helper.schema_id
        )
        .fetch_all(&helper.pool)
        .await
        .expect("Failed to fetch existing tables");

        println!("Existing tables in schema {}: {:?}",
            helper.schema_id,
            existing_tables.iter().map(|t| &t.table_name).collect::<Vec<_>>()
        );

        // Create first script: table_a.result_a depends on table_b.value_b
        let script_a = r#"(+ (steel_get_column "table_b" "value_b") "10")"#;
        println!("Testing cross-table script: {}", script_a);

        let result_a: Result<TableScriptResponse, tonic::Status> = helper.create_script(table_a_id, "result_a", script_a).await;

        match &result_a {
            Ok(response) => {
                println!("✓ Cross-table dependency succeeded! Script ID: {}", response.id);
            },
            Err(error) => {
                println!("✗ Cross-table dependency failed!");
                println!("Error code: {:?}", error.code());
                println!("Error message: {}", error.message());
                println!("Full error: {:?}", error);

                // Check if this is expected (cross-table not supported) or unexpected bug
                let error_str = error.to_string();
                if error_str.contains("does not exist") || error_str.contains("not found") {
                    println!("This appears to be a cross-table reference limitation - skipping cycle test");
                    println!("=== DEPENDENCY TEST DEBUG END (SKIPPED) ===");
                    return; // Skip the rest if cross-table isn't supported
                }
            }
        }

        assert!(result_a.is_ok(), "First dependency script should succeed, got error: {:?}", result_a.err());

        // Try to create circular dependency: table_b.result_b depends on table_a.result_a
        let script_b = r#"(* (steel_get_column "table_a" "result_a") "2")"#;
        println!("Testing circular dependency script: {}", script_b);

        let result_b: Result<TableScriptResponse, tonic::Status> = helper.create_script(table_b_id, "result_b", script_b).await;

        match &result_b {
            Ok(response) => {
                println!("⚠ Circular dependency was allowed! Script ID: {}", response.id);
                println!("This might indicate cycle detection is not implemented or is lenient");
            },
            Err(error) => {
                println!("✓ Circular dependency was blocked: {}", error.message());
                let error_str = error.to_string();
                assert!(
                    error_str.contains("cycle") || error_str.contains("circular"),
                    "Circular dependency should be detected with 'cycle' or 'circular' in message, got: {}",
                    error_str
                );
            }
        }

        println!("=== DEPENDENCY TEST DEBUG END ===");
    }

    #[tokio::test]
    async fn test_error_message_quality() {
        let helper = TableScriptTestHelper::new().await;

        let table_id = helper.create_table_with_types(
            "error_test_table",
            vec![
                ("text_field", "TEXT"),
                ("numeric_field", "NUMERIC(10, 2)"),
                ("boolean_field", "BOOLEAN"),
                ("bigint_field", "BIGINT"),
            ]
        ).await;

        // Test various error scenarios and check error message quality
        let error_scenarios = vec![
            (
                "bigint_field",
                "(+ \"10\" \"20\")",
                vec!["cannot create script", "BIGINT", "cannot target columns of type"],
                "Targeting prohibited type should give clear error"
            ),
            (
                "numeric_field",
                r#"(+ (steel_get_column "error_test_table" "text_field") "10")"#,
                vec!["mathematical", "TEXT", "operations"],
                "TEXT in math operations should give clear error"
            ),
            (
                "numeric_field",
                r#"(* (steel_get_column "error_test_table" "boolean_field") "5")"#,
                vec!["mathematical", "BOOLEAN", "operations"],
                "BOOLEAN in math operations should give clear error"
            ),
            (
                "numeric_field",
                r#"(+ (steel_get_column "nonexistent_table" "field") "10")"#,
                vec!["table", "does not exist", "nonexistent_table"],
                "Nonexistent table should give clear error"
            ),
            (
                "numeric_field",
                r#"(+ (steel_get_column "error_test_table" "nonexistent_field") "10")"#,
                vec!["column", "does not exist", "nonexistent_field"],
                "Nonexistent column should give clear error"
            ),
        ];

        for (target_column, script, expected_keywords, description) in error_scenarios {
            let result = helper.create_script(table_id, target_column, script).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
                );
            }
        }
    }

    #[tokio::test]
    async fn test_performance_with_complex_nested_expressions() {
        let helper = TableScriptTestHelper::new().await;

        let table_id = helper.create_table_with_types(
            "performance_table",
            vec![
                ("x", "NUMERIC(15, 8)"),
                ("y", "NUMERIC(15, 8)"),
                ("z", "NUMERIC(15, 8)"),
                ("w", "NUMERIC(15, 8)"),
                ("complex_result", "NUMERIC(25, 12)"),
            ]
        ).await;

        // Create a deeply nested mathematical expression
        let complex_script = r#"
            (+
                (*
                    (pow (steel_get_column "performance_table" "x") "3")
                    (sqrt (steel_get_column "performance_table" "y")))
                (-
                    (/
                        (+ (steel_get_column "performance_table" "z") "100")
                        (max (steel_get_column "performance_table" "w") "1"))
                    (* "0.5"
                       (abs (- (steel_get_column "performance_table" "x")
                               (steel_get_column "performance_table" "y"))))))
        "#;

        let start_time = std::time::Instant::now();
        let result = helper.create_script(table_id, "complex_result", complex_script).await;
        let duration = start_time.elapsed();

        assert!(result.is_ok(), "Complex nested expression should succeed");
        assert!(duration.as_millis() < 1000, "Script validation should complete within 1 second");
    }

    #[tokio::test]
    async fn test_boundary_conditions() {
        let helper = TableScriptTestHelper::new().await;

        // Test boundary conditions for NUMERIC types
        let table_id = helper.create_table_with_types(
            "boundary_table",
            vec![
                ("min_numeric", "NUMERIC(1, 0)"),      // Minimum: single digit, no decimal
                ("max_numeric", "NUMERIC(1000, 999)"),  // Maximum PostgreSQL allows
                ("zero_scale", "NUMERIC(10, 0)"),      // Integer-like numeric
                ("max_scale", "NUMERIC(28, 28)"),      // Maximum scale
                ("result", "NUMERIC(1000, 999)"),
            ]
        ).await;

        let boundary_script = r#"
            (+
                (steel_get_column "boundary_table" "min_numeric")
                (steel_get_column "boundary_table" "zero_scale"))
        "#;

        let result = helper.create_script(table_id, "result", boundary_script).await;
        assert!(result.is_ok(), "Boundary condition numeric types should be supported");
    }
}

#[cfg(test)]
mod steel_decimal_integration_tests {
    use super::*;
    use server::steel::server::execution::execute_script;
    use std::sync::Arc;
    use std::collections::HashMap;

    #[tokio::test]
    async fn test_steel_decimal_execution_with_valid_types() {
        println!("=== STEEL DECIMAL EXECUTION DEBUG START ===");

        let helper = TableScriptTestHelper::new().await;
        let pool = Arc::new(helper.pool.clone());

        println!("Schema ID: {}", helper.schema_id);

        // Create a proper table for the test
        let table_id = helper.create_table_with_types(
            "test_execution_table",
            vec![
                ("amount", "NUMERIC(10, 2)"),
                ("quantity", "INTEGER"),
                ("tax_rate", "NUMERIC(5, 4)"),
                ("result", "NUMERIC(15, 4)"), // Add a result column
            ]
        ).await;
        println!("Created test table with ID: {}", table_id);

        // Test row data
        let mut row_data = HashMap::new();
        row_data.insert("amount".to_string(), "100.50".to_string());
        row_data.insert("quantity".to_string(), "5".to_string());
        row_data.insert("tax_rate".to_string(), "0.0825".to_string());

        // Script with proper $-prefixed variables (steel_decimal syntax)
        let script = r#"
            (+
                (* $amount $quantity)
                (* $amount $tax_rate))
        "#;

        println!("Script with $-prefixed variables: {}", script);

        // STEP 1: Create the script via post_table_script (this transforms it)
        let script_creation_result = helper.create_script(table_id, "result", script).await;
        assert!(script_creation_result.is_ok(), "Script creation should succeed");
        let script_response = script_creation_result.unwrap();
        println!("✓ Script created with ID: {}", script_response.id);

        // STEP 2: Load the transformed script from the database
        let script_record = sqlx::query!(
            "SELECT script FROM table_scripts WHERE id = $1",
            script_response.id
        )
        .fetch_one(&helper.pool)
        .await
        .expect("Should be able to load the created script");

        let transformed_script = script_record.script;
        println!("Transformed script: {}", transformed_script);

        // STEP 3: Execute the transformed script
        let result = execute_script(
            transformed_script, // ✅ Now using the transformed script
            "STRINGS",
            pool.clone(),
            helper.schema_id,
            "default".to_string(),
            "test_execution_table".to_string(),
            row_data.clone(),
        ).await;

        match &result {
            Ok(value) => {
                println!("✓ Steel decimal execution succeeded!");
                println!("Result: {:?}", value);
            },
            Err(error) => {
                println!("✗ Steel decimal execution failed!");
                println!("Error: {}", error);
            }
        }

        println!("=== STEEL DECIMAL EXECUTION DEBUG END ===");
        assert!(result.is_ok(), "Steel decimal execution should succeed with valid numeric types, got: {:?}", result.err());
    }

    #[tokio::test]
    async fn test_steel_decimal_precision_handling() {
        println!("=== STEEL DECIMAL PRECISION DEBUG START ===");

        let helper = TableScriptTestHelper::new().await;
        let pool = Arc::new(helper.pool.clone());

        println!("Schema ID: {}", helper.schema_id);

        // Create a table with high precision columns
        let table_id = helper.create_table_with_types(
            "precision_test_table",
            vec![
                ("precise_value", "NUMERIC(20, 12)"),
                ("multiplier", "NUMERIC(20, 12)"),
                ("result", "NUMERIC(25, 15)"), // Add result column
            ]
        ).await;
        println!("Created precision test table with ID: {}", table_id);

        // Test high precision calculations
        let mut row_data = HashMap::new();
        row_data.insert("precise_value".to_string(), "123.456789012345".to_string());
        row_data.insert("multiplier".to_string(), "2.718281828459045".to_string());

        // Script with proper $-prefixed variables
        let script = r#"(* $precise_value $multiplier)"#;

        println!("Precision script with $-prefixed variables: {}", script);

        // STEP 1: Create the script via post_table_script (this transforms it)
        let script_creation_result = helper.create_script(table_id, "result", script).await;
        assert!(script_creation_result.is_ok(), "Script creation should succeed");
        let script_response = script_creation_result.unwrap();
        println!("✓ Precision script created with ID: {}", script_response.id);

        // STEP 2: Load the transformed script from the database
        let script_record = sqlx::query!(
            "SELECT script FROM table_scripts WHERE id = $1",
            script_response.id
        )
        .fetch_one(&helper.pool)
        .await
        .expect("Should be able to load the created script");

        let transformed_script = script_record.script;
        println!("Transformed precision script: {}", transformed_script);

        // STEP 3: Execute the transformed script
        let result = execute_script(
            transformed_script, // ✅ Now using the transformed script
            "STRINGS",
            pool.clone(),
            helper.schema_id,
            "default".to_string(),
            "precision_test_table".to_string(),
            row_data,
        ).await;

        match &result {
            Ok(value) => {
                println!("✓ Steel decimal precision test succeeded!");
                match value {
                    server::steel::server::execution::Value::Strings(values) => {
                        println!("String results: {:?}", values);
                        if !values.is_empty() {
                            if let Ok(result_value) = values[0].parse::<f64>() {
                                println!("Parsed result: {}", result_value);
                                println!("Expected > 300.0: {}", result_value > 300.0);
                            }
                        }
                    },
                    other => println!("Non-string result: {:?}", other),
                }
            },
            Err(error) => {
                println!("✗ Steel decimal precision test failed!");
                println!("Error: {}", error);
            }
        }

        println!("=== STEEL DECIMAL PRECISION DEBUG END ===");
        assert!(result.is_ok(), "Steel decimal should handle high precision calculations, got: {:?}", result.err());

        if let Ok(server::steel::server::execution::Value::Strings(values)) = result {
            assert!(!values.is_empty(), "Should return calculated values");
            // The result should maintain precision
            let result_value: f64 = values[0].parse().unwrap_or(0.0);
            assert!(result_value > 300.0, "Calculation result should be reasonable, got: {}", result_value);
        }
    }
}
