use async_trait::async_trait;
use hyper::client::HttpConnector;
use hyper_tls::HttpsConnector;

mod error;
pub use error::Error;
pub use http;

pub mod content;
pub use content::{RequestContent, ResponseContent};

#[async_trait]
pub trait HttpClient
where
    Self: Send + Sync + Sized,
{
    async fn perform(
        &self,
        r: hyper::Request<hyper::Body>,
    ) -> Result<hyper::Response<hyper::Body>, hyper::Error>;

    async fn send<Req, Res>(
        &self,
        r: http::Request<Req>,
    ) -> Result<http::Response<Res::Data>, Error>
    where
        Req: RequestContent + Send,
        Res: ResponseContent,
    {
        let (mut parts, body) = r.into_parts();

        body.apply_headers(&mut parts.headers);

        let hyper_body: hyper::Body = body.into_body()?;
        let request = hyper::Request::from_parts(parts, hyper_body);

        let http_res = self.perform(request).await?;
        let converted = Res::convert_response(http_res).await;
        converted
    }

    fn https() -> HttpsClient {
        HttpsClient::new()
    }
}

pub struct HttpsClient {
    http_client: hyper::Client<HttpsConnector<HttpConnector>>,
}

impl HttpsClient {
    pub fn new() -> HttpsClient {
        let http_client = hyper::Client::builder().build::<_, hyper::Body>(HttpsConnector::new());
        HttpsClient { http_client }
    }
}

#[async_trait]
impl HttpClient for HttpsClient {
    async fn perform(
        &self,
        r: hyper::Request<hyper::Body>,
    ) -> Result<hyper::Response<hyper::Body>, hyper::Error> {
        self.http_client.request(r).await
    }
}
