use bytes::Bytes;
use serde::{Deserialize, Serialize};
use tower::{BoxError, ServiceBuilder, ServiceExt as _};
use tower_http::ServiceBuilderExt as _;
use tower_http_client::{ResponseExt as _, ServiceExt as _};
use tower_reqwest::HttpClientLayer;
use wiremock::{
    Mock, MockServer, ResponseTemplate,
    matchers::{method, path},
};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct SomeInfo {
    name: String,
    age: u32,
}

#[tokio::main]
async fn main() -> Result<(), BoxError> {
    let (_mock_server, mock_server_uri) = create_mock_server().await;

    eprintln!("-> Creating an HTTP client with Tower layers...");
    let mut client = ServiceBuilder::new()
        // Set the request body type.
        .map_request_body(|body: http_body_util::Full<Bytes>| reqwest::Body::wrap(body))
        .layer(HttpClientLayer)
        .service(reqwest::Client::new())
        .map_err(BoxError::from)
        .boxed_clone();

    let response = client
        .post(format!("{mock_server_uri}/test"))
        .form(&SomeInfo {
            name: "John".to_string(),
            age: 30,
        })?
        .send()
        .await?;

    // Check that the request was successful.
    assert_eq!(response.status(), 200);
    assert_eq!(
        response.body_reader().json::<String>().await?,
        "I am John and 30 years old"
    );

    Ok(())
}

async fn create_mock_server() -> (MockServer, String) {
    let mock_server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/test"))
        .respond_with(move |request: &wiremock::Request| {
            let info: SomeInfo = serde_urlencoded::from_bytes(request.body.as_ref()).unwrap();

            eprintln!("Received request with info {info:?}",);
            ResponseTemplate::new(200)
                .set_body_json(format!("I am {} and {} years old", info.name, info.age))
        })
        .mount(&mock_server)
        .await;
    let mock_server_uri = mock_server.uri();
    (mock_server, mock_server_uri)
}
