use std::io::{Read as _, Write as _};
use std::net::TcpListener as StdTcpListener;
use std::thread;
use std::time::Duration;

use bytes::Bytes;
use http_body_util::{BodyExt, Empty};
use hyper::Request;
use ruin_runtime::time::sleep;
use ruin_runtime::{clear_interval, queue_future, set_interval};

fn spawn_demo_server() -> std::io::Result<(std::net::SocketAddr, thread::JoinHandle<()>)> {
    let listener = StdTcpListener::bind(("127.0.0.1", 0))?;
    let address = listener.local_addr()?;
    let handle = thread::Builder::new()
        .name("hyper-demo-server".into())
        .spawn(move || {
            let (mut stream, peer) = listener.accept().expect("demo server should accept");
            let mut request = [0; 1024];
            let read = stream.read(&mut request).expect("demo server should read");
            println!("[server] accepted {peer}, saw {} request bytes", read);

            let response = concat!(
                "HTTP/1.1 200 OK\r\n",
                "content-type: text/plain; charset=utf-8\r\n",
                "content-length: 24\r\n",
                "connection: close\r\n",
                "\r\n",
                "hello from ruin runtime!"
            );
            stream
                .write_all(response.as_bytes())
                .expect("demo server should reply");
        })
        .map_err(std::io::Error::other)?;
    Ok((address, handle))
}

#[ruin_runtime::async_main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (address, server) = spawn_demo_server()?;

    let stream = ruin_runtime::net::TcpStream::connect(address).await?;
    let (mut sender, connection) = hyper::client::conn::http1::handshake(stream).await?;
    queue_future(async move {
        if let Err(error) = connection.await {
            eprintln!("[runtime] hyper connection ended with error: {error}");
        }
    });

    println!("Sleeping a moment to let the server start...");
    let interval = set_interval(Duration::from_millis(400), || println!("..."));
    sleep(Duration::from_secs(2)).await;
    clear_interval(&interval);
    println!("Let's go!");

    let request = Request::builder()
        .method("GET")
        .uri(format!("http://{address}/demo"))
        .header("host", address.to_string())
        .body(Empty::<Bytes>::new())?;
    let response = sender.send_request(request).await?;
    let status = response.status();
    let body = response.into_body().collect().await?.to_bytes();

    println!(
        "[client] status={status}, body={}",
        String::from_utf8_lossy(&body)
    );

    server
        .join()
        .expect("demo server thread should exit cleanly");
    Ok(())
}
