#![allow(dead_code, unused_imports)]

pub use std::convert::Infallible;
pub use std::net::SocketAddr;
pub use std::sync::Arc;
pub use std::sync::atomic::{AtomicU32, Ordering};
pub use std::time::Duration;

pub use bytes::Bytes;
pub use http_body_util::Full;
pub use hyper::server::conn::http1 as server_http1;
pub use hyper::service::service_fn;
pub use hyper::{Request, Response};
pub use tokio::net::TcpListener;

pub use aioduct::Client;
pub use aioduct::runtime::TokioRuntime;

#[cfg(feature = "rustls")]
pub fn rustls_crypto_provider() -> Arc<rustls::crypto::CryptoProvider> {
    Arc::new(rustls_crypto_provider_value())
}

#[cfg(feature = "rustls")]
pub fn install_rustls_crypto_provider() {
    let _ = rustls_crypto_provider_value().install_default();
}

#[cfg(feature = "rustls")]
pub fn rustls_crypto_provider_value() -> rustls::crypto::CryptoProvider {
    #[cfg(feature = "rustls-aws-lc-rs")]
    {
        rustls::crypto::aws_lc_rs::default_provider()
    }

    #[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))]
    {
        rustls::crypto::ring::default_provider()
    }
}

pub async fn hello(
    _req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("hello aioduct"))))
}

pub async fn echo_headers(
    req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
    let host = req
        .headers()
        .get("host")
        .map(|v| v.to_str().unwrap_or(""))
        .unwrap_or("missing");
    let path = req.uri().path().to_string();
    let body = format!("host={host}\npath={path}");
    Ok(Response::new(Full::new(Bytes::from(body))))
}

pub async fn start_server() -> SocketAddr {
    start_server_with(|req| async { hello(req).await }).await
}

pub async fn start_server_with<F, Fut>(handler: F) -> SocketAddr
where
    F: Fn(Request<hyper::body::Incoming>) -> Fut + Send + Clone + 'static,
    Fut: std::future::Future<Output = Result<Response<Full<Bytes>>, Infallible>> + Send,
{
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();

    tokio::spawn(async move {
        loop {
            let (stream, _) = listener.accept().await.unwrap();
            let io = aioduct::runtime::tokio_rt::TokioIo::new(stream);
            let handler = handler.clone();
            tokio::spawn(async move {
                let _ = server_http1::Builder::new()
                    .serve_connection(io, service_fn(handler))
                    .await;
            });
        }
    });

    addr
}

pub async fn start_h2_server_with<F, Fut>(handler: F) -> SocketAddr
where
    F: Fn(Request<hyper::body::Incoming>) -> Fut + Send + Clone + 'static,
    Fut: std::future::Future<Output = Result<Response<Full<Bytes>>, Infallible>> + Send + 'static,
{
    use hyper::server::conn::http2 as server_http2;

    #[derive(Clone)]
    struct TokioExec;
    impl<F> hyper::rt::Executor<F> for TokioExec
    where
        F: std::future::Future + Send + 'static,
        F::Output: Send + 'static,
    {
        fn execute(&self, fut: F) {
            tokio::spawn(fut);
        }
    }

    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();

    tokio::spawn(async move {
        loop {
            let (stream, _) = listener.accept().await.unwrap();
            let io = aioduct::runtime::tokio_rt::TokioIo::new(stream);
            let handler = handler.clone();
            tokio::spawn(async move {
                let _ = server_http2::Builder::new(TokioExec)
                    .serve_connection(io, service_fn(handler))
                    .await;
            });
        }
    });

    addr
}
