use crate::{
    State,
    lua::{eval, exec, reload},
};
use http_body_util::{BodyExt, Empty, Full, combinators::BoxBody};
use hyper::{
    Error, Method, Request, Response, StatusCode,
    body::{Bytes, Incoming},
};

pub async fn serve(
    request: Request<Incoming>,
    state: State,
) -> Result<Response<BoxBody<Bytes, Error>>, Error> {
    Ok(match (request.method(), request.uri().path()) {
        (&Method::POST, "/reload") => {
            Response::new(full(format!("{:#?}", reload(&state.lua, None))))
        }
        (&Method::POST, "/eval") => match eval(
            &state.lua,
            &String::from_utf8_lossy(&request.into_body().collect().await?.to_bytes()),
            None,
        )
        .await
        {
            Ok(value) => status_code_response(StatusCode::OK, full(value.to_string())),
            Err(error) => status_code_response(StatusCode::BAD_REQUEST, full(error.to_string())),
        },
        (&Method::POST, "/exec") => match exec(
            &state.lua,
            &String::from_utf8_lossy(&request.into_body().collect().await?.to_bytes()),
            None,
        )
        .await
        {
            Ok(()) => status_code_response(StatusCode::OK, empty()),
            Err(error) => status_code_response(StatusCode::BAD_REQUEST, full(error.to_string())),
        },
        (&Method::GET, "/ping") => Response::new(full("pong!")),
        _ => status_code_response(StatusCode::NOT_FOUND, empty()),
    })
}

fn status_code_response(
    status_code: StatusCode,
    bytes: BoxBody<Bytes, Error>,
) -> Response<BoxBody<Bytes, Error>> {
    let mut response = Response::new(bytes);
    *response.status_mut() = status_code;
    response
}

fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> {
    Full::new(chunk.into())
        .map_err(|never| match never {})
        .boxed()
}

fn empty() -> BoxBody<Bytes, hyper::Error> {
    Empty::<Bytes>::new()
        .map_err(|never| match never {})
        .boxed()
}