//! Common error types for HTTP services

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Standard error response format
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorResponse {
    /// Error code/type
    pub error: String,
    /// Human-readable error message
    pub message: String,
    /// Optional additional details
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

impl ErrorResponse {
    pub fn new(error: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            error: error.into(),
            message: message.into(),
            details: None,
        }
    }

    pub fn with_details(mut self, details: serde_json::Value) -> Self {
        self.details = Some(details);
        self
    }
}

/// Common HTTP application errors
#[derive(Debug, Error)]
pub enum HttpError {
    /// Bad request (400)
    #[error("Bad request: {0}")]
    BadRequest(String),

    /// Unauthorized (401)
    #[error("Unauthorized: {0}")]
    Unauthorized(String),

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

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

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

    /// Unprocessable entity (422)
    #[error("Validation failed: {0}")]
    ValidationError(String),

    /// Internal server error (500)
    #[error("Internal server error: {0}")]
    InternalServer(String),

    /// Service unavailable (503)
    #[error("Service unavailable: {0}")]
    ServiceUnavailable(String),

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

    /// External service error
    #[error("External service error: {0}")]
    ExternalService(String),

    /// Custom error with status code
    #[error("{message}")]
    Custom {
        status: StatusCode,
        error_code: String,
        message: String,
    },
}

impl HttpError {
    /// Get the HTTP status code for this error
    pub fn status_code(&self) -> StatusCode {
        match self {
            HttpError::BadRequest(_) => StatusCode::BAD_REQUEST,
            HttpError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
            HttpError::Forbidden(_) => StatusCode::FORBIDDEN,
            HttpError::NotFound(_) => StatusCode::NOT_FOUND,
            HttpError::Conflict(_) => StatusCode::CONFLICT,
            HttpError::ValidationError(_) => StatusCode::UNPROCESSABLE_ENTITY,
            HttpError::InternalServer(_) => StatusCode::INTERNAL_SERVER_ERROR,
            HttpError::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
            HttpError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
            HttpError::ExternalService(_) => StatusCode::BAD_GATEWAY,
            HttpError::Custom { status, .. } => *status,
        }
    }

    /// Get the error code string
    pub fn error_code(&self) -> String {
        match self {
            HttpError::BadRequest(_) => "BAD_REQUEST".to_string(),
            HttpError::Unauthorized(_) => "UNAUTHORIZED".to_string(),
            HttpError::Forbidden(_) => "FORBIDDEN".to_string(),
            HttpError::NotFound(_) => "NOT_FOUND".to_string(),
            HttpError::Conflict(_) => "CONFLICT".to_string(),
            HttpError::ValidationError(_) => "VALIDATION_ERROR".to_string(),
            HttpError::InternalServer(_) => "INTERNAL_SERVER_ERROR".to_string(),
            HttpError::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE".to_string(),
            HttpError::Database(_) => "DATABASE_ERROR".to_string(),
            HttpError::ExternalService(_) => "EXTERNAL_SERVICE_ERROR".to_string(),
            HttpError::Custom { error_code, .. } => error_code.clone(),
        }
    }

    /// Create a custom error with specific status code
    pub fn custom(
        status: StatusCode,
        error_code: impl Into<String>,
        message: impl Into<String>,
    ) -> Self {
        HttpError::Custom {
            status,
            error_code: error_code.into(),
            message: message.into(),
        }
    }
}

impl IntoResponse for HttpError {
    fn into_response(self) -> Response {
        let status = self.status_code();
        let error_code = self.error_code();
        let message = self.to_string();

        let body = ErrorResponse::new(error_code, message);

        (status, Json(body)).into_response()
    }
}

/// Convenience type for Result with HttpError
pub type HttpResult<T> = Result<T, HttpError>;

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

    #[test]
    fn test_error_status_codes() {
        assert_eq!(
            HttpError::BadRequest("test".into()).status_code(),
            StatusCode::BAD_REQUEST
        );
        assert_eq!(
            HttpError::Unauthorized("test".into()).status_code(),
            StatusCode::UNAUTHORIZED
        );
        assert_eq!(
            HttpError::NotFound("test".into()).status_code(),
            StatusCode::NOT_FOUND
        );
    }

    #[test]
    fn test_custom_error() {
        let err = HttpError::custom(StatusCode::IM_A_TEAPOT, "TEAPOT", "I'm a teapot");
        assert_eq!(err.status_code(), StatusCode::IM_A_TEAPOT);
        assert_eq!(err.error_code(), "TEAPOT");
    }
}
