use axum::{
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response as AxumResponse},
    Json,
};
use serde::Serialize;
use serde_json::json;

/// Ergonomic builder for common HTTP responses.
///
/// Every method returns a concrete [`axum::response::Response`] so it can be
/// returned directly from any Axum handler.
pub struct Response;

impl Response {
    pub fn json<T: Serialize>(data: T) -> AxumResponse {
        (StatusCode::OK, Json(data)).into_response()
    }

    pub fn created<T: Serialize>(data: T) -> AxumResponse {
        (StatusCode::CREATED, Json(data)).into_response()
    }

    pub fn no_content() -> AxumResponse {
        StatusCode::NO_CONTENT.into_response()
    }

    pub fn error(msg: impl Into<String>, status: u16) -> AxumResponse {
        let code = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
        (code, Json(json!({ "error": msg.into() }))).into_response()
    }

    pub fn not_found(entity: impl Into<String>) -> AxumResponse {
        (
            StatusCode::NOT_FOUND,
            Json(json!({ "error": format!("{} not found", entity.into()) })),
        )
            .into_response()
    }

    pub fn unauthorized() -> AxumResponse {
        (
            StatusCode::UNAUTHORIZED,
            Json(json!({ "error": "unauthorized" })),
        )
            .into_response()
    }

    pub fn forbidden() -> AxumResponse {
        (StatusCode::FORBIDDEN, Json(json!({ "error": "forbidden" }))).into_response()
    }

    pub fn redirect(location: impl Into<String>) -> AxumResponse {
        let loc = location.into();
        match HeaderValue::from_str(&loc) {
            Ok(val) => (StatusCode::FOUND, [(header::LOCATION, val)]).into_response(),
            Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
        }
    }
}
