Documentation
use anyhow::anyhow;
use http::StatusCode;
use std::fmt::Display;

pub trait ContextWrapper<T, E> {
    fn anyhow(self) -> Result<T, anyhow::Error>;
    fn anyhow_with(self, msg: impl AsRef<str>) -> Result<T, anyhow::Error>;

    fn track(self) -> Self;
    fn track_with(self, msg: impl AsRef<str>) -> Self;
}

pub trait ErrToMsg {
    fn to_msg(self, loc: &std::panic::Location) -> String;
    fn to_msg_with(self, msg: impl AsRef<str>, loc: &std::panic::Location) -> String;
}

impl<E: Display> ErrToMsg for E {
    #[track_caller]
    fn to_msg(self, loc: &std::panic::Location) -> String {
        let e = self.to_string();

        if e.is_empty() {
            format!("\n- [{}:{}]", loc.file(), loc.line())
        } else {
            format!("\n- [{}:{}] {e}", loc.file(), loc.line())
        }
    }

    #[track_caller]
    fn to_msg_with(self, msg: impl AsRef<str>, loc: &std::panic::Location) -> String {
        let e = self.to_string();
        if e.is_empty() {
            format!("\n- [{}:{}] {}", loc.file(), loc.line(), msg.as_ref())
        } else {
            format!("\n- [{}:{}] {} {e}", loc.file(), loc.line(), msg.as_ref())
        }
    }
}

impl<T, E: Display> ContextWrapper<T, E> for Result<T, E> {
    #[track_caller]
    fn anyhow(self) -> Result<T, anyhow::Error> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(anyhow!(e.to_msg(std::panic::Location::caller()))),
        }
    }

    #[track_caller]
    fn anyhow_with(self, msg: impl AsRef<str>) -> Result<T, anyhow::Error> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(anyhow!(e.to_msg_with(msg, std::panic::Location::caller()))),
        }
    }

    #[track_caller]
    fn track(self) -> Self {
        if let Err(e) = &self {
            tracing::error!(target: "dutils", "{}", e.to_msg(std::panic::Location::caller()));
        }
        self
    }
    #[track_caller]
    fn track_with(self, msg: impl AsRef<str>) -> Self {
        if let Err(e) = &self {
            tracing::error!(target: "dutils", "{}", e.to_msg_with(msg, std::panic::Location::caller()));
        }
        self
    }
}

impl<T> ContextWrapper<T, ()> for Option<T> {
    #[track_caller]
    fn anyhow(self) -> Result<T, anyhow::Error> {
        match self {
            Some(v) => Ok(v),
            None => Err(anyhow!(
                "Value is None".to_msg(std::panic::Location::caller())
            )),
        }
    }

    #[track_caller]
    fn anyhow_with(self, msg: impl AsRef<str>) -> Result<T, anyhow::Error> {
        match self {
            Some(v) => Ok(v),
            None => Err(anyhow!(
                "Value is None".to_msg_with(msg, std::panic::Location::caller())
            )),
        }
    }

    #[track_caller]
    fn track(self) -> Self {
        if self.is_none() {
            tracing::error!(target: "dutils", "{}", "Value is None".to_msg(std::panic::Location::caller()));
        }
        self
    }
    #[track_caller]
    fn track_with(self, msg: impl AsRef<str>) -> Self {
        if self.is_none() {
            tracing::error!(target: "dutils", "{}", "Value is None".to_msg_with(msg, std::panic::Location::caller()));
        }
        self
    }
}

fn api_err_response(status: StatusCode, msg: impl AsRef<str>) -> http::Response<String> {
    http::Response::builder()
        .status(status)
        .body(serde_json::to_string(msg.as_ref()).unwrap())
        .unwrap()
}

pub trait ApiError {
    type Ok;
    fn bad_request_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>>;
    fn bad_request(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>>;
    fn not_found(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>>;
    fn internal(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>>;

    fn internal_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>>;
}

pub trait ApiErrorConflict {
    type Ok;
    fn conflict(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>>;
}

impl ApiError for String {
    type Ok = ();

    fn bad_request_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::BAD_REQUEST, self))
    }

    fn bad_request(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::BAD_REQUEST, msg))
    }

    fn not_found(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::NOT_FOUND, msg))
    }

    fn internal(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, msg))
    }

    fn internal_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, self))
    }
}

impl ApiError for &'_ str {
    type Ok = ();

    fn bad_request_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::BAD_REQUEST, self))
    }

    fn bad_request(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::BAD_REQUEST, msg))
    }

    fn not_found(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::NOT_FOUND, msg))
    }

    fn internal(
        self,
        msg: impl AsRef<str>,
    ) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, msg))
    }

    fn internal_from_error(self) -> core::result::Result<Self::Ok, http::Response<String>> {
        Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, self))
    }
}

impl<T, E: Display> ApiError for core::result::Result<T, E> {
    type Ok = T;

    fn bad_request_from_error(self) -> core::result::Result<T, http::Response<String>> {
        match self {
            Ok(a) => Ok(a),
            Err(e) => Err(api_err_response(StatusCode::BAD_REQUEST, e.to_string())),
        }
    }

    fn bad_request(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Ok(a) => Ok(a),
            Err(_) => Err(api_err_response(StatusCode::BAD_REQUEST, msg)),
        }
    }

    fn not_found(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Ok(a) => Ok(a),
            Err(_) => Err(api_err_response(StatusCode::NOT_FOUND, msg)),
        }
    }

    fn internal(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Ok(a) => Ok(a),
            Err(_) => Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, msg)),
        }
    }

    fn internal_from_error(self) -> core::result::Result<T, http::Response<String>> {
        match self {
            Ok(a) => Ok(a),
            Err(e) => Err(api_err_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                e.to_string(),
            )),
        }
    }
}

impl<T> ApiError for Option<T> {
    type Ok = T;

    fn bad_request_from_error(self) -> core::result::Result<T, http::Response<String>> {
        match self {
            Some(a) => Ok(a),
            None => Err(api_err_response(StatusCode::BAD_REQUEST, "No value")),
        }
    }

    fn bad_request(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Some(a) => Ok(a),
            None => Err(api_err_response(StatusCode::BAD_REQUEST, msg)),
        }
    }

    fn not_found(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Some(a) => Ok(a),
            None => Err(api_err_response(StatusCode::NOT_FOUND, msg)),
        }
    }

    fn internal(self, msg: impl AsRef<str>) -> core::result::Result<T, http::Response<String>> {
        match self {
            Some(a) => Ok(a),
            None => Err(api_err_response(StatusCode::INTERNAL_SERVER_ERROR, msg)),
        }
    }

    fn internal_from_error(self) -> core::result::Result<T, http::Response<String>> {
        match self {
            Some(a) => Ok(a),
            None => Err(api_err_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "No value",
            )),
        }
    }
}

impl<T> ApiErrorConflict for Option<T> {
    type Ok = ();

    fn conflict(self, msg: impl AsRef<str>) -> core::result::Result<(), http::Response<String>> {
        match self {
            None => Ok(()),
            Some(_) => Err(api_err_response(StatusCode::CONFLICT, msg)),
        }
    }
}

pub trait ToResult<T> {
    fn to_result(self) -> Result<T, anyhow::Error>;
}

impl<T> ToResult<T> for T {
    fn to_result(self) -> Result<T, anyhow::Error> {
        Ok(self)
    }
}