mod server;

#[cfg(feature = "json")]
use std::collections::HashMap;

use cyper::Client;
#[cfg(feature = "json")]
use http::header::CONTENT_TYPE;

#[compio::test]
async fn response_text() {
    let server = server::http(move |_req| async { "Hello" }).await;

    let client = Client::new().unwrap();

    let res = client
        .get(format!("http://{}/text", server.addr()))
        .expect("cannot create request builder")
        .send()
        .await
        .expect("Failed to get");
    assert_eq!(res.content_length(), Some(5));
    let text = res.text().await.expect("Failed to get text");
    assert_eq!("Hello", text);
}

#[compio::test]
async fn response_bytes() {
    let server = server::http(move |_req| async { "Hello" }).await;

    let client = Client::new().unwrap();

    let res = client
        .get(format!("http://{}/bytes", server.addr()))
        .expect("cannot create request builder")
        .send()
        .await
        .expect("Failed to get");
    assert_eq!(res.content_length(), Some(5));
    let bytes = res.bytes().await.expect("res.bytes()");
    assert_eq!("Hello", bytes);
}

#[compio::test]
#[cfg(feature = "stream")]
async fn response_bytes_stream() {
    use std::{
        pin::Pin,
        task::{Context, Poll},
    };

    use axum::body::Body;
    use compio::bytes;
    use futures_util::{Stream, StreamExt};

    #[derive(Default)]
    struct ChunkedBody {
        chunks_sent: usize,
    }

    impl Stream for ChunkedBody {
        type Item = Result<bytes::Bytes, std::io::Error>;

        fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
            if self.chunks_sent == 10 {
                return Poll::Ready(None);
            }

            let this = self.get_mut();

            this.chunks_sent += 1;
            Poll::Ready(Some(Ok(format!("Chunk {}", this.chunks_sent).into())))
        }
    }

    let server =
        server::http(move |_req| async { Body::from_stream(ChunkedBody::default()) }).await;

    let client = Client::new().unwrap();

    let res = client
        .get(format!("http://{}/bytes", server.addr()))
        .expect("cannot create request builder")
        .send()
        .await
        .expect("Failed to get");
    assert_eq!(res.content_length(), None);

    let mut bytes_stream = res.bytes_stream().enumerate();
    let mut chunks_received = 0;

    while let Some((i, bytes)) = bytes_stream.next().await {
        chunks_received += 1;
        assert_eq!(
            bytes.unwrap(),
            compio::bytes::Bytes::from(format!("Chunk {}", i + 1))
        );
    }

    assert_eq!(chunks_received, 10);
}

#[compio::test]
#[cfg(feature = "json")]
async fn response_json() {
    let server = server::http(move |_req| async { "\"Hello\"" }).await;

    let client = Client::new().unwrap();

    let res = client
        .get(format!("http://{}/json", server.addr()))
        .expect("cannot create request builder")
        .send()
        .await
        .expect("Failed to get");
    let text = res.json::<String>().await.expect("Failed to get json");
    assert_eq!("Hello", text);
}

#[compio::test]
#[cfg(feature = "stream")]
async fn service() {
    use http::Method;
    use http_body_util::BodyExt;
    use tower_service::Service;

    let server = server::http(move |_req| async { "Hello" }).await;

    let mut client = Client::new().unwrap();

    let request = http::Request::builder()
        .method(Method::GET)
        .uri(format!("http://{}/text", server.addr()))
        .body(cyper::Body::empty())
        .unwrap();

    let res = client.call(request).await.expect("Failed to get");
    let content_length = res
        .headers()
        .get(http::header::CONTENT_LENGTH)
        .unwrap()
        .to_str()
        .unwrap()
        .parse::<usize>()
        .unwrap();
    assert_eq!(content_length, 5);
    let text = res.into_body().collect().await.unwrap().to_bytes();
    assert_eq!("Hello", text);
}

#[compio::test]
#[cfg(tls)]
async fn test_allowed_methods() {
    let resp = Client::new()
        .unwrap()
        .get("https://compio.rs")
        .unwrap()
        .send()
        .await;

    assert!(resp.is_ok());
}

#[compio::test]
#[cfg(feature = "native-tls")]
async fn test_native_tls() {
    let resp = Client::builder()
        .use_native_tls()
        .build()
        .unwrap()
        .get("https://compio.rs")
        .unwrap()
        .send()
        .await
        .unwrap();
    resp.text().await.unwrap();
}

#[compio::test]
#[cfg(feature = "rustls")]
async fn test_rustls() {
    let resp = Client::builder()
        .use_rustls_default()
        .build()
        .unwrap()
        .get("https://compio.rs")
        .unwrap()
        .send()
        .await
        .unwrap();
    resp.text().await.unwrap();
}

#[compio::test]
#[cfg(feature = "http3")]
async fn test_http3() {
    let resp = Client::builder()
        .build()
        .unwrap()
        .get("https://compio.rs")
        .unwrap()
        .version(http::Version::HTTP_3)
        .send()
        .await
        .unwrap();
    resp.text().await.unwrap();
}

#[compio::test]
#[cfg(feature = "json")]
async fn add_json_default_content_type_if_not_set_manually() {
    let mut map = HashMap::new();
    map.insert("body", "json");
    let content_type = http::HeaderValue::from_static("application/vnd.api+json");
    let req = Client::new()
        .unwrap()
        .post("https://compio.rs/")
        .expect("cannot create request builder")
        .header(CONTENT_TYPE, &content_type)
        .unwrap()
        .json(&map)
        .unwrap()
        .build();

    assert_eq!(content_type, req.headers().get(CONTENT_TYPE).unwrap());
}

#[compio::test]
#[cfg(feature = "json")]
async fn update_json_content_type_if_set_manually() {
    let mut map = HashMap::new();
    map.insert("body", "json");
    let req = Client::new()
        .unwrap()
        .post("https://compio.rs/")
        .expect("cannot create request builder")
        .json(&map)
        .unwrap()
        .build();

    assert_eq!("application/json", req.headers().get(CONTENT_TYPE).unwrap());
}

#[compio::test]
pub async fn accept_encoding_header_is_not_changed_if_set() {
    let server = server::http(move |req: http::Request<axum::body::Body>| async move {
        assert_eq!(req.headers()["accept"], "*/*");
        assert_eq!(req.headers()["accept-encoding"], "identity");
        http::Response::<axum::body::Body>::default()
    })
    .await;

    let client = Client::new().unwrap();

    let res = client
        .get(format!("http://{}/accept-encoding", server.addr()))
        .unwrap()
        .header("accept-encoding", "identity")
        .unwrap()
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), http::StatusCode::OK);
}
