use super::*;
use crate::adaptive_rate::AdaptiveRateLimiter;
use crate::adaptive_rate::{DEFAULT_BACKOFF, MAX_BACKOFF};
use tokio::time::sleep;

#[tokio::test]
async fn new_limiter_allows_all_requests() {
    let limiter = AdaptiveRateLimiter::new();

    assert!(!limiter.should_throttle("example.com").await);
    assert!(!limiter.should_throttle("test.com").await);
}

#[tokio::test]
async fn records_successful_response() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", 200, Duration::from_millis(100))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    assert!(!status.is_throttled);
    assert_eq!(status.backoff_exponent, 0);
}

#[tokio::test]
async fn throttles_after_429_response() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    assert!(status.is_throttled);
    assert!(status.backoff_exponent > 0);
    assert!(status.current_backoff >= DEFAULT_BACKOFF);
}

#[tokio::test]
async fn should_throttle_returns_true_during_backoff() {
    let limiter = AdaptiveRateLimiter::new();

    // Record a 429 to trigger backoff
    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;

    // Should be throttled immediately after
    assert!(limiter.should_throttle("example.com").await);
}

#[tokio::test]
async fn exponential_backoff_increases_with_multiple_429s() {
    let limiter = AdaptiveRateLimiter::new();

    // Record multiple 429s
    for _ in 0..5 {
        limiter
            .record_response("example.com", 429, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    assert!(status.backoff_exponent >= 4);
    assert!(status.current_backoff > DEFAULT_BACKOFF);
}

#[tokio::test]
async fn successful_responses_reduce_backoff() {
    let limiter = AdaptiveRateLimiter::new();

    // First trigger backoff
    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;
    let status_after_429 = limiter.get_backoff_status("example.com").await;
    assert!(status_after_429.backoff_exponent > 0);

    // Wait for backoff to expire, then record success
    sleep(DEFAULT_BACKOFF * 2).await;
    limiter
        .record_response("example.com", 200, Duration::from_millis(100))
        .await;

    let status_after_success = limiter.get_backoff_status("example.com").await;
    assert!(
        status_after_success.backoff_exponent < status_after_429.backoff_exponent
            || !status_after_success.is_throttled
    );
}

#[tokio::test]
async fn per_host_isolation() {
    let limiter = AdaptiveRateLimiter::new();

    // Rate limit only affects the specific host
    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;

    assert!(limiter.should_throttle("example.com").await);
    assert!(!limiter.should_throttle("other.com").await);
    assert!(!limiter.should_throttle("example.org").await);
}

#[tokio::test]
async fn detects_latency_spike() {
    let limiter = AdaptiveRateLimiter::new();

    // Record several normal response times
    for _ in 0..5 {
        limiter
            .record_response("example.com", 200, Duration::from_millis(50))
            .await;
    }

    // Record a spike
    limiter
        .record_response("example.com", 200, Duration::from_secs(2))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    // Latency spike should have triggered backoff
    assert!(status.is_throttled || status.backoff_exponent > 0);
}

#[tokio::test]
async fn error_rate_threshold_triggers_backoff() {
    let limiter = AdaptiveRateLimiter::new();

    // Record several 403s to trigger error rate threshold
    for _ in 0..5 {
        limiter
            .record_response("example.com", 403, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    // Error rate should have triggered backoff
    assert!(status.is_throttled || status.recent_errors >= 5);
}

#[tokio::test]
async fn mixed_responses_maintain_tracking() {
    let limiter = AdaptiveRateLimiter::new();

    // Mix of success and errors
    limiter
        .record_response("example.com", 200, Duration::from_millis(100))
        .await;
    limiter
        .record_response("example.com", 200, Duration::from_millis(120))
        .await;
    limiter
        .record_response("example.com", 403, Duration::from_millis(50))
        .await;
    limiter
        .record_response("example.com", 200, Duration::from_millis(110))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    assert_eq!(status.total_recent_requests, 4);
    assert_eq!(status.recent_errors, 1);
}

#[tokio::test]
async fn clear_host_removes_state() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;
    assert!(limiter.should_throttle("example.com").await);

    limiter.clear_host("example.com").await;

    // After clearing, should not be throttled
    assert!(!limiter.should_throttle("example.com").await);
}

#[tokio::test]
async fn throttle_duration_returns_remaining_time() {
    let limiter = AdaptiveRateLimiter::new();

    // Initially no throttle
    assert!(limiter.throttle_duration("example.com").await.is_none());

    // Trigger backoff
    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;

    // Should have some throttle duration
    let duration = limiter.throttle_duration("example.com").await;
    assert!(duration.is_some());
    assert!(duration.expect("operation should succeed") > Duration::ZERO);
}

#[tokio::test]
async fn backoff_respects_max_limit() {
    let limiter = AdaptiveRateLimiter::new();

    // Record many 429s to try to exceed max backoff
    for _ in 0..20 {
        limiter
            .record_response("example.com", 429, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    assert!(status.current_backoff <= MAX_BACKOFF);
    assert!(status.backoff_exponent <= 10);
}

#[tokio::test]
async fn status_503_triggers_error_tracking() {
    let limiter = AdaptiveRateLimiter::new();

    // Record several 503s
    for _ in 0..5 {
        limiter
            .record_response("example.com", 503, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    assert_eq!(status.recent_errors, 5);
}

// Tests for the RateLimiter trait implementation (using SyncRateLimiter)

#[test]
fn sync_rate_limiter_implements_trait() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();
    assert_eq!(limiter.name(), "sync-adaptive");
}

#[test]
fn sync_rate_limiter_allows_all_requests_initially() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    assert!(limiter.should_throttle("example.com").is_none());
    assert!(limiter.should_throttle("test.com").is_none());
}

#[test]
fn sync_rate_limiter_throttles_after_429_response() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Record a 429 response
    limiter.record_response("example.com", 429, Duration::from_millis(100));

    // Should be throttled
    assert!(limiter.should_throttle("example.com").is_some());
}

#[test]
fn sync_rate_limiter_per_host_isolation() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Rate limit only affects the specific host
    limiter.record_response("example.com", 429, Duration::from_millis(100));

    assert!(limiter.should_throttle("example.com").is_some());
    assert!(limiter.should_throttle("other.com").is_none());
    assert!(limiter.should_throttle("example.org").is_none());
}

#[test]
fn sync_rate_limiter_successful_response_clears_throttle() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Trigger backoff
    limiter.record_response("example.com", 429, Duration::from_millis(100));
    assert!(limiter.should_throttle("example.com").is_some());

    // Wait for backoff to expire, then record success
    std::thread::sleep(DEFAULT_BACKOFF * 2);
    limiter.record_response("example.com", 200, Duration::from_millis(100));

    // After success, backoff should be reduced
    assert!(limiter.should_throttle("example.com").is_none());
}

#[test]
fn sync_rate_limiter_error_rate_triggers_backoff() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Record several 403s to trigger error rate threshold
    for _ in 0..5 {
        limiter.record_response("example.com", 403, Duration::from_millis(100));
    }

    // Error rate should have triggered backoff
    assert!(limiter.should_throttle("example.com").is_some());
}

#[test]
fn sync_rate_limiter_latency_spike_detection() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Record several normal response times
    for _ in 0..5 {
        limiter.record_response("example.com", 200, Duration::from_millis(50));
    }

    // Should not be throttled yet
    assert!(limiter.should_throttle("example.com").is_none());

    // Record a spike
    limiter.record_response("example.com", 200, Duration::from_secs(2));

    // Latency spike should have triggered backoff
    assert!(limiter.should_throttle("example.com").is_some());
}

// =============================================================================
// DESTRUCTIVE BUG-HUNTING TESTS - Find the vulnerabilities
// =============================================================================

/// Test 1: Status code 0 - edge case
#[tokio::test]
async fn test_status_code_zero() {
    let limiter = AdaptiveRateLimiter::new();

    // Status code 0 is not explicitly handled
    limiter
        .record_response("example.com", 0, Duration::from_millis(100))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    // BUG: Status 0 falls through to default case, may cause unexpected behavior
    let _ = status.is_throttled;
}

/// Test 2: Status code 65535 - u16 overflow edge case
#[tokio::test]
async fn test_status_code_max_u16() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", u16::MAX, Duration::from_millis(100))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    // Max u16 should be handled as unknown status
    let _ = status.is_throttled;
}

/// Test 3: Very long host name
#[tokio::test]
async fn test_very_long_hostname() {
    let limiter = AdaptiveRateLimiter::new();

    let long_host = "a".repeat(10000) + ".com";
    limiter
        .record_response(&long_host, 429, Duration::from_millis(100))
        .await;

    // Should handle very long hostnames
    assert!(limiter.should_throttle(&long_host).await);
}

/// Test 4: Empty host string
#[tokio::test]
async fn test_empty_host() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("", 429, Duration::from_millis(100))
        .await;

    // Empty host should be tracked
    assert!(limiter.should_throttle("").await);
}

/// Test 5: Host with null bytes and special characters
#[tokio::test]
async fn test_host_special_chars() {
    let limiter = AdaptiveRateLimiter::new();

    let special_hosts = vec![
        "host\x00hidden",
        "host\nwith\r\nnewlines",
        "host\twith\ttabs",
    ];

    for host in special_hosts {
        limiter
            .record_response(host, 429, Duration::from_millis(100))
            .await;
        // Should handle special characters
        let _ = limiter.should_throttle(host).await;
    }
}

/// Test 6: Concurrent access - race condition test
#[tokio::test]
async fn test_concurrent_record_response() {
    use std::sync::Arc;

    let limiter = Arc::new(AdaptiveRateLimiter::new());
    let mut handles = vec![];

    // Spawn 100 tasks recording responses concurrently
    for i in 0..100 {
        let limiter_clone = Arc::clone(&limiter);
        let host = format!("host{}", i % 10); // 10 different hosts
        handles.push(tokio::spawn(async move {
            limiter_clone
                .record_response(&host, 429, Duration::from_millis(100))
                .await;
        }));
    }

    for handle in handles {
        handle.await.unwrap();
    }

    // All hosts should be throttled
    for i in 0..10 {
        assert!(limiter.should_throttle(&format!("host{i}")).await);
    }
}

/// Test 7: Very small duration - precision issues
#[tokio::test]
async fn test_very_small_duration() {
    let limiter = AdaptiveRateLimiter::new();

    // Record several near-zero durations
    for _ in 0..5 {
        limiter
            .record_response("example.com", 200, Duration::from_nanos(1))
            .await;
    }

    // Then a "spike" that's still very small
    limiter
        .record_response("example.com", 200, Duration::from_millis(1))
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    // BUG: May incorrectly detect spike due to division by near-zero
    let _ = status.is_throttled;
}

/// Test 8: Very large duration - overflow in calculations
#[tokio::test]
async fn test_very_large_duration() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", 200, Duration::MAX)
        .await;

    let status = limiter.get_backoff_status("example.com").await;
    // BUG: Duration::MAX may cause overflow in calculations
    let _ = status.ewma_latency_ms;
}

/// Test 9: Rapid `clear_host` and `record_response` - consistency
#[tokio::test]
async fn test_rapid_clear_and_record() {
    let limiter = AdaptiveRateLimiter::new();

    // Rapidly clear and record
    for _ in 0..100 {
        limiter
            .record_response("example.com", 429, Duration::from_millis(100))
            .await;
        limiter.clear_host("example.com").await;
    }

    // State should be cleared
    assert!(!limiter.should_throttle("example.com").await);
}

/// Test 10: Overflow in exponent calculation
#[tokio::test]
async fn test_backoff_exponent_overflow() {
    let limiter = AdaptiveRateLimiter::new();

    // Record many 429s to max out exponent
    for _ in 0..100 {
        limiter
            .record_response("example.com", 429, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    // Exponent should be capped at 10
    assert!(status.backoff_exponent <= 10);
    assert!(status.current_backoff <= MAX_BACKOFF);
}

/// Test 11: `SyncRateLimiter` with concurrent access
#[test]
fn test_sync_rate_limiter_concurrent() {
    use secir::plugin::RateLimiter;
    use std::sync::Arc;
    use std::thread;

    let limiter = Arc::new(SyncRateLimiter::new());
    let mut handles = vec![];

    // Spawn threads concurrently
    for i in 0..100 {
        let limiter_clone = Arc::clone(&limiter);
        handles.push(thread::spawn(move || {
            limiter_clone.record_response(
                &format!("host{}", i % 10),
                429,
                Duration::from_millis(100),
            );
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }

    // All hosts should be throttled
    for i in 0..10 {
        assert!(limiter.should_throttle(&format!("host{i}")).is_some());
    }
}

/// Test 12: Error rate exactly at threshold boundary
#[tokio::test]
async fn test_error_rate_exact_threshold() {
    let limiter = AdaptiveRateLimiter::new();

    // 5 requests, 2 errors = 0.4 error rate (below 0.5 threshold)
    for _ in 0..3 {
        limiter
            .record_response("example.com", 200, Duration::from_millis(100))
            .await;
    }
    for _ in 0..2 {
        limiter
            .record_response("example.com", 403, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    // Should not be throttled (error rate 0.4 < 0.5)
    // BUG: Off-by-one errors in threshold comparison
    let _ = status.is_throttled;
}

/// Test 13: Hosts with same prefix
#[tokio::test]
async fn test_hosts_with_same_prefix() {
    let limiter = AdaptiveRateLimiter::new();

    // These should be treated as different hosts
    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;
    limiter
        .record_response("example.com.au", 200, Duration::from_millis(100))
        .await;

    // Only example.com should be throttled
    assert!(limiter.should_throttle("example.com").await);
    assert!(!limiter.should_throttle("example.com.au").await);
}

/// Test 14: Unicode hostnames
#[tokio::test]
async fn test_unicode_hostnames() {
    let limiter = AdaptiveRateLimiter::new();

    let unicode_hosts = vec![
        "日本語.example.com",
        "münchen.example.com",
        "🎉.example.com",
    ];

    for host in unicode_hosts {
        limiter
            .record_response(host, 429, Duration::from_millis(100))
            .await;
        assert!(
            limiter.should_throttle(host).await,
            "unicode host {host} should be throttled"
        );
    }
}

/// Test 15: Zero duration response
#[tokio::test]
async fn test_zero_duration_response() {
    let limiter = AdaptiveRateLimiter::new();

    // Record responses with zero duration
    for _ in 0..10 {
        limiter
            .record_response("example.com", 200, Duration::ZERO)
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    // Zero duration may cause division by zero in spike detection
    let _ = status.ewma_latency_ms;
}

/// Test 16: Rapid status code alternation
#[tokio::test]
async fn test_rapid_status_alternation() {
    let limiter = AdaptiveRateLimiter::new();

    // Rapidly alternate between success and rate limit
    for i in 0..100 {
        if i % 2 == 0 {
            limiter
                .record_response("example.com", 429, Duration::from_millis(100))
                .await;
        } else {
            limiter
                .record_response("example.com", 200, Duration::from_millis(100))
                .await;
        }
    }

    let status = limiter.get_backoff_status("example.com").await;
    // State should be consistent
    let _ = status.total_recent_requests;
}

/// Test 17: Large number of unique hosts
#[tokio::test]
async fn test_large_number_of_hosts() {
    let limiter = AdaptiveRateLimiter::new();

    // Record responses for 10000 different hosts
    for i in 0..10000 {
        limiter
            .record_response(
                &format!("host{i}.example.com"),
                429,
                Duration::from_millis(100),
            )
            .await;
    }

    // All should be tracked
    for i in 0..10000 {
        assert!(
            limiter
                .should_throttle(&format!("host{i}.example.com"))
                .await
        );
    }
}

/// Test 18: Negative throttle duration check
#[tokio::test]
async fn test_negative_throttle_duration() {
    let limiter = AdaptiveRateLimiter::new();

    limiter
        .record_response("example.com", 429, Duration::from_millis(100))
        .await;

    // Wait for backoff to expire
    sleep(Duration::from_secs(2)).await;

    // After backoff expires, throttle_duration should return None
    let duration = limiter.throttle_duration("example.com").await;
    assert!(
        duration.is_none(),
        "after backoff expires, should return None"
    );
}

/// Test 19: `SyncRateLimiter` `clear_all` under load
#[test]
fn test_sync_clear_all_under_load() {
    use secir::plugin::RateLimiter;

    let limiter = SyncRateLimiter::new();

    // Record many responses
    for i in 0..100 {
        limiter.record_response(&format!("host{i}"), 429, Duration::from_millis(100));
    }

    // Clear all
    limiter.clear_all();

    // All should be cleared
    for i in 0..100 {
        assert!(limiter.should_throttle(&format!("host{i}")).is_none());
    }
}

/// Test 20: Multiple identical status codes in succession
#[tokio::test]
async fn test_identical_status_codes() {
    let limiter = AdaptiveRateLimiter::new();

    // Record 100 identical 429 responses
    for _ in 0..100 {
        limiter
            .record_response("example.com", 429, Duration::from_millis(100))
            .await;
    }

    let status = limiter.get_backoff_status("example.com").await;
    // Exponent should max out at 10, not grow indefinitely
    assert!(status.backoff_exponent <= 10);
    assert!(status.current_backoff <= MAX_BACKOFF);
}
