use crate::Error;
use async_trait::async_trait;
use futures::StreamExt;

#[async_trait]
pub trait ResponseContent
where
    Self::Data: Send + 'static + Unpin,
{
    type Data;

    async fn convert_response(
        response: hyper::Response<hyper::Body>,
    ) -> Result<http::Response<Self::Data>, Error>;
}

pub trait RequestContent
where
    Self::Data: Send + 'static + Unpin,
{
    type Data;

    fn apply_headers(&self, headers: &mut http::HeaderMap);

    fn into_body(self) -> Result<hyper::Body, Error>;
}

pub struct Empty;

#[async_trait]
impl ResponseContent for Empty {
    type Data = ();

    async fn convert_response(
        response: hyper::Response<hyper::Body>,
    ) -> Result<http::Response<Self::Data>, Error> {
        let (parts, _) = response.into_parts();

        Ok(http::Response::from_parts(parts, ()))
    }
}

impl RequestContent for Empty {
    type Data = ();

    fn into_body(self) -> Result<hyper::Body, Error> {
        Ok(hyper::Body::empty())
    }

    fn apply_headers(&self, headers: &mut http::HeaderMap) {
        if !headers.append("Content-Length", http::HeaderValue::from_static("0")) {
            log::warn!("Failed to add Content-Length header for Empty body");
        }
    }
}

pub struct Json<T>(pub T);

#[async_trait]
impl<T> ResponseContent for Json<T>
where
    T: serde::de::DeserializeOwned + Send + 'static + Unpin,
{
    type Data = T;

    async fn convert_response(
        response: hyper::Response<hyper::Body>,
    ) -> Result<http::Response<Self::Data>, Error> {
        let (parts, mut body_stream) = response.into_parts();
        let mut body = bytes::BytesMut::default();

        while let Some(res) = body_stream.next().await {
            let bs = res?;
            body.extend(bs);
        }
        let received_status = parts.status;

        if received_status.as_u16() / 100 != 2 {
            return Err(Error::non_2xx(received_status, &body));
        }

        serde_json::from_slice(&body)
            .map_err(|err| Error::deserialization(err, &body))
            .map(move |body: T| http::Response::from_parts(parts, body))
    }
}

impl<T> RequestContent for Json<T>
where
    T: serde::Serialize + 'static + Send + Unpin,
{
    type Data = T;

    fn apply_headers(&self, headers: &mut http::HeaderMap) {
        if !headers.append(
            "Content-Type",
            http::HeaderValue::from_static("application/json"),
        ) {
            log::warn!("Failed to add Content-Type header for Json body");
        }
    }

    fn into_body(self) -> Result<hyper::Body, Error> {
        serde_json::to_vec(&self.0)
            .map_err(Error::Serialization)
            .map(|bs| {
                log::debug!(
                    "Sending body: `{}`",
                    std::str::from_utf8(&bs).unwrap_or("INVALID UTF8")
                );
                hyper::Body::from(bs)
            })
    }
}

pub struct Bytes(Vec<u8>);

#[async_trait]
impl ResponseContent for Bytes {
    type Data = Vec<u8>;

    async fn convert_response(
        response: hyper::Response<hyper::Body>,
    ) -> Result<http::Response<Self::Data>, Error> {
        let (parts, mut body_stream) = response.into_parts();
        let mut body = bytes::BytesMut::default();

        while let Some(res) = body_stream.next().await {
            let bs = res?;
            body.extend(bs);
        }
        let received_status = parts.status;

        if received_status.as_u16() / 100 != 2 {
            return Err(Error::non_2xx(received_status, &body));
        }

        Ok(http::Response::from_parts(parts, body.to_vec()))
    }
}

impl RequestContent for Bytes {
    type Data = Vec<u8>;

    fn apply_headers(&self, headers: &mut http::HeaderMap) {
        if !headers.append(
            "Content-Type",
            http::HeaderValue::from_static("application/json"),
        ) {
            log::warn!("Failed to add Content-Type header for Json body");
        }
    }

    fn into_body(self) -> Result<hyper::Body, Error> {
        Ok(hyper::Body::from(self.0))
    }
}
