use leankg::graph::cache::{CacheEntry, QueryCache, TimedCache};
use std::time::{Duration, Instant};

#[cfg(test)]
mod timed_cache_tests {

    use super::*;

    #[test]
    fn test_ttl_expiry_immediate_with_zero_ttl() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(0, 100);
        cache.insert("key1", "value1");
        assert_eq!(cache.get(&"key1"), None);
    }

    #[test]
    fn test_ttl_expiry_after_some_time() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(0, 100);
        cache.insert("key1", "value1");
        std::thread::sleep(Duration::from_millis(10));
        assert_eq!(cache.get(&"key1"), None);
    }

    #[test]
    fn test_ttl_not_expired_within_ttl() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        assert_eq!(cache.get(&"key1"), Some("value1"));
    }

    #[test]
    fn test_ttl_not_expired_concurrent_access() {
        let mut cache: TimedCache<String, Vec<u32>> = TimedCache::new(60, 100);
        cache.insert("nums".to_string(), vec![1, 2, 3]);
        let result = cache.get(&"nums".to_string());
        assert_eq!(result, Some(vec![1, 2, 3]));
    }

    #[test]
    fn test_max_entries_eviction_removes_oldest() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 2);
        cache.insert("key1", "value1");
        std::thread::sleep(Duration::from_millis(5));
        cache.insert("key2", "value2");
        std::thread::sleep(Duration::from_millis(5));
        cache.insert("key3", "value3");

        assert!(cache.get(&"key1").is_none());
        assert_eq!(cache.get(&"key2"), Some("value2"));
        assert_eq!(cache.get(&"key3"), Some("value3"));
    }

    #[test]
    fn test_max_entries_eviction_when_exactly_full() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 3);
        cache.insert("key1", "value1");
        cache.insert("key2", "value2");
        cache.insert("key3", "value3");
        assert_eq!(cache.len(), 3);
        assert!(cache.get(&"key1").is_some());
        assert!(cache.get(&"key2").is_some());
        assert!(cache.get(&"key3").is_some());
    }

    #[test]
    fn test_max_entries_eviction_on_insert() {
        let mut cache: TimedCache<i32, &str> = TimedCache::new(60, 2);
        cache.insert(1, "one");
        cache.insert(2, "two");
        cache.insert(3, "three");
        assert!(cache.get(&1).is_none());
        assert_eq!(cache.get(&2), Some("two"));
        assert_eq!(cache.get(&3), Some("three"));
    }

    #[test]
    fn test_invalidate_prefix_removes_matching_keys() {
        let mut cache: TimedCache<String, String> = TimedCache::new(60, 100);
        cache.insert("src/main.rs".to_string(), "content1".to_string());
        cache.insert("src/lib.rs".to_string(), "content2".to_string());
        cache.insert("tests/main.rs".to_string(), "content3".to_string());

        cache.invalidate_prefix("src/");

        assert!(cache.get(&"src/main.rs".to_string()).is_none());
        assert!(cache.get(&"src/lib.rs".to_string()).is_none());
        assert_eq!(
            cache.get(&"tests/main.rs".to_string()),
            Some("content3".to_string())
        );
    }

    #[test]
    fn test_invalidate_prefix_removes_all_prefixed() {
        let mut cache: TimedCache<String, i32> = TimedCache::new(60, 100);
        cache.insert("prefix_a".to_string(), 1);
        cache.insert("prefix_b".to_string(), 2);
        cache.insert("other".to_string(), 3);

        cache.invalidate_prefix("prefix_");

        assert!(cache.get(&"prefix_a".to_string()).is_none());
        assert!(cache.get(&"prefix_b".to_string()).is_none());
        assert_eq!(cache.get(&"other".to_string()), Some(3));
    }

    #[test]
    fn test_invalidate_prefix_no_match() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        cache.insert("key2", "value2");

        cache.invalidate_prefix("nonexistent");

        assert_eq!(cache.get(&"key1"), Some("value1"));
        assert_eq!(cache.get(&"key2"), Some("value2"));
    }

    #[test]
    fn test_invalidate_prefix_empty_cache() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.invalidate_prefix("any");
        assert!(cache.is_empty());
    }

    #[test]
    fn test_invalidate_removes_specific_key() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        cache.insert("key2", "value2");

        cache.invalidate(&"key1");

        assert!(cache.get(&"key1").is_none());
        assert_eq!(cache.get(&"key2"), Some("value2"));
    }

    #[test]
    fn test_invalidate_nonexistent_key() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        cache.invalidate(&"nonexistent");
        assert_eq!(cache.len(), 1);
    }

    #[test]
    fn test_clear_removes_all_entries() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        cache.insert("key2", "value2");
        cache.insert("key3", "value3");

        cache.clear();

        assert!(cache.is_empty());
        assert_eq!(cache.len(), 0);
    }

    #[test]
    fn test_len_returns_correct_count() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        assert_eq!(cache.len(), 0);
        cache.insert("key1", "value1");
        assert_eq!(cache.len(), 1);
        cache.insert("key2", "value2");
        assert_eq!(cache.len(), 2);
    }

    #[test]
    fn test_is_empty_returns_true_when_empty() {
        let cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        assert!(cache.is_empty());
    }

    #[test]
    fn test_is_empty_returns_false_when_not_empty() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 100);
        cache.insert("key1", "value1");
        assert!(!cache.is_empty());
    }

    #[test]
    fn test_cache_entry_clone() {
        let entry = CacheEntry {
            value: "test".to_string(),
            created_at: Instant::now(),
        };
        let cloned = entry.clone();
        assert_eq!(cloned.value, entry.value);
    }

    #[test]
    fn test_multiple_inserts_same_key_updates_value() {
        let mut cache: TimedCache<&str, &str> = TimedCache::new(60, 10);
        cache.insert("key1", "value1");
        cache.insert("key1", "value2");
        assert_eq!(cache.get(&"key1"), Some("value2"));
        assert_eq!(cache.len(), 1);
    }
}

#[cfg(test)]
mod query_cache_tests {

    use super::*;

    #[tokio::test(flavor = "multi_thread")]
    async fn test_set_and_get_dependencies() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies(
                "file1.rs".to_string(),
                vec!["file2.rs".to_string(), "file3.rs".to_string()],
            )
            .await;

        let result = cache.get_dependencies("file1.rs").await;
        assert_eq!(
            result,
            Some(vec!["file2.rs".to_string(), "file3.rs".to_string()])
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_set_and_get_dependents() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependents("file2.rs".to_string(), vec!["file1.rs".to_string()])
            .await;

        let result = cache.get_dependents("file2.rs").await;
        assert_eq!(result, Some(vec!["file1.rs".to_string()]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_get_dependencies_nonexistent() {
        let cache = QueryCache::new(60, 100);
        let result = cache.get_dependencies("nonexistent.rs").await;
        assert_eq!(result, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_get_dependents_nonexistent() {
        let cache = QueryCache::new(60, 100);
        let result = cache.get_dependents("nonexistent.rs").await;
        assert_eq!(result, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_file_clears_dependencies() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("src/main.rs".to_string(), vec!["lib.rs".to_string()])
            .await;

        cache.invalidate_file("src/main.rs").await;

        let result = cache.get_dependencies("src/main.rs").await;
        assert_eq!(result, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_file_clears_dependents() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependents("src/lib.rs".to_string(), vec!["main.rs".to_string()])
            .await;

        cache.invalidate_file("src/lib.rs").await;

        let result = cache.get_dependents("src/lib.rs").await;
        assert_eq!(result, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_file_clears_both_caches() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("src/main.rs".to_string(), vec!["lib.rs".to_string()])
            .await;
        cache
            .set_dependents("src/main.rs".to_string(), vec!["test.rs".to_string()])
            .await;

        cache.invalidate_file("src/main.rs").await;

        let deps = cache.get_dependencies("src/main.rs").await;
        let depts = cache.get_dependents("src/main.rs").await;
        assert_eq!(deps, None);
        assert_eq!(depts, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_file_with_nested_path() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies(
                "src/handlers/mod.rs".to_string(),
                vec!["lib.rs".to_string()],
            )
            .await;
        cache
            .set_dependents(
                "src/handlers/mod.rs".to_string(),
                vec!["main.rs".to_string()],
            )
            .await;

        cache.invalidate_file("src/handlers/mod.rs").await;

        let deps = cache.get_dependencies("src/handlers/mod.rs").await;
        let depts = cache.get_dependents("src/handlers/mod.rs").await;
        assert_eq!(deps, None);
        assert_eq!(depts, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_file_preserves_other_entries() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("src/file1.rs".to_string(), vec!["lib.rs".to_string()])
            .await;
        cache
            .set_dependencies("src/file2.rs".to_string(), vec!["lib.rs".to_string()])
            .await;

        cache.invalidate_file("src/file1.rs").await;

        let file1_deps = cache.get_dependencies("src/file1.rs").await;
        let file2_deps = cache.get_dependencies("src/file2.rs").await;
        assert_eq!(file1_deps, None);
        assert_eq!(file2_deps, Some(vec!["lib.rs".to_string()]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_clear_clears_both_dependencies_and_dependents() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("file1.rs".to_string(), vec!["file2.rs".to_string()])
            .await;
        cache
            .set_dependents("file2.rs".to_string(), vec!["file1.rs".to_string()])
            .await;

        cache.clear();

        let deps = cache.get_dependencies("file1.rs").await;
        let depts = cache.get_dependents("file2.rs").await;
        assert_eq!(deps, None);
        assert_eq!(depts, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_clear_on_empty_cache() {
        let cache = QueryCache::new(60, 100);
        cache.clear();
        let deps = cache.get_dependencies("any.rs").await;
        let depts = cache.get_dependents("any.rs").await;
        assert_eq!(deps, None);
        assert_eq!(depts, None);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_multiple_dependencies_entries() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("a.rs".to_string(), vec!["b.rs".to_string()])
            .await;
        cache
            .set_dependencies("c.rs".to_string(), vec!["d.rs".to_string()])
            .await;

        assert_eq!(
            cache.get_dependencies("a.rs").await,
            Some(vec!["b.rs".to_string()])
        );
        assert_eq!(
            cache.get_dependencies("c.rs").await,
            Some(vec!["d.rs".to_string()])
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_multiple_dependents_entries() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependents("b.rs".to_string(), vec!["a.rs".to_string()])
            .await;
        cache
            .set_dependents("d.rs".to_string(), vec!["c.rs".to_string()])
            .await;

        assert_eq!(
            cache.get_dependents("b.rs").await,
            Some(vec!["a.rs".to_string()])
        );
        assert_eq!(
            cache.get_dependents("d.rs").await,
            Some(vec!["c.rs".to_string()])
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_update_existing_dependencies() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("file.rs".to_string(), vec!["old.rs".to_string()])
            .await;
        cache
            .set_dependencies("file.rs".to_string(), vec!["new.rs".to_string()])
            .await;

        let result = cache.get_dependencies("file.rs").await;
        assert_eq!(result, Some(vec!["new.rs".to_string()]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_update_existing_dependents() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependents("file.rs".to_string(), vec!["old.rs".to_string()])
            .await;
        cache
            .set_dependents("file.rs".to_string(), vec!["new.rs".to_string()])
            .await;

        let result = cache.get_dependents("file.rs").await;
        assert_eq!(result, Some(vec!["new.rs".to_string()]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_empty_vec_dependencies() {
        let cache = QueryCache::new(60, 100);
        cache.set_dependencies("file.rs".to_string(), vec![]).await;

        let result = cache.get_dependencies("file.rs").await;
        assert_eq!(result, Some(vec![]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_empty_vec_dependents() {
        let cache = QueryCache::new(60, 100);
        cache.set_dependents("file.rs".to_string(), vec![]).await;

        let result = cache.get_dependents("file.rs").await;
        assert_eq!(result, Some(vec![]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_concurrent_dependencies_access() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("file.rs".to_string(), vec!["dep.rs".to_string()])
            .await;

        let cache_clone = cache.clone();
        let handle = tokio::spawn(async move { cache_clone.get_dependencies("file.rs").await });

        let result = handle.await.unwrap();
        assert_eq!(result, Some(vec!["dep.rs".to_string()]));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalidate_nonexistent_file() {
        let cache = QueryCache::new(60, 100);
        cache
            .set_dependencies("real.rs".to_string(), vec!["dep.rs".to_string()])
            .await;

        cache.invalidate_file("nonexistent.rs").await;

        let result = cache.get_dependencies("real.rs").await;
        assert_eq!(result, Some(vec!["dep.rs".to_string()]));
    }
}
