use super::*;
use proptest::prelude::*;

// === Unit tests ===

#[test]
fn new_index_is_empty() {
    let index = BitmapIndex::new("category");
    assert!(index.is_empty());
    assert_eq!(index.total_count(), 0);
    assert_eq!(index.distinct_values(), 0);
    assert!(index.get("jazz").is_none());
    assert_eq!(index.cardinality("jazz"), 0);
}

#[test]
fn insert_single_entity() {
    let index = BitmapIndex::new("category");
    assert!(index.insert(1, "jazz"));
    assert!(!index.is_empty());
    assert_eq!(index.cardinality("jazz"), 1);
    assert_eq!(index.total_count(), 1);
    let bitmap = index.get("jazz").unwrap();
    assert!(bitmap.contains(1));
}

#[test]
fn insert_same_entity_same_value_is_idempotent() {
    let index = BitmapIndex::new("category");
    assert!(index.insert(1, "jazz"));
    assert!(!index.insert(1, "jazz"));
    assert_eq!(index.cardinality("jazz"), 1);
}

#[test]
fn multi_value_field_tags() {
    let index = BitmapIndex::new("tags");
    index.insert(1, "jazz");
    index.insert(1, "piano");
    index.insert(1, "classical");

    assert!(index.get("jazz").unwrap().contains(1));
    assert!(index.get("piano").unwrap().contains(1));
    assert!(index.get("classical").unwrap().contains(1));

    assert_eq!(index.total_count(), 1);
    assert_eq!(index.distinct_values(), 3);
}

#[test]
fn delete_entity_from_all_values() {
    let index = BitmapIndex::new("tags");
    index.insert(1, "jazz");
    index.insert(1, "piano");
    index.insert(2, "jazz");

    let removed_count = index.delete_entity(1);
    assert_eq!(removed_count, 2);

    assert!(!index.get("jazz").unwrap().contains(1));
    assert!(index.get("piano").is_none());

    assert!(index.get("jazz").unwrap().contains(2));
    assert_eq!(index.total_count(), 1);
}

#[test]
fn get_union_multiple_values() {
    let index = BitmapIndex::new("category");
    index.insert(1, "jazz");
    index.insert(2, "blues");
    index.insert(3, "jazz");

    let union = index.get_union(&["jazz", "blues"]);
    assert_eq!(union.len(), 3);
    assert!(union.contains(1));
    assert!(union.contains(2));
    assert!(union.contains(3));
}

#[test]
fn get_union_with_missing_value() {
    let index = BitmapIndex::new("category");
    index.insert(1, "jazz");

    let union = index.get_union(&["jazz", "nonexistent"]);
    assert_eq!(union.len(), 1);
    assert!(union.contains(1));
}

#[test]
fn get_union_empty_values() {
    let index = BitmapIndex::new("category");
    index.insert(1, "jazz");

    let union = index.get_union(&[]);
    assert!(union.is_empty());
}

#[test]
fn values_enumerates_all() {
    let index = BitmapIndex::new("category");
    index.insert(1, "jazz");
    index.insert(2, "blues");
    index.insert(3, "rock");

    let mut values = index.values();
    values.sort();
    assert_eq!(values, vec!["blues", "jazz", "rock"]);
}

#[test]
fn delete_nonexistent_returns_false() {
    let index = BitmapIndex::new("category");
    assert!(!index.delete(99, "jazz"));
}

#[test]
fn field_name_accessor() {
    let index = BitmapIndex::new("category");
    assert_eq!(index.field_name(), "category");
}

#[test]
fn total_cardinality_empty() {
    let idx = BitmapIndex::new("test");
    assert_eq!(idx.total_cardinality(), 0);
}

#[test]
fn total_cardinality_after_inserts() {
    let idx = BitmapIndex::new("test");
    idx.insert(1, "jazz");
    idx.insert(2, "jazz");
    idx.insert(3, "rock");
    // 2 in "jazz" + 1 in "rock" = 3 (sum, not union)
    assert_eq!(idx.total_cardinality(), 3);
}

#[test]
fn total_cardinality_counts_duplicates_across_values() {
    let idx = BitmapIndex::new("tags");
    // Entity 1 appears in two values -- total_cardinality counts it twice.
    idx.insert(1, "jazz");
    idx.insert(1, "piano");
    idx.insert(2, "jazz");
    // 2 in "jazz" + 1 in "piano" = 3, even though only 2 unique entities.
    assert_eq!(idx.total_cardinality(), 3);
    assert_eq!(idx.total_count(), 2); // total_count de-duplicates
}

#[test]
fn clone_shares_data() {
    let original = BitmapIndex::new("category");
    let cloned = original.clone();
    original.insert(1, "jazz");
    // The clone sees the write because Arc<RwLock<...>> is shared.
    assert_eq!(cloned.total_cardinality(), 1);
    assert_eq!(cloned.cardinality("jazz"), 1);
}

#[test]
fn empty_value_bitmaps_are_cleaned_up() {
    let index = BitmapIndex::new("category");
    index.insert(1, "jazz");
    index.delete(1, "jazz");

    assert!(index.get("jazz").is_none() || index.get("jazz").unwrap().is_empty());
    assert_eq!(index.total_count(), 0);
}

// === Property tests ===

proptest! {
    #[test]
    fn insert_query_roundtrip(
        entries in prop::collection::vec(
            (0u32..100_000, "[a-z]{1,5}"),
            1..500,
        ),
    ) {
        let index = BitmapIndex::new("test_field");
        for &(id, ref value) in &entries {
            index.insert(id, value.clone());
        }

        for &(id, ref value) in &entries {
            let bitmap = index.get(value).expect("value should exist");
            prop_assert!(bitmap.contains(id), "entity {id} not found for '{value}'");
        }
    }

    #[test]
    fn delete_removes_entity(
        ids in prop::collection::hash_set(0u32..10_000, 2..100),
        value in "[a-z]{1,5}",
    ) {
        let ids: Vec<u32> = ids.into_iter().collect();
        let index = BitmapIndex::new("test_field");
        for &id in &ids {
            index.insert(id, value.clone());
        }

        let half = ids.len() / 2;
        for &id in &ids[..half] {
            index.delete(id, &value);
        }

        let bitmap = index.get(&value).unwrap_or_default();
        for &id in &ids[..half] {
            prop_assert!(!bitmap.contains(id), "deleted entity {id} still in bitmap");
        }
        for &id in &ids[half..] {
            prop_assert!(bitmap.contains(id), "surviving entity {id} missing");
        }
    }

    #[test]
    fn cardinality_matches_bitmap(
        ids in prop::collection::hash_set(0u32..100_000, 1..1000),
        value in "[a-z]{1,3}",
    ) {
        let index = BitmapIndex::new("test_field");
        for &id in &ids {
            index.insert(id, value.clone());
        }
        prop_assert_eq!(
            index.cardinality(&value),
            ids.len() as u64,
        );
    }

    #[test]
    fn total_count_no_double_counting(
        entries in prop::collection::vec(
            (0u32..1_000, "[a-z]{1,3}"),
            1..200,
        ),
    ) {
        let index = BitmapIndex::new("tags");
        let mut all_ids = RoaringBitmap::new();
        for &(id, ref value) in &entries {
            index.insert(id, value.clone());
            all_ids.insert(id);
        }
        prop_assert_eq!(index.total_count(), all_ids.len());
    }

    #[test]
    fn get_union_is_correct(
        a_ids in prop::collection::vec(0u32..1000, 1..50),
        b_ids in prop::collection::vec(0u32..1000, 1..50),
    ) {
        let index = BitmapIndex::new("test_field");
        for &id in &a_ids {
            index.insert(id, "a".to_string());
        }
        for &id in &b_ids {
            index.insert(id, "b".to_string());
        }

        let union = index.get_union(&["a", "b"]);
        let manual_union = {
            let mut bm = index.get("a").unwrap_or_default();
            bm |= &index.get("b").unwrap_or_default();
            bm
        };
        prop_assert_eq!(union, manual_union);
    }

    #[test]
    fn serialize_deserialize_roundtrip(
        entries in prop::collection::vec(
            (0u32..10_000, "[a-z]{1,3}"),
            1..100,
        ),
    ) {
        let index = BitmapIndex::new("test_field");
        for &(id, ref value) in &entries {
            index.insert(id, value.clone());
        }

        let kv_pairs = index.serialize_to_kv_pairs().unwrap();
        let restored = BitmapIndex::load_from_kv_pairs(
            "test_field",
            kv_pairs.into_iter(),
        ).unwrap();

        for value in index.values() {
            let orig = index.get(&value).unwrap();
            let rest = restored.get(&value).unwrap();
            prop_assert_eq!(orig, rest, "mismatch for value '{}'", value);
        }
        prop_assert_eq!(index.total_count(), restored.total_count());
    }
}
