// tests/table_script/prohibited_types_test.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;
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 create a table link (for cross-table references)
async fn create_table_link(pool: &PgPool, source_table_id: i64, linked_table_id: i64, is_required: bool) {
    sqlx::query!(
        r#"INSERT INTO table_definition_links (source_table_id, linked_table_id, is_required)
           VALUES ($1, $2, $3)
           ON CONFLICT (source_table_id, linked_table_id) DO NOTHING"#,
        source_table_id,
        linked_table_id,
        is_required
    )
    .execute(pool)
    .await
    .expect("Failed to create table link");
}

/// 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")
}

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

    // Create a table with a BIGINT column
    let table_id = create_test_table(
        &pool,
        schema_id,
        "bigint_table",
        vec![("name", "TEXT"), ("big_number", "BIGINT")]
    ).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "big_number".to_string(), // This is BIGINT
        script: r#"(+ "10" "20")"#.to_string(),
        description: "Test script".to_string(),
    };

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

    // Should fail with prohibited type error
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("prohibited type") || error_msg.contains("BIGINT"),
        "Error should mention prohibited type or BIGINT: {}",
        error_msg
    );
}

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

    // Create a table with a DATE column
    let table_id = create_test_table(
        &pool,
        schema_id,
        "date_table",
        vec![("name", "TEXT"), ("event_date", "DATE")]
    ).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "event_date".to_string(), // This is DATE
        script: r#"(+ "10" "20")"#.to_string(),
        description: "Test script".to_string(),
    };

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

    // Should fail with prohibited type error
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("prohibited type") || error_msg.contains("DATE"),
        "Error should mention prohibited type or DATE: {}",
        error_msg
    );
}

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

    // Create a table with a TIMESTAMPTZ column
    let table_id = create_test_table(
        &pool,
        schema_id,
        "timestamp_table",
        vec![("name", "TEXT"), ("created_time", "TIMESTAMPTZ")]
    ).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "created_time".to_string(), // This is TIMESTAMPTZ
        script: r#"(+ "10" "20")"#.to_string(),
        description: "Test script".to_string(),
    };

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

    // Should fail with prohibited type error
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("prohibited type") || error_msg.contains("TIMESTAMPTZ"),
        "Error should mention prohibited type or TIMESTAMPTZ: {}",
        error_msg
    );
}

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

    // Create a table with TEXT and NUMERIC columns
    let table_id = create_test_table(
        &pool,
        schema_id,
        "text_math_table",
        vec![
            ("description", "TEXT"),
            ("amount", "NUMERIC(10, 2)"),
            ("result", "NUMERIC(10, 2)")
        ]
    ).await;

    // No self-link needed - self-references are automatically allowed

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: r#"(+ (steel_get_column "text_math_table" "description") "10")"#.to_string(),
        description: "Script that tries to use TEXT in math".to_string(),
    };

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

    // Should fail because script uses TEXT in mathematical operation
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("mathematical operations") || error_msg.contains("TEXT"),
        "Error should mention mathematical operations or TEXT: {}",
        error_msg
    );
}

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

    // Create a table with BOOLEAN and NUMERIC columns
    let table_id = create_test_table(
        &pool,
        schema_id,
        "boolean_math_table",
        vec![
            ("is_active", "BOOLEAN"),
            ("amount", "NUMERIC(10, 2)"),
            ("result", "NUMERIC(10, 2)")
        ]
    ).await;

    // No self-link needed - self-references are automatically allowed

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: r#"(* (steel_get_column "boolean_math_table" "is_active") "5")"#.to_string(),
        description: "Script that tries to use BOOLEAN in math".to_string(),
    };

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

    // Should fail because script uses BOOLEAN in mathematical operation
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("mathematical operations") || error_msg.contains("BOOLEAN"),
        "Error should mention mathematical operations or BOOLEAN: {}",
        error_msg
    );
}

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

    // Create a table with BIGINT and NUMERIC columns
    let table_id = create_test_table(
        &pool,
        schema_id,
        "bigint_math_table",
        vec![
            ("big_value", "BIGINT"),
            ("result", "NUMERIC(10, 2)")
        ]
    ).await;

    // No self-link needed - self-references are automatically allowed

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "result".to_string(),
        script: r#"(/ (steel_get_column "bigint_math_table" "big_value") "2")"#.to_string(),
        description: "Script that tries to use BIGINT in math".to_string(),
    };

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

    // Should fail because script uses BIGINT in mathematical operation
    assert!(result.is_err());
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("mathematical operations") || error_msg.contains("BIGINT"),
        "Error should mention mathematical operations or BIGINT: {}",
        error_msg
    );
}

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

    // Create a table with allowed column types
    let table_id = create_test_table(
        &pool,
        schema_id,
        "allowed_types_table",
        vec![
            ("name", "TEXT"),
            ("count", "INTEGER"),
            ("amount", "NUMERIC(10, 2)"),
            ("computed_value", "TEXT")
        ]
    ).await;

    // No self-link needed - self-references are automatically allowed

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "computed_value".to_string(), // This is TEXT (allowed as target)
        script: r#"(steel_get_column "allowed_types_table" "name")"#.to_string(),
        description: "Valid script using allowed types".to_string(),
    };

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

    // Should succeed
    assert!(result.is_ok(), "Valid script with allowed types should succeed");
    let response = result.unwrap();
    assert!(response.id > 0);
}

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

    // Create a table with allowed mathematical types
    let table_id = create_test_table(
        &pool,
        schema_id,
        "math_allowed_table",
        vec![
            ("quantity", "INTEGER"),
            ("price", "NUMERIC(10, 2)"),
            ("total", "NUMERIC(12, 2)")
        ]
    ).await;

    println!("Created table with ID: {}", table_id);
    println!("Schema ID: {}", schema_id);

    // Verify the table exists
    let table_check = sqlx::query!(
        "SELECT table_name FROM table_definitions WHERE id = $1",
        table_id
    )
    .fetch_one(&pool)
    .await;
    
    println!("Table verification: {:?}", table_check);

    let request = PostTableScriptRequest {
        table_definition_id: table_id,
        target_column: "total".to_string(),
        script: r#"(* (steel_get_column "math_allowed_table" "quantity")
                      (steel_get_column "math_allowed_table" "price"))"#.to_string(),
        description: "Valid mathematical operation with INTEGER and NUMERIC".to_string(),
    };

    println!("About to call post_table_script");
    let result = post_table_script(&pool, request).await;
    
    // SHOW THE ACTUAL ERROR
    if let Err(e) = &result {
        println!("ERROR: {}", e);
        println!("ERROR DEBUG: {:?}", e);
    }

    assert!(result.is_ok(), "Mathematical operations with INTEGER and NUMERIC should succeed");
    let response = result.unwrap();
    assert!(response.id > 0);
}

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

    // Create two separate tables
    let table_a_id = create_test_table(
        &pool,
        schema_id,
        "table_a",
        vec![("value_a", "INTEGER"), ("result", "INTEGER")]
    ).await;

    let _table_b_id = create_test_table(
        &pool,
        schema_id,
        "table_b", 
        vec![("value_b", "INTEGER")]
    ).await;

    // DON'T create a link between the tables

    let request = PostTableScriptRequest {
        table_definition_id: table_a_id,
        target_column: "result".to_string(),
        script: r#"(+ (steel_get_column "table_b" "value_b") "10")"#.to_string(),
        description: "Script trying to access unlinked table".to_string(),
    };

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

    // Should fail because tables aren't linked
    assert!(result.is_err(), "Should fail when tables aren't linked");
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("not linked") || error_msg.contains("link"),
        "Error should mention table linking requirement: {}",
        error_msg
    );
}

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

    // Create two separate tables
    let table_a_id = create_test_table(
        &pool,
        schema_id,
        "linked_table_a",
        vec![("value_a", "INTEGER"), ("result", "INTEGER")]
    ).await;

    let table_b_id = create_test_table(
        &pool,
        schema_id,
        "linked_table_b",
        vec![("value_b", "INTEGER")]
    ).await;

    // Create a link between the tables (table_a can access table_b)
    create_table_link(&pool, table_a_id, table_b_id, false).await;

    let request = PostTableScriptRequest {
        table_definition_id: table_a_id,
        target_column: "result".to_string(),
        script: r#"(+ (steel_get_column "linked_table_b" "value_b") "10")"#.to_string(),
        description: "Script accessing properly linked table".to_string(),
    };

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

    // Should succeed because tables are properly linked
    assert!(result.is_ok(), "Should succeed when tables are properly linked");
    let response = result.unwrap();
    assert!(response.id > 0);
}
