mod common;
use common::{base_url, client};
use reqwest::StatusCode;
use serde_json::json;

#[tokio::test]
#[ignore = "rate limiting is disabled in test environment"]
async fn test_login_rate_limiting() {
    let client = client();
    let url = format!("{}/xrpc/com.atproto.server.createSession", base_url().await);
    let payload = json!({
        "identifier": "nonexistent-user-for-rate-limit-test",
        "password": "wrongpassword"
    });
    let mut rate_limited_count = 0;
    let mut auth_failed_count = 0;
    for _ in 0..15 {
        let res = client
            .post(&url)
            .json(&payload)
            .send()
            .await
            .expect("Request failed");
        match res.status() {
            StatusCode::TOO_MANY_REQUESTS => {
                rate_limited_count += 1;
            }
            StatusCode::UNAUTHORIZED => {
                auth_failed_count += 1;
            }
            status => {
                panic!("Unexpected status: {}", status);
            }
        }
    }
    assert!(
        rate_limited_count > 0,
        "Expected at least one rate-limited response after 15 login attempts. Got {} auth failures and {} rate limits.",
        auth_failed_count,
        rate_limited_count
    );
}

#[tokio::test]
#[ignore = "rate limiting is disabled in test environment"]
async fn test_password_reset_rate_limiting() {
    let client = client();
    let url = format!(
        "{}/xrpc/com.atproto.server.requestPasswordReset",
        base_url().await
    );
    let mut rate_limited_count = 0;
    let mut success_count = 0;
    for i in 0..8 {
        let payload = json!({
            "email": format!("ratelimit-test_{}@example.com", i)
        });
        let res = client
            .post(&url)
            .json(&payload)
            .send()
            .await
            .expect("Request failed");
        match res.status() {
            StatusCode::TOO_MANY_REQUESTS => {
                rate_limited_count += 1;
            }
            StatusCode::OK => {
                success_count += 1;
            }
            status => {
                panic!("Unexpected status: {} - {:?}", status, res.text().await);
            }
        }
    }
    assert!(
        rate_limited_count > 0,
        "Expected rate limiting after {} password reset requests. Got {} successes.",
        success_count + rate_limited_count,
        success_count
    );
}

#[tokio::test]
#[ignore = "rate limiting is disabled in test environment"]
async fn test_account_creation_rate_limiting() {
    let client = client();
    let url = format!("{}/xrpc/com.atproto.server.createAccount", base_url().await);
    let mut rate_limited_count = 0;
    let mut other_count = 0;
    for i in 0..15 {
        let unique_id = uuid::Uuid::new_v4();
        let payload = json!({
            "handle": format!("ratelimit-{}_{}", i, unique_id),
            "email": format!("ratelimit-{}_{}@example.com", i, unique_id),
            "password": "Testpass123!"
        });
        let res = client
            .post(&url)
            .json(&payload)
            .send()
            .await
            .expect("Request failed");
        match res.status() {
            StatusCode::TOO_MANY_REQUESTS => {
                rate_limited_count += 1;
            }
            _ => {
                other_count += 1;
            }
        }
    }
    assert!(
        rate_limited_count > 0,
        "Expected rate limiting after account creation attempts. Got {} other responses and {} rate limits.",
        other_count,
        rate_limited_count
    );
}

#[cfg(feature = "valkey")]
#[tokio::test]
async fn test_valkey_connection() {
    if std::env::var("VALKEY_URL").is_err() {
        println!("VALKEY_URL not set, skipping Valkey connection test");
        return;
    }
    let valkey_url = std::env::var("VALKEY_URL").unwrap();
    let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client");
    let mut conn = client
        .get_multiplexed_async_connection()
        .await
        .expect("Failed to connect to Valkey");
    let pong: String = redis::cmd("PING")
        .query_async(&mut conn)
        .await
        .expect("PING failed");
    assert_eq!(pong, "PONG");
    let _: () = redis::cmd("SET")
        .arg("test_key")
        .arg("test_value")
        .arg("EX")
        .arg(10)
        .query_async(&mut conn)
        .await
        .expect("SET failed");
    let value: String = redis::cmd("GET")
        .arg("test_key")
        .query_async(&mut conn)
        .await
        .expect("GET failed");
    assert_eq!(value, "test_value");
    let _: () = redis::cmd("DEL")
        .arg("test_key")
        .query_async(&mut conn)
        .await
        .expect("DEL failed");
}

#[cfg(feature = "valkey")]
#[tokio::test]
async fn test_distributed_rate_limiter_directly() {
    if std::env::var("VALKEY_URL").is_err() {
        println!("VALKEY_URL not set, skipping distributed rate limiter test");
        return;
    }
    use tranquil_pds::cache::{DistributedRateLimiter, RedisRateLimiter};
    let valkey_url = std::env::var("VALKEY_URL").unwrap();
    let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client");
    let conn = client
        .get_connection_manager()
        .await
        .expect("Failed to get connection manager");
    let rate_limiter = RedisRateLimiter::new(conn);
    let test_key = format!("test_rate_limit_{}", uuid::Uuid::new_v4());
    let limit = 5;
    let window_ms = 60_000;
    for i in 0..limit {
        let allowed = rate_limiter
            .check_rate_limit(&test_key, limit, window_ms)
            .await;
        assert!(
            allowed,
            "Request {} should have been allowed (limit: {})",
            i + 1,
            limit
        );
    }
    let allowed = rate_limiter
        .check_rate_limit(&test_key, limit, window_ms)
        .await;
    assert!(
        !allowed,
        "Request {} should have been rate limited (limit: {})",
        limit + 1,
        limit
    );
    let allowed = rate_limiter
        .check_rate_limit(&test_key, limit, window_ms)
        .await;
    assert!(!allowed, "Subsequent request should also be rate limited");
}
