// src/steel/server/execution.rs

use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
use steel::rvals::SteelVal;
use super::functions::{SteelContext, convert_row_data_for_steel};
use steel_decimal::registry::FunctionRegistry;
use sqlx::PgPool;
use std::sync::Arc;
use std::collections::HashMap;
use thiserror::Error;
use tracing::{debug, error};

/// Represents different types of values that can be returned from Steel script execution.
#[derive(Debug)]
pub enum Value {
    Strings(Vec<String>),
}

/// Errors that can occur during Steel script execution.
#[derive(Debug, Error)]
pub enum ExecutionError {
    #[error("Script execution failed: {0}")]
    RuntimeError(String),
    #[error("Type conversion error: {0}")]
    TypeConversionError(String),
    #[error("Unsupported target type: {0}")]
    UnsupportedType(String),
}

/// Creates a Steel execution context with proper boolean value conversion.
pub async fn create_steel_context_with_boolean_conversion(
    current_table: String,
    schema_id: i64,
    schema_name: String,
    mut row_data: HashMap<String, String>,
    db_pool: Arc<PgPool>,
) -> Result<SteelContext, ExecutionError> {
    // Convert boolean values in row_data to Steel format
    convert_row_data_for_steel(&db_pool, schema_id, &current_table, &mut row_data)
        .await
        .map_err(|e| {
            error!("Failed to convert row data for Steel: {}", e);
            ExecutionError::RuntimeError(format!("Failed to convert row data: {}", e))
        })?;

    Ok(SteelContext {
        current_table,
        schema_id,
        schema_name,
        row_data,
        db_pool,
    })
}

/// Executes a Steel script with database context and type-safe result processing.
pub async fn execute_script(
    script: String,
    target_type: &str,
    db_pool: Arc<PgPool>,
    schema_id: i64,
    schema_name: String,
    current_table: String,
    row_data: HashMap<String, String>,
) -> Result<Value, ExecutionError> {
    let mut vm = Engine::new();

    // Create execution context with proper boolean value conversion
    let context = create_steel_context_with_boolean_conversion(
        current_table,
        schema_id,
        schema_name,
        row_data,
        db_pool.clone(),
    ).await?;

    let context = Arc::new(context);

    // Register database access functions
    register_steel_functions(&mut vm, context.clone());

    // Register decimal math operations
    register_decimal_math_functions(&mut vm);

    // Register row data as variables in the Steel VM for get-var access
    let mut define_script = String::new();

    for (key, value) in &context.row_data {
        // Register only bare variable names for get-var access
        define_script.push_str(&format!("(define {} \"{}\")\n", key, value));
    }

    // Execute variable definitions if any exist
    if !define_script.is_empty() {
        vm.compile_and_run_raw_program(define_script)
            .map_err(|e| ExecutionError::RuntimeError(format!("Failed to register variables: {}", e)))?;
    }

    // Also register variables using the decimal registry as backup method
    FunctionRegistry::register_variables(&mut vm, context.row_data.clone());

    // Execute the main script
    let results = vm.compile_and_run_raw_program(script.clone())
        .map_err(|e| {
            error!("Steel script execution failed: {}", e);
            error!("Script was: {}", script);
            error!("Available variables were: {:?}", context.row_data);
            ExecutionError::RuntimeError(e.to_string())
        })?;

    // Convert results to the requested target type
    match target_type {
        "STRINGS" => process_string_results(results),
        _ => Err(ExecutionError::UnsupportedType(target_type.into()))
    }
}

/// Registers Steel functions for database access within the VM context.
fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
    debug!("Registering Steel functions with context");

    // Register column access function for current and related tables
    vm.register_fn("steel_get_column", {
        let ctx = context.clone();
        move |table: String, column: String| {
            debug!("steel_get_column called with table: '{}', column: '{}'", table, column);
            ctx.steel_get_column(&table, &column)
                .map_err(|e| {
                    error!("steel_get_column failed: {:?}", e);
                    e.to_string()
                })
        }
    });

    // Register indexed column access for comma-separated values
    vm.register_fn("steel_get_column_with_index", {
        let ctx = context.clone();
        move |table: String, index: i64, column: String| {
            debug!("steel_get_column_with_index called with table: '{}', index: {}, column: '{}'", table, index, column);
            ctx.steel_get_column_with_index(&table, index, &column)
                .map_err(|e| {
                    error!("steel_get_column_with_index failed: {:?}", e);
                    e.to_string()
                })
        }
    });

    // Register safe SQL query execution
    vm.register_fn("steel_query_sql", {
        let ctx = context.clone();
        move |query: String| {
            debug!("steel_query_sql called with query: '{}'", query);
            ctx.steel_query_sql(&query)
                .map_err(|e| {
                    error!("steel_query_sql failed: {:?}", e);
                    e.to_string()
                })
        }
    });
}

/// Registers decimal mathematics functions in the Steel VM.
fn register_decimal_math_functions(vm: &mut Engine) {
    debug!("Registering decimal math functions");
    FunctionRegistry::register_all(vm);
}

/// Processes Steel script results into string format for consistent output.
fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionError> {
    let mut strings = Vec::new();

    for result in results {
        let result_str = match result {
            SteelVal::StringV(s) => s.to_string(),
            SteelVal::NumV(n) => n.to_string(),
            SteelVal::IntV(i) => i.to_string(),
            SteelVal::BoolV(b) => b.to_string(),
            _ => {
                error!("Unexpected result type: {:?}", result);
                return Err(ExecutionError::TypeConversionError(
                    format!("Expected string-convertible type, got {:?}", result)
                ));
            }
        };
        strings.push(result_str);
    }

    Ok(Value::Strings(strings))
}
