//! Unified error type for the rok ecosystem.
//!
//! `RokError` consolidates ORM, auth, and validation errors into one type that
//! implements [`axum::response::IntoResponse`], so handler return types become
//! simple `Result<T, RokError>` rather than nested match expressions.
//!
//! # Example
//!
//! ```rust,ignore
//! use rok_error::RokError;
//! use rok_orm::PgModel;
//! use axum::{Json, extract::Path};
//!
//! async fn get_user(Path(id): Path<i64>) -> Result<Json<User>, RokError> {
//!     let pool = rok_orm::pool::current_pool();
//!     let user = User::find_or_fail(&pool, id).await?;  // sqlx::Error::RowNotFound → 404
//!     Ok(Json(user))
//! }
//! ```

use thiserror::Error;

use rok_auth::AuthError;
use rok_validate::ValidationErrors;

/// Unified application error covering the most common rok error scenarios.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RokError {
    /// A database row was not found — maps to `404 Not Found`.
    #[error("not found")]
    NotFound,

    /// Access denied — maps to `403 Forbidden`.
    #[error("forbidden")]
    Forbidden,

    /// Request validation failed — maps to `422 Unprocessable Entity`.
    #[error("validation failed")]
    Validation(ValidationErrors),

    /// Authentication / authorization error from rok-auth.
    #[error("auth error: {0}")]
    Auth(#[from] AuthError),

    /// SQL / database error.
    #[error("database error: {0}")]
    Orm(sqlx::Error),

    /// Catch-all for unexpected errors.
    #[error("internal server error: {0}")]
    Internal(String),
}

// ── From impls ────────────────────────────────────────────────────────────────

impl From<sqlx::Error> for RokError {
    fn from(e: sqlx::Error) -> Self {
        match e {
            sqlx::Error::RowNotFound => Self::NotFound,
            other => Self::Orm(other),
        }
    }
}

impl From<ValidationErrors> for RokError {
    fn from(e: ValidationErrors) -> Self {
        Self::Validation(e)
    }
}

impl From<String> for RokError {
    fn from(s: String) -> Self {
        Self::Internal(s)
    }
}

impl From<&str> for RokError {
    fn from(s: &str) -> Self {
        Self::Internal(s.to_string())
    }
}

// ── IntoResponse (axum feature) ───────────────────────────────────────────────

#[cfg(feature = "axum")]
mod axum_impl {
    use super::RokError;
    use axum::{
        http::StatusCode,
        response::{IntoResponse, Response},
        Json,
    };

    impl IntoResponse for RokError {
        fn into_response(self) -> Response {
            match self {
                RokError::NotFound => {
                    let body = serde_json::json!({"message": "not found"});
                    (StatusCode::NOT_FOUND, Json(body)).into_response()
                }
                RokError::Forbidden => {
                    let body = serde_json::json!({"message": "forbidden"});
                    (StatusCode::FORBIDDEN, Json(body)).into_response()
                }
                RokError::Validation(errs) => {
                    let body = serde_json::json!({
                        "message": "Validation failed",
                        "errors": errs,
                    });
                    (StatusCode::UNPROCESSABLE_ENTITY, Json(body)).into_response()
                }
                RokError::Auth(e) => {
                    let status = match &e {
                        rok_auth::AuthError::InvalidToken
                        | rok_auth::AuthError::TokenExpired
                        | rok_auth::AuthError::InvalidCredentials => StatusCode::UNAUTHORIZED,
                        rok_auth::AuthError::Forbidden(_) => StatusCode::FORBIDDEN,
                        _ => StatusCode::INTERNAL_SERVER_ERROR,
                    };
                    let body = serde_json::json!({"message": e.to_string()});
                    (status, Json(body)).into_response()
                }
                RokError::Orm(_) | RokError::Internal(_) => {
                    let body = serde_json::json!({"message": "internal server error"});
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(body)).into_response()
                }
            }
        }
    }
}
