use super::AppJson;

use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::response::Response;
use serde::Serialize;
use std::result::Result as StdResult;

pub type Result<T> = StdResult<T, AppError>;

pub enum AppError {
    JsonRejection(JsonRejection),
    DbError(surrealdb::Error),
    InvalidCredentials,
    Arbitrary(&'static str, String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        #[derive(Serialize)]
        struct ErrorResponse {
            code: &'static str,
            message: String,
        }

        let (status, code, message) = match self {
            AppError::JsonRejection(rejection) => {
                // Error caused by bad user input, so do not log
                (rejection.status(), "invalid-json", rejection.body_text())
            }
            AppError::DbError(dbe) => {
                tracing::error!(err_source = "surrealdb", "Database error: {dbe}");
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "db-error",
                    "a database error occured".to_string(),
                )
            }
            AppError::InvalidCredentials => (
                StatusCode::UNAUTHORIZED,
                "invalid-credentials",
                "Invalid credentials given.".to_string(),
            ),
            AppError::Arbitrary(code, message) => {
                (StatusCode::INTERNAL_SERVER_ERROR, code, message)
            }
        };

        (status, AppJson(ErrorResponse { code, message })).into_response()
    }
}

impl From<JsonRejection> for AppError {
    fn from(value: JsonRejection) -> Self {
        Self::JsonRejection(value)
    }
}

impl From<surrealdb::Error> for AppError {
    fn from(value: surrealdb::Error) -> Self {
        Self::DbError(value)
    }
}