use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Application error types for chattyness.
///
/// All errors derive From for automatic conversion where applicable.
#[derive(Error, Debug)]
pub enum AppError {
    #[cfg(feature = "ssr")]
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[cfg(not(feature = "ssr"))]
    #[error("Database error: {0}")]
    Database(String),

    #[error("Validation error: {0}")]
    Validation(String),

    #[error("Authentication required")]
    Unauthorized,

    #[error("Forbidden: {0}")]
    Forbidden(String),

    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Conflict: {0}")]
    Conflict(String),

    #[error("Internal error: {0}")]
    Internal(String),

    #[error("Invalid credentials")]
    InvalidCredentials,

    #[error("Account suspended or banned")]
    AccountSuspended,

    #[error("Not a staff member")]
    NotStaffMember,

    #[error("Password reset required")]
    PasswordResetRequired,
}

/// API error response for JSON serialization.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    pub error: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<String>,
}

impl From<AppError> for ErrorResponse {
    fn from(err: AppError) -> Self {
        let code = match &err {
            AppError::Database(_) => Some("DATABASE_ERROR".to_string()),
            AppError::Validation(_) => Some("VALIDATION_ERROR".to_string()),
            AppError::Unauthorized => Some("UNAUTHORIZED".to_string()),
            AppError::Forbidden(_) => Some("FORBIDDEN".to_string()),
            AppError::NotFound(_) => Some("NOT_FOUND".to_string()),
            AppError::Conflict(_) => Some("CONFLICT".to_string()),
            AppError::Internal(_) => Some("INTERNAL_ERROR".to_string()),
            AppError::InvalidCredentials => Some("INVALID_CREDENTIALS".to_string()),
            AppError::AccountSuspended => Some("ACCOUNT_SUSPENDED".to_string()),
            AppError::NotStaffMember => Some("NOT_STAFF_MEMBER".to_string()),
            AppError::PasswordResetRequired => Some("PASSWORD_RESET_REQUIRED".to_string()),
        };

        ErrorResponse {
            error: err.to_string(),
            code,
        }
    }
}

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

    impl IntoResponse for AppError {
        fn into_response(self) -> Response {
            let status = match &self {
                AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
                AppError::Validation(_) => StatusCode::BAD_REQUEST,
                AppError::Unauthorized => StatusCode::UNAUTHORIZED,
                AppError::Forbidden(_) => StatusCode::FORBIDDEN,
                AppError::NotFound(_) => StatusCode::NOT_FOUND,
                AppError::Conflict(_) => StatusCode::CONFLICT,
                AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
                AppError::InvalidCredentials => StatusCode::UNAUTHORIZED,
                AppError::AccountSuspended => StatusCode::FORBIDDEN,
                AppError::NotStaffMember => StatusCode::FORBIDDEN,
                AppError::PasswordResetRequired => StatusCode::FORBIDDEN,
            };

            let body = ErrorResponse::from(self);
            (status, Json(body)).into_response()
        }
    }
}
