use crate::support;
use tokio_test::{assert_pending, assert_ready, assert_ready_ok};
use tower::limit::concurrency::ConcurrencyLimitLayer;
use tower_test::{assert_request_eq, mock};

#[tokio::test(flavor = "current_thread")]
async fn basic_service_limit_functionality_with_poll_ready() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(2);
    let (mut service, mut handle) = mock::spawn_layer(limit);

    assert_ready_ok!(service.poll_ready());
    let r1 = service.call("hello 1");

    assert_ready_ok!(service.poll_ready());
    let r2 = service.call("hello 2");

    assert_pending!(service.poll_ready());

    assert!(!service.is_woken());

    // The request gets passed through
    assert_request_eq!(handle, "hello 1").send_response("world 1");

    // The next request gets passed through
    assert_request_eq!(handle, "hello 2").send_response("world 2");

    // There are no more requests
    assert_pending!(handle.poll_request());

    assert_eq!(r1.await.unwrap(), "world 1");

    assert!(service.is_woken());

    // Another request can be sent
    assert_ready_ok!(service.poll_ready());

    let r3 = service.call("hello 3");

    assert_pending!(service.poll_ready());

    assert_eq!(r2.await.unwrap(), "world 2");

    // The request gets passed through
    assert_request_eq!(handle, "hello 3").send_response("world 3");

    assert_eq!(r3.await.unwrap(), "world 3");
}

#[tokio::test(flavor = "current_thread")]
async fn basic_service_limit_functionality_without_poll_ready() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(2);
    let (mut service, mut handle) = mock::spawn_layer(limit);

    assert_ready_ok!(service.poll_ready());
    let r1 = service.call("hello 1");

    assert_ready_ok!(service.poll_ready());
    let r2 = service.call("hello 2");

    assert_pending!(service.poll_ready());

    // The request gets passed through
    assert_request_eq!(handle, "hello 1").send_response("world 1");

    assert!(!service.is_woken());

    // The next request gets passed through
    assert_request_eq!(handle, "hello 2").send_response("world 2");

    assert!(!service.is_woken());

    // There are no more requests
    assert_pending!(handle.poll_request());

    assert_eq!(r1.await.unwrap(), "world 1");

    assert!(service.is_woken());

    // One more request can be sent
    assert_ready_ok!(service.poll_ready());
    let r4 = service.call("hello 4");

    assert_pending!(service.poll_ready());

    assert_eq!(r2.await.unwrap(), "world 2");
    assert!(service.is_woken());

    // The request gets passed through
    assert_request_eq!(handle, "hello 4").send_response("world 4");

    assert_eq!(r4.await.unwrap(), "world 4");
}

#[tokio::test(flavor = "current_thread")]
async fn request_without_capacity() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(0);
    let (mut service, _) = mock::spawn_layer::<(), (), _>(limit);

    assert_pending!(service.poll_ready());
}

#[tokio::test(flavor = "current_thread")]
async fn reserve_capacity_without_sending_request() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(1);
    let (mut s1, mut handle) = mock::spawn_layer(limit);

    let mut s2 = s1.clone();

    // Reserve capacity in s1
    assert_ready_ok!(s1.poll_ready());

    // Service 2 cannot get capacity
    assert_pending!(s2.poll_ready());

    // s1 sends the request, then s2 is able to get capacity
    let r1 = s1.call("hello");

    assert_request_eq!(handle, "hello").send_response("world");

    assert_pending!(s2.poll_ready());

    r1.await.unwrap();

    assert_ready_ok!(s2.poll_ready());
}

#[tokio::test(flavor = "current_thread")]
async fn service_drop_frees_capacity() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(1);
    let (mut s1, _handle) = mock::spawn_layer::<(), (), _>(limit);

    let mut s2 = s1.clone();

    // Reserve capacity in s1
    assert_ready_ok!(s1.poll_ready());

    // Service 2 cannot get capacity
    assert_pending!(s2.poll_ready());

    drop(s1);

    assert!(s2.is_woken());
    assert_ready_ok!(s2.poll_ready());
}

#[tokio::test(flavor = "current_thread")]
async fn response_error_releases_capacity() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(1);
    let (mut s1, mut handle) = mock::spawn_layer::<_, (), _>(limit);

    let mut s2 = s1.clone();

    // Reserve capacity in s1
    assert_ready_ok!(s1.poll_ready());

    // s1 sends the request, then s2 is able to get capacity
    let r1 = s1.call("hello");

    assert_request_eq!(handle, "hello").send_error("boom");

    r1.await.unwrap_err();

    assert_ready_ok!(s2.poll_ready());
}

#[tokio::test(flavor = "current_thread")]
async fn response_future_drop_releases_capacity() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(1);
    let (mut s1, _handle) = mock::spawn_layer::<_, (), _>(limit);

    let mut s2 = s1.clone();

    // Reserve capacity in s1
    assert_ready_ok!(s1.poll_ready());

    // s1 sends the request, then s2 is able to get capacity
    let r1 = s1.call("hello");

    assert_pending!(s2.poll_ready());

    drop(r1);

    assert_ready_ok!(s2.poll_ready());
}

#[tokio::test(flavor = "current_thread")]
async fn multi_waiters() {
    let _t = support::trace_init();
    let limit = ConcurrencyLimitLayer::new(1);
    let (mut s1, _handle) = mock::spawn_layer::<(), (), _>(limit);
    let mut s2 = s1.clone();
    let mut s3 = s1.clone();

    // Reserve capacity in s1
    assert_ready_ok!(s1.poll_ready());

    // s2 and s3 are not ready
    assert_pending!(s2.poll_ready());
    assert_pending!(s3.poll_ready());

    drop(s1);

    assert!(s2.is_woken());
    assert!(!s3.is_woken());

    drop(s2);

    assert!(s3.is_woken());
}
