use axum::response::IntoResponse;
use reqwest::StatusCode;

/// Central error type for infrastructure-layer failures.
///
/// Each variant maps to an appropriate HTTP status code when converted
/// into an Axum response.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("{0}")]
    StateError(String),

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

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

    #[error("IoError: {0}")]
    IoError(#[from] std::io::Error),
}

impl From<mongodb::error::Error> for Error {
    fn from(err: mongodb::error::Error) -> Self {
        Self::DatabaseError(err.to_string())
    }
}

impl IntoResponse for Error {
    fn into_response(self) -> axum::response::Response {
        let msg = self.to_string();
        tracing::error!("Converting Error to Response: {msg}");
        match self {
            Self::StateError(e) | Self::ConfigError(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, e).into_response()
            }
            Self::DatabaseError(e) => (StatusCode::SERVICE_UNAVAILABLE, e).into_response(),
            Self::IoError(_) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "Unknown error").into_response()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::response::IntoResponse;
    use pretty_assertions::assert_eq;

    #[test]
    fn state_error_display() {
        let err = Error::StateError("bad state".into());
        assert_eq!(err.to_string(), "bad state");
    }

    #[test]
    fn database_error_display() {
        let err = Error::DatabaseError("connection lost".into());
        assert_eq!(err.to_string(), "database error: connection lost");
    }

    #[test]
    fn config_error_display() {
        let err = Error::ConfigError("missing var".into());
        assert_eq!(err.to_string(), "configuration error: missing var");
    }

    #[test]
    fn state_error_into_response_500() {
        let resp = Error::StateError("oops".into()).into_response();
        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[test]
    fn database_error_into_response_503() {
        let resp = Error::DatabaseError("down".into()).into_response();
        assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
    }

    #[test]
    fn config_error_into_response_500() {
        let resp = Error::ConfigError("bad cfg".into()).into_response();
        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[test]
    fn io_error_into_response_500() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
        let resp = Error::IoError(io_err).into_response();
        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }
}
