use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration;

use crate::config::UserMaxUniqueIpsMode;
use crate::ip_tracker::UserIpTracker;

fn ip_from_idx(idx: u32) -> IpAddr {
    IpAddr::V4(Ipv4Addr::new(
        10,
        ((idx >> 16) & 0xff) as u8,
        ((idx >> 8) & 0xff) as u8,
        (idx & 0xff) as u8,
    ))
}

#[tokio::test]
async fn hotpath_empty_drain_is_idempotent() {
    let tracker = UserIpTracker::new();
    for _ in 0..128 {
        tracker.drain_cleanup_queue().await;
    }
    assert_eq!(tracker.get_active_ip_count("none").await, 0);
}

#[tokio::test]
async fn hotpath_batch_cleanup_drain_clears_all_active_entries() {
    let tracker = UserIpTracker::new();
    tracker.set_user_limit("u", 100).await;

    for idx in 0..32 {
        let ip = ip_from_idx(idx);
        tracker.check_and_add("u", ip).await.unwrap();
        tracker.enqueue_cleanup("u".to_string(), ip);
    }

    tracker.drain_cleanup_queue().await;
    assert_eq!(tracker.get_active_ip_count("u").await, 0);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn hotpath_parallel_enqueue_and_drain_does_not_deadlock() {
    let tracker = Arc::new(UserIpTracker::new());
    tracker.set_user_limit("p", 64).await;

    let mut tasks = Vec::new();
    for worker in 0..32u32 {
        let t = tracker.clone();
        tasks.push(tokio::spawn(async move {
            let ip = ip_from_idx(1_000 + worker);
            for _ in 0..64 {
                let _ = t.check_and_add("p", ip).await;
                t.enqueue_cleanup("p".to_string(), ip);
                t.drain_cleanup_queue().await;
            }
        }));
    }

    for task in tasks {
        tokio::time::timeout(Duration::from_secs(3), task)
            .await
            .expect("worker must not deadlock")
            .expect("worker task must not panic");
    }
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn hotpath_parallel_unique_ip_limit_never_exceeds_cap() {
    let tracker = Arc::new(UserIpTracker::new());
    tracker.set_user_limit("limit", 5).await;

    let mut tasks = Vec::new();
    for idx in 0..64u32 {
        let t = tracker.clone();
        tasks.push(tokio::spawn(async move {
            t.check_and_add("limit", ip_from_idx(idx)).await.is_ok()
        }));
    }

    let mut admitted = 0usize;
    for task in tasks {
        if task.await.expect("task must not panic") {
            admitted += 1;
        }
    }

    assert!(
        admitted <= 5,
        "admitted unique IPs must not exceed configured cap"
    );
    assert!(tracker.get_active_ip_count("limit").await <= 5);
}

#[tokio::test]
async fn hotpath_repeated_same_ip_counter_balances_to_zero() {
    let tracker = UserIpTracker::new();
    tracker.set_user_limit("same", 1).await;
    let ip = ip_from_idx(77);

    for _ in 0..512 {
        tracker.check_and_add("same", ip).await.unwrap();
    }
    for _ in 0..512 {
        tracker.remove_ip("same", ip).await;
    }

    assert_eq!(tracker.get_active_ip_count("same").await, 0);
}

#[tokio::test]
async fn hotpath_light_fuzz_mixed_operations_preserve_limit_invariants() {
    let tracker = UserIpTracker::new();
    tracker.set_user_limit("fuzz", 4).await;

    let mut state: u64 = 0xA55A_5AA5_D15C_B00B;
    for _ in 0..4_000 {
        state ^= state << 7;
        state ^= state >> 9;
        state ^= state << 8;

        let ip = ip_from_idx((state as u32) % 8);
        match state & 0x3 {
            0 | 1 => {
                let _ = tracker.check_and_add("fuzz", ip).await;
            }
            _ => {
                tracker.remove_ip("fuzz", ip).await;
            }
        }

        assert!(
            tracker.get_active_ip_count("fuzz").await <= 4,
            "active count must stay within configured cap"
        );
    }
}

#[tokio::test]
async fn hotpath_multi_user_churn_keeps_isolation() {
    let tracker = UserIpTracker::new();
    tracker.set_user_limit("u1", 2).await;
    tracker.set_user_limit("u2", 3).await;

    for idx in 0..200u32 {
        let ip1 = ip_from_idx(idx % 5);
        let ip2 = ip_from_idx(100 + (idx % 7));
        let _ = tracker.check_and_add("u1", ip1).await;
        let _ = tracker.check_and_add("u2", ip2).await;
        if idx % 2 == 0 {
            tracker.remove_ip("u1", ip1).await;
        }
        if idx % 3 == 0 {
            tracker.remove_ip("u2", ip2).await;
        }
    }

    assert!(tracker.get_active_ip_count("u1").await <= 2);
    assert!(tracker.get_active_ip_count("u2").await <= 3);
}

#[tokio::test]
async fn hotpath_time_window_expiry_allows_new_ip_after_window() {
    let tracker = UserIpTracker::new();
    tracker.set_user_limit("tw", 1).await;
    tracker
        .set_limit_policy(UserMaxUniqueIpsMode::TimeWindow, 1)
        .await;

    let ip1 = ip_from_idx(901);
    let ip2 = ip_from_idx(902);

    tracker.check_and_add("tw", ip1).await.unwrap();
    tracker.remove_ip("tw", ip1).await;
    assert!(tracker.check_and_add("tw", ip2).await.is_err());

    tokio::time::sleep(Duration::from_millis(1_100)).await;
    assert!(tracker.check_and_add("tw", ip2).await.is_ok());
}
