// src/tables_data/handlers/post_table_data.rs

use tonic::Status;
use sqlx::{PgPool, Arguments};
use sqlx::postgres::PgArguments;
use chrono::{DateTime, Utc};
use common::proto::komp_ac::tables_data::{PostTableDataRequest, PostTableDataResponse};
use std::collections::HashMap;
use std::sync::Arc;
use prost_types::value::Kind;
use rust_decimal::Decimal;
use std::str::FromStr;

use crate::steel::server::execution::{self, Value};

use crate::indexer::{IndexCommand, IndexCommandData};
use tokio::sync::mpsc;
use tracing::error;

/// Inserts new record into a table with validation through associated scripts.
/// 
/// This function handles creating new records in dynamically defined tables while:
/// - Validating permissions and column existence
/// - Executing validation scripts for computed columns
/// - Performing type-safe database insertions
/// - Maintaining search index consistency
pub async fn post_table_data(
    db_pool: &PgPool,
    request: PostTableDataRequest,
    indexer_tx: &mpsc::Sender<IndexCommand>,
) -> Result<PostTableDataResponse, Status> {
    let profile_name = request.profile_name;
    let table_name = request.table_name;

    // Fetch schema and table metadata
    let schema = sqlx::query!(
        "SELECT id FROM schemas WHERE name = $1",
        profile_name
    )
    .fetch_optional(db_pool)
    .await
    .map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?;

    let schema_id = schema.ok_or_else(|| Status::not_found("Profile not found"))?.id;

    let table_def = sqlx::query!(
        r#"SELECT id, columns FROM table_definitions
        WHERE schema_id = $1 AND table_name = $2"#,
        schema_id,
        table_name
    )
    .fetch_optional(db_pool)
    .await
    .map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?;

    let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;

    // Parse column definitions from JSON format
    let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
        .map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;

    let mut columns = Vec::new();
    for col_def in columns_json {
        let parts: Vec<&str> = col_def.splitn(2, ' ').collect();
        if parts.len() != 2 {
            return Err(Status::internal("Invalid column format"));
        }
        let name = parts[0].trim_matches('"').to_string();
        let sql_type = parts[1].to_string();
        columns.push((name, sql_type));
    }

    // Build list of valid system columns (foreign keys and special columns)
    let fk_columns = sqlx::query!(
        r#"SELECT ltd.table_name
        FROM table_definition_links tdl
        JOIN table_definitions ltd ON tdl.linked_table_id = ltd.id
        WHERE tdl.source_table_id = $1"#,
        table_def.id
    )
    .fetch_all(db_pool)
    .await
    .map_err(|e| Status::internal(format!("Foreign key lookup error: {}", e)))?;

    let mut system_columns = vec!["deleted".to_string()];
    for fk in fk_columns {
        system_columns.push(format!("{}_id", fk.table_name));
    }

    let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();

    // Validate all requested columns exist and are insertable
    let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
    for key in request.data.keys() {
        if !system_columns_set.contains(key.as_str()) &&
           !user_columns.contains(&&key.to_string()) {
            return Err(Status::invalid_argument(format!("Invalid column: {}", key)));
        }
    }

    // Convert proto values to strings for script execution context
    let mut string_data_for_scripts = HashMap::new();
    for (key, proto_value) in &request.data {
        let str_val = match &proto_value.kind {
            Some(Kind::StringValue(s)) => {
                let trimmed = s.trim();
                if trimmed.is_empty() {
                    continue;
                }
                trimmed.to_string()
            },
            Some(Kind::NumberValue(n)) => n.to_string(),
            Some(Kind::BoolValue(b)) => b.to_string(),
            Some(Kind::NullValue(_)) | None => continue,
            Some(Kind::StructValue(_)) | Some(Kind::ListValue(_)) => {
                return Err(Status::invalid_argument(format!("Unsupported type for script validation in column '{}'", key)));
            }
        };
        string_data_for_scripts.insert(key.clone(), str_val);
    }

    // Execute validation scripts for computed columns
    let scripts = sqlx::query!(
        "SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
        table_def.id
    )
    .fetch_all(db_pool)
    .await
    .map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;

    for script_record in scripts {
        let target_column = script_record.target_column;

        // All script target columns are required for INSERT operations
        let user_value = string_data_for_scripts.get(&target_column)
            .ok_or_else(|| Status::invalid_argument(
                format!("Script target column '{}' is required", target_column)
            ))?;



        // Execute script to calculate expected value
        let script_result = execution::execute_script(
            script_record.script,
            "STRINGS",
            Arc::new(db_pool.clone()),
            schema_id,
            profile_name.clone(),
            table_name.clone(),
            string_data_for_scripts.clone(),
        )
        .await
        .map_err(|e| Status::invalid_argument(
            format!("Script execution failed for '{}': {}", target_column, e)
        ))?;

        let Value::Strings(mut script_output) = script_result;

        let expected_value = script_output.pop()
            .ok_or_else(|| Status::internal("Script returned no values"))?;

        // Perform numeric comparison for decimal values to handle equivalent representations
        // (e.g., "76.5" should equal "76.50")
        let user_decimal = Decimal::from_str(user_value).map_err(|_| {
            Status::invalid_argument(format!(
                "Invalid decimal format provided for column '{}': {}",
                target_column, user_value
            ))
        })?;

        let expected_decimal = Decimal::from_str(&expected_value).map_err(|_| {
            Status::internal(format!(
                "Script for column '{}' produced an invalid decimal format: {}",
                target_column, expected_value
            ))
        })?;

        if user_decimal != expected_decimal {
            return Err(Status::invalid_argument(format!(
                "Validation failed for column '{}': Script calculated '{}', but user provided '{}'",
                target_column, expected_value, user_value
            )));
        }
    }

    // Build parameterized INSERT query with type-safe parameter binding
    let mut params = PgArguments::default();
    let mut columns_list = Vec::new();
    let mut placeholders = Vec::new();
    let mut param_idx = 1;

    for (col, proto_value) in request.data {
        // Determine SQL type for proper parameter binding
        let sql_type = if system_columns_set.contains(col.as_str()) {
            match col.as_str() {
                "deleted" => "BOOLEAN",
                _ if col.ends_with("_id") => "BIGINT",
                _ => return Err(Status::invalid_argument("Invalid system column")),
            }
        } else {
            columns.iter()
                .find(|(name, _)| name == &col)
                .map(|(_, sql_type)| sql_type.as_str())
                .ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
        };

        // Handle NULL values first
        let kind = match &proto_value.kind {
            None | Some(Kind::NullValue(_)) => {
                match sql_type {
                    "BOOLEAN" => params.add(None::<bool>),
                    "TEXT" => params.add(None::<String>),
                    "TIMESTAMPTZ" => params.add(None::<DateTime<Utc>>),
                    "BIGINT" => params.add(None::<i64>),
                    "INTEGER" => params.add(None::<i32>),
                    s if s.starts_with("NUMERIC") => params.add(None::<Decimal>),
                    _ => return Err(Status::invalid_argument(format!("Unsupported type for null value: {}", sql_type))),
                }.map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;

                columns_list.push(format!("\"{}\"", col));
                placeholders.push(format!("${}", param_idx));
                param_idx += 1;
                continue;
            }
            Some(k) => k,
        };

        // Handle typed values with validation
        match sql_type {
            "TEXT" => {
                if let Kind::StringValue(value) = kind {
                    let trimmed_value = value.trim();

                    if trimmed_value.is_empty() {
                        params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
                    } else {
                        // Apply business rule validation (e.g., phone number length)
                        if col == "telefon" && trimmed_value.len() > 15 {
                            return Err(Status::internal(format!("Value too long for {}", col)));
                        }
                        params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
                    }
                } else {
                    return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
                }
            },
            "BOOLEAN" => {
                if let Kind::BoolValue(val) = kind {
                    params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
                } else {
                    return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
                }
            },
            "TIMESTAMPTZ" => {
                if let Kind::StringValue(value) = kind {
                    let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
                    params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
                } else {
                    return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
                }
            },
            "BIGINT" => {
                if let Kind::NumberValue(val) = kind {
                    if val.fract() != 0.0 {
                        return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
                    }

                    // Validate conversion is reversible to catch overflow, underflow, and precision loss
                    let as_i64 = *val as i64;
                    if (as_i64 as f64) != *val {
                        return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
                    }

                    params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
                } else {
                    return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
                }
            },
            "INTEGER" => {
                if let Kind::NumberValue(val) = kind {
                    if val.fract() != 0.0 {
                        return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
                    }

                    // Validate conversion is reversible to catch overflow, underflow, and precision loss
                    let as_i32 = *val as i32;
                    if (as_i32 as f64) != *val {
                        return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
                    }

                    params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
                } else {
                    return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
                }
            },
            s if s.starts_with("NUMERIC") => {
                // Decimal values must be provided as strings for precise parsing
                let decimal_val = match kind {
                    Kind::StringValue(s) => {
                        let trimmed = s.trim();
                        if trimmed.is_empty() {
                            None // Treat empty string as NULL
                        } else {
                            Some(Decimal::from_str(trimmed).map_err(|_| {
                                Status::invalid_argument(format!(
                                    "Invalid decimal string format for column '{}': {}",
                                    col, s
                                ))
                            })?)
                        }
                    }
                    // Reject other types to ensure precise decimal handling
                    _ => {
                        return Err(Status::invalid_argument(format!(
                            "Expected a string representation for decimal column '{}', but received a different type.",
                            col
                        )));
                    }
                };

                params.add(decimal_val).map_err(|e| {
                    Status::invalid_argument(format!(
                        "Failed to add decimal parameter for {}: {}",
                        col, e
                    ))
                })?;
            },
            _ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
        }

        columns_list.push(format!("\"{}\"", col));
        placeholders.push(format!("${}", param_idx));
        param_idx += 1;
    }

    if columns_list.is_empty() {
        return Err(Status::invalid_argument("No valid columns to insert"));
    }

    // Execute the INSERT query
    let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
        db_pool,
        &profile_name,
        &table_name,
    )
    .await?;

    let sql = format!(
        "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
        qualified_table,
        columns_list.join(", "),
        placeholders.join(", ")
    );

    let result = sqlx::query_scalar_with::<_, i64, _>(&sql, params)
        .fetch_one(db_pool)
        .await;

    let inserted_id = match result {
        Ok(id) => id,
        Err(e) => {
            // Handle specific database errors with user-friendly messages
            if let Some(db_err) = e.as_database_error() {
                if db_err.code() == Some(std::borrow::Cow::Borrowed("22P02")) ||
                   db_err.code() == Some(std::borrow::Cow::Borrowed("22003")) {
                     return Err(Status::invalid_argument(format!(
                        "Numeric field overflow or invalid format. Check precision and scale. Details: {}", db_err.message()
                    )));
                }
                if db_err.code() == Some(std::borrow::Cow::Borrowed("42P01")) {
                    return Err(Status::internal(format!(
                        "Table '{}' is defined but does not physically exist in the database as {}",
                        table_name, qualified_table
                    )));
                }
            }
            return Err(Status::internal(format!("Insert failed: {}", e)));
        }
    };

    // Queue record for search index update
    let command = IndexCommand::AddOrUpdate(IndexCommandData {
        table_name: table_name.clone(),
        row_id: inserted_id,
    });

    if let Err(e) = indexer_tx.send(command).await {
        error!(
            "CRITICAL: DB insert for table '{}' (id: {}) succeeded but failed to queue for indexing: {}. Search index is now inconsistent.",
            table_name, inserted_id, e
        );
    }

    Ok(PostTableDataResponse {
        success: true,
        message: "Data inserted successfully".into(),
        inserted_id,
    })
}
