use http::StatusCode;
//
use kxio::net::{Error, MockNet, Net};

use assert2::let_assert;

#[tokio::test]
async fn test_get_url() {
    //given
    let mock_net = kxio::net::mock();

    let url = "https://www.example.com";
    let url_alpha = format!("{url}/alpha");
    let url_beta = format!("{url}/beta");

    mock_net
        .on()
        .get(&url_alpha)
        .respond(StatusCode::OK)
        .body("Get OK alpha")
        .expect("mock alpha");
    mock_net
        .on()
        .get(&url_beta)
        .respond(StatusCode::OK)
        .body("Get OK beta")
        .expect("mock beta");
    mock_net
        .on()
        .get(url)
        .respond(StatusCode::OK)
        .body("Get OK")
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let response_alpha = net.get(url_alpha).send().await.expect("response alpha");
    let response_beta = net.get(url_beta).send().await.expect("response beta");
    let response = net.get(url).send().await.expect("response");

    //then
    assert_eq!(response_alpha.status(), http::StatusCode::OK);
    assert_eq!(
        response_alpha.bytes().await.expect("response body alpha"),
        "Get OK alpha"
    );
    assert_eq!(response_beta.status(), http::StatusCode::OK);
    assert_eq!(
        response_beta.bytes().await.expect("response body beta"),
        "Get OK beta"
    );
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "Get OK");
}

#[tokio::test]
async fn test_post_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .post(url)
        .respond(StatusCode::OK)
        .body("post OK")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(client.post(url))
        .await
        .expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "post OK");
}

#[tokio::test]
async fn test_put_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .put(url)
        .respond(StatusCode::OK)
        .body("put OK")
        .expect("mock");

    //when
    let response = Net::from(net).send(client.put(url)).await.expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "put OK");
}

#[tokio::test]
async fn test_delete_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .delete(url)
        .respond(StatusCode::OK)
        .body("delete OK")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(client.delete(url))
        .await
        .expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "delete OK");
}

#[tokio::test]
async fn test_head_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .head(url)
        .respond(StatusCode::OK)
        .body("head OK")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(client.head(url))
        .await
        .expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "head OK");
}

#[tokio::test]
async fn test_patch_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .patch(url)
        .respond(StatusCode::OK)
        .body("patch OK")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(client.patch(url))
        .await
        .expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "patch OK");
}

// #[tokio::test]
// async fn test_get_auth_url() {
//     //given
//     let net = kxio::net::mock();
//
//     let url = "https://user:pass@www.example.com";
//
//     net.on()
//         .get(url)
//         .respond(StatusCode::OK)
//         .body("post OK")
//         .expect("mock");
//
//     //when
//     let response = Net::from(net).get(url).send().await.expect("reponse");
//
//     //then
//     assert_eq!(response.status(), http::StatusCode::OK);
//     assert_eq!(response.bytes().await.expect("response body"), "post OK");
// }

#[tokio::test]
async fn test_get_wrong_url() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com";

    net.on()
        .get(url)
        .respond(StatusCode::OK)
        .body("Get OK")
        .expect("mock");

    let net = Net::from(net);

    //when
    let_assert!(
        Err(Error::UnexpectedMockRequest { request }) =
            net.send(client.get("https://some.other.url/")).await
    );

    //then
    assert_eq!(request.url().to_string(), "https://some.other.url/");

    // remove pending unmatched request - we never meant to match against it
    let mock_net = MockNet::try_from(net).await.expect("recover net");
    mock_net.reset();
}

#[tokio::test]
async fn test_post_by_method() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    // NOTE: No URL specified - so should match any URL
    net.on().respond(StatusCode::OK).body("").expect("mock");

    //when
    let response = Net::from(net)
        .send(client.post("https://some.other.url"))
        .await
        .expect("response");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "");
}

#[tokio::test]
async fn test_post_by_body() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    // No URL - so any POST with a matching body
    net.on()
        .body("match on body")
        .respond(StatusCode::OK)
        .body("response body")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(client.post("https://some.other.url").body("match on body"))
        .await
        .expect("response");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(
        response.bytes().await.expect("response body"),
        "response body"
    );
}

#[tokio::test]
async fn test_post_by_header() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    net.on()
        .header("test", "match")
        .respond(StatusCode::OK)
        .body("response body")
        .expect("mock");

    //when
    let response = Net::from(net)
        .send(
            client
                .post("https://some.other.url")
                .body("any body")
                .header("test", "match"),
        )
        .await
        .expect("response");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(
        response.bytes().await.expect("response body"),
        "response body"
    );
}

#[tokio::test]
async fn test_post_by_header_wrong_value() {
    //given
    let mock_net = kxio::net::mock();
    let client = mock_net.client();

    mock_net
        .on()
        .header("test", "match")
        .respond(StatusCode::OK)
        .body("response body")
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let response = net
        .send(
            client
                .post("https://some.other.url")
                .body("any body")
                .header("test", "no match"),
        )
        .await;

    //then
    let_assert!(Err(kxio::net::Error::UnexpectedMockRequest { request: _ }) = response);

    MockNet::try_from(net).await.expect("recover mock").reset();
}

#[tokio::test]
#[should_panic(expected = "1 expected requests were not made")]
async fn test_unused_post_as_net() {
    //given
    let mock_net = kxio::net::mock();

    let url = "https://www.example.com";

    mock_net
        .on()
        .post(url)
        .respond(StatusCode::OK)
        .body("Post OK")
        .expect("mock");

    let net = Net::from(mock_net);

    //when
    // don't send the planned request
    // let _response = net.send(client.post(url)).await.expect("send");

    //then
    net.assert_no_unused_plans();
}

#[tokio::test]
#[should_panic(expected = "1 expected requests were not made")]
async fn test_unused_post_as_mocknet() {
    //given
    let mock_net = kxio::net::mock();

    let url = "https://www.example.com";

    mock_net
        .on()
        .post(url)
        .respond(StatusCode::OK)
        .body("Post OK")
        .expect("mock");

    //when
    // don't send the planned request
    // let _response = mock_net.send(client.post(url)).await.expect("send");

    //then
    mock_net.assert_no_unused_plans();
}

#[tokio::test]
async fn test_get_url_with_fragment() {
    //given
    let net = kxio::net::mock();
    let client = net.client();

    let url = "https://www.example.com#test";

    net.on()
        .get(url)
        .respond(StatusCode::OK)
        .body("post OK")
        .expect("mock");

    //when
    let response = Net::from(net).send(client.get(url)).await.expect("reponse");

    //then
    assert_eq!(response.status(), http::StatusCode::OK);
    assert_eq!(response.bytes().await.expect("response body"), "post OK");
}

#[tokio::test]
async fn test_get_with_query_parameters() {
    //given
    let mock_net = kxio::net::mock();
    let url = "https://www.example.com/path";

    mock_net
        .on()
        .get(url)
        .query("key-1", "value-1")
        .respond(StatusCode::OK)
        .body("with query parameters 1/1")
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .query("key-1", "value-2")
        .respond(StatusCode::OK)
        .body("with query parameters 1/2")
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .query("key-2", "value-2")
        .respond(StatusCode::OK)
        .body("with query parameters 2/2")
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .respond(StatusCode::OK)
        .body("sans query parameters")
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    // The order of 12 nad 11 should be in that order to ensure we test the discrimination of the
    // query value when the keys are the same
    let response_with_12 = net
        .get(url)
        .query("key-1", "value-2")
        .send()
        .await
        .expect("response with qp 1/2");
    let response_with_11 = net
        .get(url)
        .query("key-1", "value-1")
        .send()
        .await
        .expect("response with qp 1/1");
    let response_with_22 = net
        .get(url)
        .query("key-2", "value-2")
        .send()
        .await
        .expect("response with qp 2/2");
    let response_sans_qp = net.get(url).send().await.expect("response sans qp");

    //then
    assert_eq!(
        response_with_11.bytes().await.expect("with qp 1/1 body"),
        "with query parameters 1/1"
    );
    assert_eq!(
        response_with_12.bytes().await.expect("with qp 1/2 body"),
        "with query parameters 1/2"
    );
    assert_eq!(
        response_with_22.bytes().await.expect("with qp 2/2 body"),
        "with query parameters 2/2"
    );
    assert_eq!(
        response_sans_qp.bytes().await.expect("sans qp body"),
        "sans query parameters"
    );
}

#[tokio::test]
async fn test_get_with_duplicate_query_keys() {
    //given
    let mock_net = kxio::net::mock();
    let url = "https://www.example.com/path";

    mock_net
        .on()
        .get(url)
        .query("key", "value-1")
        .query("key", "value-2")
        .respond(StatusCode::OK)
        .body("key:value-1,value-2")
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .query("key", "value-3")
        .query("key", "value-4")
        .respond(StatusCode::OK)
        .body("key:value-3,value-4")
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let response_a = net
        .get(url)
        .query("key", "value-2")
        .query("key", "value-1")
        .send()
        .await
        .expect("response a");
    let response_b = net
        .get(url)
        .query("key", "value-3")
        .query("key", "value-4")
        .send()
        .await
        .expect("response b");

    //then
    assert_eq!(
        response_a.bytes().await.expect("response a bytes"),
        "key:value-1,value-2"
    );
    assert_eq!(
        response_b.bytes().await.expect("response b bytes"),
        "key:value-3,value-4"
    );
}

#[tokio::test]
async fn test_get_with_basic_auth() {
    //given
    let mock_net = kxio::net::mock();
    let url = "https://www.example.com/path";

    mock_net
        .on()
        .get(url)
        .basic_auth("bob", Some("secret"))
        .respond(StatusCode::OK)
        .mock()
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .basic_auth("bob", None::<String>)
        .respond(StatusCode::FORBIDDEN)
        .mock()
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let invalid = net.get(url).basic_auth("bob", None::<String>).send().await;
    let valid = net
        .get(url)
        .basic_auth("bob", Some("secret"))
        .send()
        .await
        .expect("valid response");

    //then
    let_assert!(Err(Error::ResponseError { response }) = invalid);
    assert_eq!(response.status(), StatusCode::FORBIDDEN);
    assert_eq!(valid.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_get_with_bearer_auth() {
    //given
    let mock_net = kxio::net::mock();
    let url = "https://www.example.com/path";

    mock_net
        .on()
        .get(url)
        .bearer_auth("token")
        .respond(StatusCode::OK)
        .mock()
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .bearer_auth("invalid")
        .respond(StatusCode::FORBIDDEN)
        .mock()
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let invalid = net.get(url).bearer_auth("invalid").send().await;
    let valid = net
        .get(url)
        .bearer_auth("token")
        .send()
        .await
        .expect("valid response");

    //then
    let_assert!(Err(Error::ResponseError { response }) = invalid);
    assert_eq!(response.status(), StatusCode::FORBIDDEN);
    assert_eq!(valid.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_get_with_user_agent() {
    //given
    let mock_net = kxio::net::mock();
    let url = "https://www.example.com/path";

    mock_net
        .on()
        .get(url)
        .user_agent("007")
        .respond(StatusCode::OK)
        .mock()
        .expect("mock");
    mock_net
        .on()
        .get(url)
        .user_agent("orange")
        .respond(StatusCode::FORBIDDEN)
        .mock()
        .expect("mock");
    let net = Net::from(mock_net);

    //when
    let invalid = net.get(url).user_agent("orange").send().await;
    let valid = net
        .get(url)
        .user_agent("007")
        .send()
        .await
        .expect("valid response");

    //then
    let_assert!(Err(Error::ResponseError { response }) = invalid);
    assert_eq!(response.status(), StatusCode::FORBIDDEN);
    assert_eq!(valid.status(), StatusCode::OK);
}