use std::borrow::Cow;
use std::convert::TryFrom;

use hyper::http::{
    self,
    header::{HeaderMap, HeaderName, HeaderValue},
    StatusCode,
};

pub type HyperResponse = http::Response<hyper::Body>;

pub struct Response {
    pub(crate) inner: HyperResponse,
}

impl Response {
    pub fn new() -> Self {
        HyperResponse::default().into()
    }

    pub fn with_status(status: StatusCode) -> Self {
        Self::new().set_status(status)
    }

    pub fn with_html<T>(body: T) -> Self
        where
            hyper::Body: From<T>,
            T: Send,
    {
        html(body).into()
    }

    pub fn with_json<T>(val: &T) -> Self
        where
            T: serde::Serialize,
    {
        json(val).into()
    }

    pub fn with_bytes(val: &'static [u8]) -> Self {
        val.into()
    }

    pub fn with_bytes_vec(val: Vec<u8>) -> Self {
        val.into()
    }

    pub fn with_str(s: &'static str) -> Self {
        s.into()
    }

    pub fn with_string(s: impl ToString) -> Self {
        s.to_string().into()
    }

    pub fn inner(&self) -> &HyperResponse {
        &self.inner
    }

    pub fn inner_mut(&mut self) -> &mut HyperResponse {
        &mut self.inner
    }

    pub fn status(&self) -> StatusCode {
        self.inner.status()
    }

    pub fn headers(&self) -> &HeaderMap<HeaderValue> {
        self.inner.headers()
    }

    pub fn headers_mut(&mut self) -> &mut HeaderMap<HeaderValue> {
        self.inner.headers_mut()
    }

    pub fn into_hyper_response(self) -> HyperResponse {
        let Self { inner } = self;
        inner
    }

    pub fn set_status(mut self, status: StatusCode) -> Self {
        *self.inner.status_mut() = status;
        self
    }

    pub fn insert_header<K, V>(mut self, name: K, value: V) -> Self
        where
            HeaderName: TryFrom<K>,
            <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
            HeaderValue: TryFrom<V>,
            <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
    {
        match crate::utils::utils::parse_header(name, value) {
            Ok((name, value)) => {
                self.inner.headers_mut().insert(name, value);
            }
            Err(e) => {
                tracing::error!("with_header error: {}", e);
            }
        }

        self
    }

    pub async fn body_bytes(&mut self) -> Result<Vec<u8>, crate::Error> {
        use bytes::Buf;
        use bytes::BytesMut;
        use hyper::body::HttpBody;

        let mut bufs = BytesMut::new();

        while let Some(buf) = self.inner.body_mut().data().await {
            let buf = buf?;
            if buf.has_remaining() {
                bufs.extend(buf);
            }
        }

        Ok(bufs.freeze().to_vec())
    }
}

impl Default for Response {
    fn default() -> Self {
        Self::new()
    }
}

impl From<HyperResponse> for Response {
    fn from(response: HyperResponse) -> Self {
        Response { inner: response }
    }
}

impl Into<HyperResponse> for Response {
    fn into(self) -> HyperResponse {
        let Response { inner } = self;
        inner
    }
}

impl From<StatusCode> for Response {
    fn from(val: StatusCode) -> Self {
        Self::with_status(val)
    }
}

impl From<&'static [u8]> for Response {
    fn from(val: &'static [u8]) -> Self {
        http::Response::builder()
            .header(
                hyper::header::CONTENT_TYPE,
                mime::APPLICATION_OCTET_STREAM.to_string(),
            )
            .body(hyper::Body::from(val))
            .unwrap()
            .into()
    }
}

impl From<Vec<u8>> for Response {
    fn from(val: Vec<u8>) -> Self {
        http::Response::builder()
            .header(
                hyper::header::CONTENT_TYPE,
                mime::APPLICATION_OCTET_STREAM.to_string(),
            )
            .body(hyper::Body::from(val))
            .unwrap()
            .into()
    }
}

impl From<&'static str> for Response {
    fn from(val: &'static str) -> Self {
        http::Response::builder()
            .header(
                hyper::header::CONTENT_TYPE,
                mime::TEXT_PLAIN_UTF_8.to_string(),
            )
            .body(hyper::Body::from(val))
            .unwrap()
            .into()
    }
}

impl From<String> for Response {
    fn from(val: String) -> Self {
        http::Response::builder()
            .header(
                hyper::header::CONTENT_TYPE,
                mime::TEXT_PLAIN_UTF_8.to_string(),
            )
            .body(hyper::Body::from(val))
            .unwrap()
            .into()
    }
}

impl From<Cow<'static, str>> for Response {
    fn from(val: Cow<'static, str>) -> Self {
        match val {
            Cow::Borrowed(s) => s.into(),
            Cow::Owned(s) => s.into(),
        }
    }
}

impl<E, R> From<Result<R, E>> for Response
    where
        R: Into<Response>,
        E: std::error::Error + 'static + Send + Sync,
{
    fn from(val: Result<R, E>) -> Self {
        match val {
            Ok(r) => r.into(),
            Err(e) => {
                tracing::error!("on Result<R, E>, error: {:?}", e);

                http::Response::builder()
                    .status(StatusCode::INTERNAL_SERVER_ERROR)
                    .body(hyper::Body::from("Internal Server Error"))
                    .unwrap()
                    .into()
            }
        }
    }
}

pub struct Html<T> {
    body: T,
}

pub fn html<T>(body: T) -> Html<T>
    where
        hyper::Body: From<T>,
        T: Send,
{
    Html { body }
}

impl<T> From<Html<T>> for Response
    where
        hyper::Body: From<T>,
        T: Send,
{
    fn from(val: Html<T>) -> Response {
        http::Response::builder()
            .header(
                hyper::header::CONTENT_TYPE,
                mime::TEXT_HTML_UTF_8.to_string(),
            )
            .body(hyper::Body::from(val.body))
            .unwrap()
            .into()
    }
}

pub struct Json {
    inner: Result<Vec<u8>, serde_json::Error>,
}

pub fn json<T>(val: &T) -> Json
    where
        T: serde::Serialize,
{
    Json {
        inner: serde_json::to_vec(val),
    }
}

impl From<Json> for Response {
    fn from(val: Json) -> Response {
        let resp: Result<Response, _> = val
            .inner
            .map(|j| {
                http::Response::builder()
                    .header(
                        hyper::header::CONTENT_TYPE,
                        mime::APPLICATION_JSON.to_string(),
                    )
                    .body(hyper::Body::from(j))
                    .unwrap()
                    .into()
            })
            .map_err(|e| {
                tracing::error!("json serialize failed, {:?}", e);
                e
            });

        resp.into()
    }
}
