use std::time::Duration;

use actix_limitation::{Error, Limiter, RateLimiter};
use actix_web::{dev::ServiceRequest, http::StatusCode, test, web, App, HttpRequest, HttpResponse};
use uuid::Uuid;

#[test]
#[should_panic = "Redis URL did not parse"]
async fn test_create_limiter_error() {
    Limiter::builder("127.0.0.1").build().unwrap();
}

#[actix_web::test]
async fn test_limiter_count() -> Result<(), Error> {
    let limiter = Limiter::builder("redis://127.0.0.1:6379/2")
        .limit(20)
        .build()
        .unwrap();

    let id = Uuid::new_v4();

    for i in 0..20 {
        let status = limiter.count(id.to_string()).await?;
        println!("status: {status:?}");
        assert_eq!(20 - status.remaining(), i + 1);
    }

    Ok(())
}

#[actix_web::test]
async fn test_limiter_count_error() -> Result<(), Error> {
    let limiter = Limiter::builder("redis://127.0.0.1:6379/3")
        .limit(25)
        .build()
        .unwrap();

    let id = Uuid::new_v4();
    for i in 0..25 {
        let status = limiter.count(id.to_string()).await?;
        assert_eq!(25 - status.remaining(), i + 1);
    }

    match limiter.count(id.to_string()).await.unwrap_err() {
        Error::LimitExceeded(status) => assert_eq!(status.remaining(), 0),
        _ => panic!("error should be LimitExceeded variant"),
    };

    let id = Uuid::new_v4();
    for i in 0..25 {
        let status = limiter.count(id.to_string()).await?;
        assert_eq!(25 - status.remaining(), i + 1);
    }

    Ok(())
}

#[actix_web::test]
async fn test_limiter_key_by() -> Result<(), Error> {
    let cooldown_period = Duration::from_secs(1);
    let limiter = Limiter::builder("redis://127.0.0.1:6379/3")
        .limit(2)
        .period(cooldown_period)
        .key_by(|_: &ServiceRequest| Some("fix_key".to_string()))
        .build()
        .unwrap();

    let app = test::init_service(
        App::new()
            .wrap(RateLimiter::default())
            .app_data(web::Data::new(limiter))
            .route(
                "/",
                web::get().to(|_: HttpRequest| async { HttpResponse::Ok().body("ok") }),
            ),
    )
    .await;
    for _ in 1..2 {
        for index in 1..4 {
            let req = test::TestRequest::default().to_request();
            let resp = test::call_service(&app, req).await;
            if index <= 2 {
                assert!(resp.status().is_success());
            } else {
                assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);
            }
        }
        std::thread::sleep(cooldown_period);
    }

    Ok(())
}