//! tokio-postgres error parser for converting database errors into ProcessedError responses.
use actix_web::http::StatusCode;
use serde_json::json;

use crate::error::{ErrorCategory, ProcessedError, generate_trace_id};

pub fn process_tokio_postgres_error(
    err: &tokio_postgres::Error,
    context: Option<&str>,
) -> ProcessedError {
    let trace_id: String = generate_trace_id();
    if let Some(db_err) = err.as_db_error() {
        let code: &str = db_err.code().code();
        let message: String = db_err.message().to_string();

        let (status, error_code, category) = match code {
            "23505" => (
                StatusCode::CONFLICT,
                "unique_violation",
                ErrorCategory::DatabaseConstraint,
            ),
            "23503" => (
                StatusCode::CONFLICT,
                "foreign_key_violation",
                ErrorCategory::DatabaseConstraint,
            ),
            "23502" => (
                StatusCode::BAD_REQUEST,
                "not_null_violation",
                ErrorCategory::DatabaseConstraint,
            ),
            _ if code.starts_with("22") => (
                StatusCode::BAD_REQUEST,
                "data_exception",
                ErrorCategory::QuerySyntax,
            ),
            _ if code.starts_with("42") => (
                StatusCode::BAD_REQUEST,
                "query_syntax",
                ErrorCategory::QuerySyntax,
            ),
            _ => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "db_error",
                ErrorCategory::Internal,
            ),
        };

        let mut processed: ProcessedError =
            ProcessedError::new(category, status, error_code, message, trace_id)
                .with_metadata("sql_state", json!(code));

        if let Some(ctx) = context {
            processed = processed.with_metadata("context", json!(ctx));
        }

        return processed;
    }

    let mut processed: ProcessedError = ProcessedError::new(
        ErrorCategory::DatabaseConnection,
        StatusCode::BAD_GATEWAY,
        "postgres_driver_error",
        err.to_string(),
        trace_id,
    );
    if let Some(ctx) = context {
        processed = processed.with_metadata("context", json!(ctx));
    }
    processed
}

pub fn process_tokio_postgres_db_error(
    sql_state: &str,
    message: &str,
    context: Option<&str>,
) -> ProcessedError {
    let trace_id: String = generate_trace_id();
    let (status, error_code, category) = match sql_state {
        "23505" => (
            StatusCode::CONFLICT,
            "unique_violation",
            ErrorCategory::DatabaseConstraint,
        ),
        "23503" => (
            StatusCode::CONFLICT,
            "foreign_key_violation",
            ErrorCategory::DatabaseConstraint,
        ),
        "23502" => (
            StatusCode::BAD_REQUEST,
            "not_null_violation",
            ErrorCategory::DatabaseConstraint,
        ),
        _ if sql_state.starts_with("22") => (
            StatusCode::BAD_REQUEST,
            "data_exception",
            ErrorCategory::QuerySyntax,
        ),
        _ if sql_state.starts_with("42") => (
            StatusCode::BAD_REQUEST,
            "query_syntax",
            ErrorCategory::QuerySyntax,
        ),
        _ => (
            StatusCode::INTERNAL_SERVER_ERROR,
            "db_error",
            ErrorCategory::Internal,
        ),
    };

    let mut processed: ProcessedError =
        ProcessedError::new(category, status, error_code, message.to_string(), trace_id)
            .with_metadata("sql_state", json!(sql_state));

    if let Some(ctx) = context {
        processed = processed.with_metadata("context", json!(ctx));
    }

    processed
}
