// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::btree::block::BlockCompressionType;
use crate::btree::meta::BTreeIndexMeta;
use crate::btree::query::IndexQuery;
use crate::btree::reader::BTreeIndexReader;
use crate::btree::test_util::{BytesFileRead, VecFileWrite};
use crate::btree::writer::BTreeIndexWriter;
use crate::spec::{DataType, Datum, PredicateOperator, VarCharType};
use bytes::Bytes;

fn int_key(v: i32) -> Vec<u8> {
    v.to_be_bytes().to_vec()
}

fn int_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
    let a_val = i32::from_be_bytes(a.try_into().unwrap());
    let b_val = i32::from_be_bytes(b.try_into().unwrap());
    a_val.cmp(&b_val)
}

/// Helper: write entries, finish, then open a reader from the in-memory bytes.
async fn write_and_open<F: Fn(&[u8], &[u8]) -> std::cmp::Ordering>(
    buf: &VecFileWrite,
    result: &crate::btree::writer::BTreeWriteResult,
    cmp: F,
) -> BTreeIndexReader<F> {
    let data = Bytes::from(buf.to_vec());
    let file_size = data.len() as u64;
    let reader: Box<dyn crate::io::FileRead> = Box::new(BytesFileRead(data));
    BTreeIndexReader::open(reader, file_size, &result.meta, cmp)
        .await
        .unwrap()
}

#[tokio::test]
async fn test_write_read_roundtrip() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    for i in 0..100 {
        let key = int_key(i);
        writer.write(Some(&key), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    assert_eq!(result.row_count, 100);
    assert!(!result.meta.has_nulls);

    let reader = write_and_open(&buf, &result, int_cmp).await;

    // Spot check via queries
    let bm = reader.query_equal(&int_key(0)).await.unwrap();
    assert!(bm.contains(0));
    let bm = reader.query_equal(&int_key(99)).await.unwrap();
    assert!(bm.contains(99));
    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 100);
}

#[tokio::test]
async fn test_duplicate_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    let key = int_key(42);
    writer.write(Some(&key), 100).await.unwrap();
    writer.write(Some(&key), 200).await.unwrap();
    writer.write(Some(&key), 300).await.unwrap();

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(42)).await.unwrap();
    assert_eq!(bm.len(), 3);
    assert!(bm.contains(100));
    assert!(bm.contains(200));
    assert!(bm.contains(300));
}

#[tokio::test]
async fn test_null_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    writer.write(None, 10).await.unwrap();
    writer.write(Some(&int_key(1)), 20).await.unwrap();
    writer.write(None, 30).await.unwrap();
    writer.write(Some(&int_key(2)), 40).await.unwrap();

    let result = writer.finish().await.unwrap();
    assert_eq!(result.row_count, 4);
    assert!(result.meta.has_nulls);

    let reader = write_and_open(&buf, &result, int_cmp).await;

    let null_bm = reader.null_bitmap();
    assert!(null_bm.contains(10));
    assert!(null_bm.contains(30));
    assert!(!null_bm.contains(20));
    assert!(!null_bm.contains(40));

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 2);
}

#[tokio::test]
async fn test_only_nulls() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    writer.write(None, 1).await.unwrap();
    writer.write(None, 2).await.unwrap();
    writer.write(None, 3).await.unwrap();

    let result = writer.finish().await.unwrap();
    assert!(result.meta.only_nulls());
    assert!(result.meta.has_nulls);

    let reader = write_and_open(&buf, &result, int_cmp).await;
    let null_bm = reader.null_bitmap();
    assert_eq!(null_bm.len(), 3);
}

#[tokio::test]
async fn test_equal_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    for i in 0..50 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(25)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(25));

    let bm = reader.query_equal(&int_key(999)).await.unwrap();
    assert_eq!(bm.len(), 0);
}

#[tokio::test]
async fn test_range_queries() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    for i in 0..20 {
        writer
            .write(Some(&int_key(i * 10)), i as i64)
            .await
            .unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_less_than(&int_key(50)).await.unwrap();
    assert_eq!(bm.len(), 5);

    let bm = reader.query_greater_or_equal(&int_key(150)).await.unwrap();
    assert_eq!(bm.len(), 5);

    let bm = reader
        .query_between(&int_key(30), &int_key(70))
        .await
        .unwrap();
    assert_eq!(bm.len(), 5);
}

#[tokio::test]
async fn test_prefix_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    let keys = ["apple", "apply", "apricot", "banana", "band", "bee"];
    for (i, k) in keys.iter().enumerate() {
        writer.write(Some(k.as_bytes()), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, |a: &[u8], b: &[u8]| a.cmp(b)).await;

    let bm = reader.query_prefix(b"ap").await.unwrap();
    assert_eq!(bm.len(), 3);
    assert!(bm.contains(0) && bm.contains(1) && bm.contains(2));

    let bm = reader.query_prefix(b"app").await.unwrap();
    assert_eq!(bm.len(), 2);
    assert!(bm.contains(0) && bm.contains(1));

    let bm = reader.query_prefix(b"ban").await.unwrap();
    assert_eq!(bm.len(), 2);
    assert!(bm.contains(3) && bm.contains(4));

    let bm = reader.query_prefix(b"b").await.unwrap();
    assert_eq!(bm.len(), 3);

    let bm = reader.query_prefix(b"z").await.unwrap();
    assert_eq!(bm.len(), 0);
    let bm = reader.query_prefix(b"").await.unwrap();
    assert_eq!(bm.len(), keys.len() as u64);
}

#[tokio::test]
async fn test_string_fallback_scan_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 32, BlockCompressionType::None);

    writer.write(None, 99).await.unwrap();
    let keys = [
        ("alice", 0),
        ("alpine", 1),
        ("bob", 2),
        ("carol", 3),
        ("malice", 4),
        ("office", 5),
    ];
    for (key, row_id) in keys {
        writer.write(Some(key.as_bytes()), row_id).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, |a: &[u8], b: &[u8]| a.cmp(b)).await;
    let data_type = DataType::VarChar(VarCharType::string_type());

    let ends_with = reader
        .query(
            PredicateOperator::EndsWith,
            &[Datum::String("ice".to_string())],
            &data_type,
        )
        .await
        .unwrap();
    assert_eq!(ends_with.iter().collect::<Vec<_>>(), vec![0, 4, 5]);

    let contains = reader
        .query(
            PredicateOperator::Contains,
            &[Datum::String("o".to_string())],
            &data_type,
        )
        .await
        .unwrap();
    assert_eq!(contains.iter().collect::<Vec<_>>(), vec![2, 3, 5]);

    let like = reader
        .query(
            PredicateOperator::Like,
            &[Datum::String("a%c_".to_string())],
            &data_type,
        )
        .await
        .unwrap();
    assert_eq!(like.iter().collect::<Vec<_>>(), vec![0]);
}

#[tokio::test]
async fn test_not_equal_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    for i in 0..5 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_not_equal(&int_key(2)).await.unwrap();
    assert_eq!(bm.len(), 4);
    assert!(!bm.contains(2));
}

#[tokio::test]
async fn test_in_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    for i in 0..10 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let keys: Vec<Vec<u8>> = vec![int_key(2), int_key(5), int_key(8)];
    let key_refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
    let bm = reader.query_in(&key_refs).await.unwrap();
    assert_eq!(bm.len(), 3);
    assert!(bm.contains(2));
    assert!(bm.contains(5));
    assert!(bm.contains(8));
}

#[tokio::test]
async fn test_string_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    let keys = ["apple", "banana", "cherry", "date", "elderberry"];
    for (i, k) in keys.iter().enumerate() {
        writer.write(Some(k.as_bytes()), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, |a, b| a.cmp(b)).await;

    let bm = reader.query_equal(b"apple").await.unwrap();
    assert!(bm.contains(0));
    let bm = reader.query_equal(b"elderberry").await.unwrap();
    assert!(bm.contains(4));
}

#[tokio::test]
async fn test_string_keys_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);

    let keys = ["apple", "banana", "cherry", "date", "elderberry"];
    for (i, k) in keys.iter().enumerate() {
        writer.write(Some(k.as_bytes()), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, |a, b| a.cmp(b)).await;

    let bm = reader.query_equal(b"cherry").await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(2));
}

#[tokio::test]
async fn test_large_dataset() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 4096, BlockCompressionType::None);

    let n = 10000;
    for i in 0..n {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    assert_eq!(result.row_count, n as u64);

    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(5000)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(5000));

    let bm = reader
        .query_between(&int_key(100), &int_key(199))
        .await
        .unwrap();
    assert_eq!(bm.len(), 100);
}

#[test]
fn test_meta_serialization() {
    let meta = BTreeIndexMeta::new(Some(int_key(0)), Some(int_key(99)), false);
    let bytes = meta.serialize();
    let decoded = BTreeIndexMeta::deserialize(&bytes).unwrap();
    assert_eq!(decoded.first_key, Some(int_key(0)));
    assert_eq!(decoded.last_key, Some(int_key(99)));
    assert!(!decoded.has_nulls);
}

#[tokio::test]
async fn test_zstd_compression() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::Zstd);

    for i in 0..100 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 100);

    let bm = reader.query_equal(&int_key(50)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(50));
}

#[tokio::test]
async fn test_single_entry() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);
    writer.write(Some(&int_key(42)), 99).await.unwrap();

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(42)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(99));

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 1);
}

#[tokio::test]
async fn test_boundary_queries() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);
    for i in 10..20 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }
    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_less_than(&int_key(10)).await.unwrap();
    assert_eq!(bm.len(), 0);

    let bm = reader.query_greater_than(&int_key(19)).await.unwrap();
    assert_eq!(bm.len(), 0);

    let bm = reader.query_equal(&int_key(10)).await.unwrap();
    assert_eq!(bm.len(), 1);

    let bm = reader.query_equal(&int_key(19)).await.unwrap();
    assert_eq!(bm.len(), 1);

    let bm = reader.query_less_or_equal(&int_key(10)).await.unwrap();
    assert_eq!(bm.len(), 1);

    let bm = reader.query_greater_or_equal(&int_key(19)).await.unwrap();
    assert_eq!(bm.len(), 1);
}

#[tokio::test]
async fn test_large_row_ids() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    let large_ids: Vec<i64> = vec![0, i32::MAX as i64, i64::MAX / 2];
    for (i, &id) in large_ids.iter().enumerate() {
        writer.write(Some(&int_key(i as i32)), id).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(0)).await.unwrap();
    assert!(bm.contains(0));
    let bm = reader.query_equal(&int_key(1)).await.unwrap();
    assert!(bm.contains(i32::MAX as u64));
    let bm = reader.query_equal(&int_key(2)).await.unwrap();
    assert!(bm.contains((i64::MAX / 2) as u64));
}

#[tokio::test]
async fn test_many_duplicate_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    for i in 0..100 {
        writer.write(Some(&int_key(1)), i).await.unwrap();
    }

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(1)).await.unwrap();
    assert_eq!(bm.len(), 100);
}

#[tokio::test]
async fn test_nulls_with_range_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 256, BlockCompressionType::None);

    writer.write(None, 0).await.unwrap();
    for i in 1..=5 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }
    writer.write(None, 100).await.unwrap();

    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 5);
    assert!(!all.contains(0));
    assert!(!all.contains(100));

    let nulls = reader.null_bitmap();
    assert_eq!(nulls.len(), 2);
    assert!(nulls.contains(0));
    assert!(nulls.contains(100));
}

#[tokio::test]
async fn test_seek_before_all_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);
    for i in 100..110 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }
    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_greater_or_equal(&int_key(0)).await.unwrap();
    assert_eq!(bm.len(), 10);
}

#[tokio::test]
async fn test_seek_after_all_keys() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 64, BlockCompressionType::None);
    for i in 0..10 {
        writer.write(Some(&int_key(i)), i as i64).await.unwrap();
    }
    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader.query_equal(&int_key(999)).await.unwrap();
    assert_eq!(bm.len(), 0);

    let bm = reader.query_greater_or_equal(&int_key(999)).await.unwrap();
    assert_eq!(bm.len(), 0);
}

#[tokio::test]
async fn test_multiple_blocks_range_query() {
    let buf = VecFileWrite::new();
    let mut writer = BTreeIndexWriter::new(Box::new(buf.clone()), 16, BlockCompressionType::None);
    for i in 0..50 {
        writer.write(Some(&int_key(i * 2)), i as i64).await.unwrap();
    }
    let result = writer.finish().await.unwrap();
    let reader = write_and_open(&buf, &result, int_cmp).await;

    let bm = reader
        .query_between(&int_key(20), &int_key(40))
        .await
        .unwrap();
    assert_eq!(bm.len(), 11);

    let bm = reader.query_equal(&int_key(3)).await.unwrap();
    assert_eq!(bm.len(), 0);
}

// ============================================================
// Java compatibility tests — read files generated by Java Paimon
// ============================================================

fn le_int_key(v: i32) -> Vec<u8> {
    v.to_le_bytes().to_vec()
}

fn le_int_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
    let a_val = i32::from_le_bytes(a.try_into().unwrap());
    let b_val = i32::from_le_bytes(b.try_into().unwrap());
    a_val.cmp(&b_val)
}

fn load_testdata(name: &str) -> Vec<u8> {
    let path = format!("{}/testdata/btree/{name}", env!("CARGO_MANIFEST_DIR"));
    std::fs::read(&path).unwrap_or_else(|e| panic!("Failed to read {path}: {e}"))
}

async fn open_testdata<F: Fn(&[u8], &[u8]) -> std::cmp::Ordering>(
    name: &str,
    meta: &BTreeIndexMeta,
    cmp: F,
) -> BTreeIndexReader<F> {
    let data = Bytes::from(load_testdata(name));
    let file_size = data.len() as u64;
    let reader: Box<dyn crate::io::FileRead> = Box::new(BytesFileRead(data));
    BTreeIndexReader::open(reader, file_size, meta, cmp)
        .await
        .unwrap()
}

#[tokio::test]
async fn test_java_compat_int_no_compress() {
    let meta = BTreeIndexMeta::new(Some(le_int_key(0)), Some(le_int_key(198)), false);
    let reader = open_testdata("btree_int_100_no_compress.bin", &meta, le_int_cmp).await;

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 100);

    let bm = reader.query_equal(&le_int_key(50)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(25));

    let bm = reader
        .query_between(&le_int_key(10), &le_int_key(20))
        .await
        .unwrap();
    assert_eq!(bm.len(), 6);

    let bm = reader.query_equal(&le_int_key(1)).await.unwrap();
    assert_eq!(bm.len(), 0);
}

#[tokio::test]
async fn test_java_compat_int_zstd() {
    let meta = BTreeIndexMeta::new(Some(le_int_key(0)), Some(le_int_key(198)), false);
    let reader = open_testdata("btree_int_100_zstd.bin", &meta, le_int_cmp).await;

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 100);

    let bm = reader.query_equal(&le_int_key(100)).await.unwrap();
    assert_eq!(bm.len(), 1);
    assert!(bm.contains(50));
}

#[tokio::test]
async fn test_java_compat_int_with_nulls() {
    let meta = BTreeIndexMeta::new(Some(le_int_key(0)), Some(le_int_key(198)), true);
    let reader = open_testdata("btree_int_100_with_nulls.bin", &meta, le_int_cmp).await;

    let null_bm = reader.null_bitmap();
    assert!(!null_bm.is_empty());

    let all = reader.all_non_null_rows().await.unwrap();
    assert!(!all.is_empty());

    let total = null_bm.len() + all.len();
    assert!(total > 0);

    let bm = reader.query_equal(&le_int_key(0)).await.unwrap();
    assert_eq!(bm.len(), 1);

    for null_id in null_bm.iter() {
        assert!(!all.contains(null_id));
    }
}

#[tokio::test]
async fn test_java_compat_varchar_no_compress() {
    let meta = BTreeIndexMeta::new(Some(b"a".to_vec()), Some(b"yyyy".to_vec()), false);
    let reader = open_testdata("btree_varchar_100_no_compress.bin", &meta, |a, b| a.cmp(b)).await;

    let all = reader.all_non_null_rows().await.unwrap();
    assert_eq!(all.len(), 100);

    let bm = reader.query_equal(b"a").await.unwrap();
    assert_eq!(bm.len(), 1);
}

#[tokio::test]
async fn test_java_compat_int_lz4_unsupported() {
    let data = Bytes::from(load_testdata("btree_int_100_lz4.bin"));
    let file_size = data.len() as u64;
    let meta = BTreeIndexMeta::new(Some(le_int_key(0)), Some(le_int_key(198)), false);
    let reader_result =
        BTreeIndexReader::open(Box::new(BytesFileRead(data)), file_size, &meta, le_int_cmp).await;

    match reader_result {
        Err(e) => {
            assert!(
                e.to_string().contains("not supported") || e.to_string().contains("Unsupported"),
                "Expected unsupported compression error, got: {e}"
            );
        }
        Ok(reader) => {
            // If reader creation succeeded (index block not compressed),
            // data block read should fail
            let result = reader.query_equal(&le_int_key(0)).await;
            assert!(result.is_err(), "LZ4 data block read should fail");
        }
    }
}
