use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use serde::de::DeserializeOwned;
use snafu::prelude::*;

use crate::http::{HttpClient, HttpResponse};

#[derive(Debug, Snafu)]
pub enum GetError<HttpReqErr: crate::Error + 'static, HttpRespErr: crate::Error + 'static> {
    Request {
        source: HttpReqErr,
    },
    Response {
        source: HttpRespErr,
    },
    Deserialize {
        source: serde_json::Error,
    },
    #[snafu(display("Bad status: {}", status))]
    BadStatus {
        status: StatusCode,
        body: Vec<u8>,
    },
}

impl<HttpReqErr: crate::Error, HttpRespErr: crate::Error> crate::Error
    for GetError<HttpReqErr, HttpRespErr>
{
    fn is_retryable(&self) -> bool {
        match self {
            GetError::Request { source } => source.is_retryable(),
            GetError::Response { source } => source.is_retryable(),
            GetError::Deserialize { .. } | GetError::BadStatus { .. } => false,
        }
    }
}

pub(crate) async fn get<C: HttpClient, T: DeserializeOwned>(
    http_client: &C,
    uri: http::Uri,
    headers: HeaderMap,
) -> Result<T, GetError<C::Error, C::ResponseError>> {
    let (mut parts, ()) = http::Request::new(()).into_parts();
    parts.headers = headers;
    parts.uri = uri;
    let request = http::Request::from_parts(parts, Bytes::new());

    let response = http_client.execute(request).await.context(RequestSnafu)?;
    let status = response.status();
    let body = response.body().await.context(ResponseSnafu)?;

    if status.is_success() {
        Ok(serde_json::from_slice(&body).context(DeserializeSnafu)?)
    } else {
        BadStatusSnafu { status, body }.fail()
    }
}
