use std::{
    io,
    net::{Shutdown, TcpStream},
    thread,
};

use touche::{upgrade::Upgrade, Body, Connection, Method, Request, Response, Server, StatusCode};

// Try with: curl --proxy http://localhost:4444 https://en.wikipedia.org/wiki/HTTP_tunnel
fn main() -> io::Result<()> {
    Server::bind("0.0.0.0:4444").serve(|req: Request<_>| {
        if req.method() != Method::CONNECT {
            return Response::builder()
                .status(StatusCode::METHOD_NOT_ALLOWED)
                .header("allow", "connect")
                .body(Body::empty());
        }

        if let Some(address) = req.uri().authority().map(|a| a.to_string()) {
            Response::builder()
                .status(StatusCode::OK)
                .upgrade(move |conn: Connection| {
                    if let Ok(server) = TcpStream::connect(&address) {
                        match tunnel(conn.downcast().unwrap(), server) {
                            Ok((w, r)) => eprintln!("Tunneled bytes: {r} (read) {w} (write)"),
                            Err(err) => eprintln!("Tunnel error: {err}"),
                        };
                    } else {
                        eprintln!("Could not connect to address: {address}");
                    }
                })
                .body(Body::empty())
        } else {
            Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body(Body::from("Invalid address"))
        }
    })
}

fn tunnel(mut client: TcpStream, mut server: TcpStream) -> io::Result<(u64, u64)> {
    let mut client_writer = client.try_clone()?;
    let mut server_writer = server.try_clone()?;

    let client_to_server = thread::spawn(move || {
        let bytes = io::copy(&mut client, &mut server_writer)?;
        server_writer.shutdown(Shutdown::Both)?;
        io::Result::Ok(bytes)
    });

    let server_to_client = thread::spawn(move || {
        let bytes = io::copy(&mut server, &mut client_writer)?;
        client_writer.shutdown(Shutdown::Both)?;
        io::Result::Ok(bytes)
    });

    Ok((
        client_to_server.join().unwrap()?,
        server_to_client.join().unwrap()?,
    ))
}
