//! SQL query endpoint for executing queries against supported drivers.
//!
//! Overview
//! --------
//! This endpoint accepts a JSON payload with `query`, `driver`, and `db_name`,
//! dispatches to the selected backend (Athena/Scylla, PostgreSQL, or Supabase),
//! and returns a normalized JSON response that includes timing and status fields.
//!
//! Request shape (JSON)
//! --------------------
//! - `query`: SQL string to execute.
//! - `driver`: One of `"athena" | "postgresql" | "supabase"`.
//! - `db_name`: Logical database name (used by some drivers, e.g. PostgreSQL/Supabase).
//!
//! For `driver: postgresql`, use `X-Athena-Client` or `X-JDBC-URL` (same resolution rules as gateway `resolve_postgres_pool`).
//!
//! Response shape (success)
//! ------------------------
//! For relational backends (PostgreSQL/Supabase) a typical successful response resembles:
//!
//! ```json
//! {
//!   "data": [ { "col": "value" } ],
//!   "db_name": "example_db",
//!   "duration": 12,
//!   "message": "Successfully executed query",
//!   "status": "success"
//! }
//! ```
//!
//! For Athena/Scylla, results are returned as a JSON array of rows:
//!
//! ```json
//! [ { "col": "value" } ]
//! ```
//!
//! Error responses
//! ---------------
//! - `400 Bad Request` if an unsupported `driver` is provided.
//! - `503 Service Unavailable` when Athena/Scylla is unreachable (connection errors).
//! - `500 Internal Server Error` for driver-specific execution failures.
//!
//! Tracing and logging
//! -------------------
//! - The handler uses `tracing` with `#[instrument]` to attach request-scoped fields
//!   (`driver`, `db_name`, `query_len`).
//! - Logs are structured; long SQL is truncated to a short preview to limit noise.
//! - Configure verbosity with `RUST_LOG`, e.g. `RUST_LOG=info,athena=debug`.
//!
//! Security
//! --------
//! - Ensure queries come from trusted sources or are validated to avoid unsafe operations.
//! - Avoid logging full SQL containing sensitive data; only a short preview is emitted.

use actix_web::{HttpRequest, Responder, post, web};
use serde_json::json;
use std::collections::HashMap;
use std::time::Instant;
use tracing::{debug, error, info, warn};

const MAX_SQL_DRIVER_LEN: usize = 32;

/// True if the error is a missing-relation / undefined-table style error (e.g. auth.users missing).
fn is_missing_relation(err: &sqlx::Error) -> bool {
    if let sqlx::Error::Database(db) = err {
        let msg = db.message();
        let code = db.code().as_ref().map(|c| c.to_string());
        let code_str = code.as_deref();
        code_str == Some("42P01") || msg.contains("does not exist")
    } else {
        false
    }
}

// crate imports
use crate::AppState;
use crate::api::client_context::ATHENA_CLIENT_HEADER;
use crate::api::gateway::auth::{query_right, require_admin_or_gateway};
use crate::api::gateway::contracts::GatewaySqlExecutionRequest;
use crate::api::gateway::pool_resolver::resolve_postgres_pool;
use crate::api::rate_limit::check_inbound_optional;
use crate::api::response::{
    api_ok, api_success_value, bad_request, internal_error, processed_error, service_unavailable,
};
use crate::athena::resolver::{
    AthenaClientResolveError, AthenaResolvedQueryBackend, resolve_query_backend,
};
use crate::drivers::postgresql::raw_sql::{execute_postgres_sql, normalize_sql_query};
use crate::drivers::scylla::client::{execute_query, execute_query_with_info};
use crate::drivers::supabase::execute_query_supabase;
use crate::error::sqlx_parser::process_sqlx_error_with_context;

/// Builder-friendly representation of a SQL query request with parameters and cache key.
pub struct SqlQuery {
    pub query: String,
    pub params: HashMap<String, String>,
    pub cache_key: String,
    pub driver: String,
}

impl SqlQuery {
    pub fn new(query: String, params: HashMap<String, String>, cache_key: String) -> Self {
        Self {
            query,
            params,
            cache_key,
            driver: "scylla".to_string(),
        }
    }
}

fn normalize_sql_driver(driver: &str) -> Option<&'static str> {
    match driver.trim().to_ascii_lowercase().as_str() {
        "athena" | "scylla" | "scylladb" => Some("athena"),
        "postgresql" | "postgres" => Some("postgresql"),
        "supabase" => Some("supabase"),
        _ => None,
    }
}

fn scylla_resolution_error_response(err: AthenaClientResolveError) -> actix_web::HttpResponse {
    match err {
        AthenaClientResolveError::Inactive { client_name } => bad_request(
            "Scylla client is inactive",
            format!("Client '{}' is inactive.", client_name),
        ),
        AthenaClientResolveError::Frozen { client_name } => bad_request(
            "Scylla client is frozen",
            format!("Client '{}' is frozen.", client_name),
        ),
        AthenaClientResolveError::InvalidMetadata {
            client_name,
            message,
        } => bad_request(
            "Invalid Scylla client metadata",
            format!("Client '{}' {}", client_name, message),
        ),
        AthenaClientResolveError::Lookup {
            client_name,
            message,
        } => service_unavailable(
            "Failed to resolve Scylla client",
            format!("Client '{}' lookup failed: {}", client_name, message),
        ),
    }
}

async fn execute_scylla_request(
    req: &HttpRequest,
    app_state: &AppState,
    sql_text: String,
) -> Result<actix_web::HttpResponse, actix_web::HttpResponse> {
    let client_name = req
        .headers()
        .get(ATHENA_CLIENT_HEADER)
        .and_then(|value| value.to_str().ok())
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string);

    let resolved_backend = match client_name.as_deref() {
        Some(client_name) => match resolve_query_backend(app_state, client_name).await {
            Ok(resolution) => resolution,
            Err(err) => return Err(scylla_resolution_error_response(err)),
        },
        None => None,
    };

    let result = match resolved_backend {
        Some(AthenaResolvedQueryBackend::Scylla {
            connection_info, ..
        }) => execute_query_with_info(sql_text.clone(), &connection_info).await,
        _ => execute_query(sql_text.clone()).await,
    };

    match result {
        Ok((rows, columns)) => Ok(api_success_value(
            "Successfully executed query",
            json!({
                "rows": rows,
                "columns": columns,
                "driver": "scylla",
            }),
        )),
        Err(err) => {
            let error_msg: String = err.to_string();
            error!(error = %error_msg, "athena query failed");

            if error_msg.contains("connection")
                && (error_msg.contains("refused")
                    || error_msg.contains("Control connection pool error")
                    || error_msg.contains("target machine actively refused"))
            {
                warn!("athena/scylladb unreachable");
                return Err(service_unavailable(
                    "Athena server is not reachable",
                    format!(
                        "Connection error: {}. Ensure ScyllaDB is running on the configured port.",
                        error_msg
                    ),
                ));
            }

            warn!(
                client = %client_name.unwrap_or_else(|| "<default>".to_string()),
                failed_query_preview = %sql_text.chars().take(100).collect::<String>(),
                "failed query preview"
            );

            Err(internal_error(
                "Query execution failed",
                format!("Athena error: {}", error_msg),
            ))
        }
    }
}

// #[instrument(
//     skip(body),
//     fields(
//         driver = %body.driver,
//         db_name = %body.db_name,
//         query_len = body.query.len()
//     )
// )]
async fn handle_sql_query(
    req: HttpRequest,
    body: web::Json<GatewaySqlExecutionRequest>,
    app_state: web::Data<AppState>,
) -> actix_web::HttpResponse {
    let client_for_auth: Option<String> = req
        .headers()
        .get(ATHENA_CLIENT_HEADER)
        .and_then(|value| value.to_str().ok())
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string);

    if let Err(resp) = require_admin_or_gateway(
        &req,
        app_state.get_ref(),
        client_for_auth.as_deref(),
        vec![query_right()],
    )
    .await
    {
        return resp;
    }
    if let Err(resp) = check_inbound_optional(
        &app_state.inbound_rate_limit_raw_sql,
        app_state.inbound_rate_limit_trust_x_forwarded_for,
        &req,
    ) {
        return resp;
    }

    let driver_trimmed: String = body.driver.trim().to_string();
    if driver_trimmed.is_empty() || driver_trimmed.len() > MAX_SQL_DRIVER_LEN {
        return bad_request(
            "Invalid driver specified",
            "driver must be a non-empty supported identifier",
        );
    }
    let driver: &str = match normalize_sql_driver(&driver_trimmed) {
        Some(driver) => driver,
        None => {
            debug!(
                driver_len = driver_trimmed.len(),
                "unsupported sql driver requested"
            );
            return bad_request(
                "Invalid driver specified",
                "Driver is not supported. Use athena/scylla, postgresql, or supabase.",
            );
        }
    };

    let sql_text: String = body.query.clone();

    if driver == "postgresql" {
        let pool = match resolve_postgres_pool(&req, app_state.get_ref()).await {
            Ok(pool) => pool,
            Err(resp) => return resp,
        };

        let normalized_sql = normalize_sql_query(&body.query);
        let start_time: Instant = Instant::now();

        if normalized_sql.is_empty() {
            return bad_request(
                "Invalid query",
                "Query cannot be empty or contain only semicolons.",
            );
        }

        match execute_postgres_sql(&pool, &normalized_sql).await {
            Ok(result) => {
                let duration: u64 = start_time.elapsed().as_millis() as u64;

                info!("postgresql query ok");
                return api_success_value(
                    "Successfully executed query",
                    json!({
                        "rows": result.rows,
                        "db_name": body.db_name.clone(),
                        "duration_ms": duration,
                        "statement_count": result.summary.statement_count,
                        "rows_affected": result.summary.rows_affected,
                        "returned_row_count": result.summary.returned_row_count,
                    }),
                );
            }
            Err(e) => {
                if is_missing_relation(&e) {
                    warn!(
                        error = %e,
                        "postgresql query failed (missing relation) — table/schema may be absent for this client",
                    );
                } else {
                    error!(error = %e, "postgresql query failed");
                }
                let processed = process_sqlx_error_with_context(&e, Some(&body.db_name));
                return processed_error(processed);
            }
        }
    }

    if driver == "supabase" {
        match execute_query_supabase(sql_text.clone(), body.db_name.clone()).await {
            Ok(results) => {
                info!("supabase query ok");
                return api_ok(results);
            }
            Err(e) => {
                error!(error = %e, "supabase query failed");
                return internal_error("Query execution failed", format!("Supabase error: {}", e));
            }
        }
    }

    match execute_scylla_request(&req, app_state.get_ref(), sql_text.clone()).await {
        Ok(response) => response,
        Err(response) => response,
    }
}

#[post("/query/sql")]
/// Execute the given SQL against the specified `driver` and return JSON results.
///
/// Examples
/// --------
/// Request (POST `/query/sql`):
///
/// ```json
/// {
///   "query": "select 1 as col",
///   "driver": "postgresql",
///   "db_name": "example_db"
/// }
/// ```
///
/// Successful response (PostgreSQL/Supabase):
///
/// ```json
/// {
///   "data": [{ "col": 1 }],
///   "db_name": "example_db",
///   "duration": 5,
///   "message": "Successfully executed query",
///   "status": "success"
/// }
/// ```
///
/// On failure, a structured error is returned with an appropriate HTTP status code.
pub async fn sql_query(
    req: HttpRequest,
    body: web::Json<GatewaySqlExecutionRequest>,
    app_state: web::Data<AppState>,
) -> impl Responder {
    handle_sql_query(req, body, app_state).await
}

/// Alias route for SQL execution so SDKs can consistently target `/gateway/sql`.
#[post("/gateway/sql")]
pub async fn gateway_sql_query(
    req: HttpRequest,
    body: web::Json<GatewaySqlExecutionRequest>,
    app_state: web::Data<AppState>,
) -> impl Responder {
    handle_sql_query(req, body, app_state).await
}

#[cfg(test)]
mod tests {
    use super::normalize_sql_driver;

    #[test]
    fn normalize_sql_driver_accepts_scylla_aliases() {
        assert_eq!(normalize_sql_driver("athena"), Some("athena"));
        assert_eq!(normalize_sql_driver("scylla"), Some("athena"));
        assert_eq!(normalize_sql_driver("scylladb"), Some("athena"));
        assert_eq!(normalize_sql_driver("postgres"), Some("postgresql"));
        assert_eq!(normalize_sql_driver("supabase"), Some("supabase"));
        assert_eq!(normalize_sql_driver("mysql"), None);
    }
}
