//! Battery 5: scan_prefix with ConceptPath Keys.
//!
//! Tests hierarchical prefix scanning for concept path subjects.
//!
//! # Test Coverage
//!
//! | Test | Feature | Validates |
//! |------|---------|-----------|
//! | `test_prefix_scan_concept_path_keys` | Hierarchical paths | Multi-level prefix matching |
//! | `test_prefix_scan_no_false_positives` | Trailing slash | Prevents substring false positives |
//! | `test_prefix_scan_sp_keys_with_concept_paths` | SP: keys | Compound key prefix scanning |

#![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages

use super::helpers::*;

/// Test 5.1: Prefix scan with ConceptPath-shaped subject keys.
///
/// Store assertions with subjects that look like hierarchical paths:
/// - S:code://rust/citadeldb/auth/jwt/aud_validation
/// - S:code://rust/citadeldb/auth/jwt/expiry
/// - S:code://rust/citadeldb/net/tls/verify
/// - S:code://rust/citadeldb/auth/oauth/scopes
///
/// Verify prefix scans correctly match hierarchical subject paths:
/// - Prefix "code://rust/citadeldb/auth/jwt/" matches 2 keys
/// - Prefix "code://rust/citadeldb/auth/" matches 3 keys
/// - Prefix "code://rust/citadeldb/" matches 4 keys
/// - Prefix "code://" matches 4 keys
/// - Prefix "rfc://" matches 0 keys (different scheme)
#[tokio::test]
async fn test_prefix_scan_concept_path_keys() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Store keys with ConceptPath-shaped subjects
    let key1 = key_codec::subject_index_key("code://rust/citadeldb/auth/jwt/aud_validation");
    let key2 = key_codec::subject_index_key("code://rust/citadeldb/auth/jwt/expiry");
    let key3 = key_codec::subject_index_key("code://rust/citadeldb/net/tls/verify");
    let key4 = key_codec::subject_index_key("code://rust/citadeldb/auth/oauth/scopes");

    store.put(&key1, b"hash_a").await.expect("put key1");
    store.put(&key2, b"hash_b").await.expect("put key2");
    store.put(&key3, b"hash_c").await.expect("put key3");
    store.put(&key4, b"hash_d").await.expect("put key4");

    // Test 1: Prefix scan for auth/jwt/ should match 2 keys
    // Since subject_index_key creates {subject}\x00S:, we scan with partial subject as prefix
    let prefix_jwt = b"code://rust/citadeldb/auth/jwt/";
    let results_jwt = store.scan_prefix(prefix_jwt).await.expect("scan jwt");
    assert_eq!(
        results_jwt.len(),
        2,
        "Prefix 'code://rust/citadeldb/auth/jwt/' should match 2 keys (aud_validation, expiry)"
    );

    // Test 2: Prefix scan for auth/ should match 3 keys
    let prefix_auth = b"code://rust/citadeldb/auth/";
    let results_auth = store.scan_prefix(prefix_auth).await.expect("scan auth");
    assert_eq!(
        results_auth.len(),
        3,
        "Prefix 'code://rust/citadeldb/auth/' should match 3 keys (jwt/aud, jwt/expiry, oauth/scopes)"
    );

    // Test 3: Prefix scan for citadeldb/ should match 4 keys
    let prefix_citadeldb = b"code://rust/citadeldb/";
    let results_citadeldb = store.scan_prefix(prefix_citadeldb).await.expect("scan citadeldb");
    assert_eq!(
        results_citadeldb.len(),
        4,
        "Prefix 'code://rust/citadeldb/' should match 4 keys (all)"
    );

    // Test 4: Prefix scan for code:// should match 4 keys
    let prefix_code = b"code://";
    let results_code = store.scan_prefix(prefix_code).await.expect("scan code");
    assert_eq!(results_code.len(), 4, "Prefix 'code://' should match 4 keys (all)");

    // Test 5: Prefix scan for rfc:// should match 0 keys (different scheme)
    let prefix_rfc = b"rfc://";
    let results_rfc = store.scan_prefix(prefix_rfc).await.expect("scan rfc");
    assert_eq!(results_rfc.len(), 0, "Prefix 'rfc://' should match 0 keys (different scheme)");
}

/// Test 5.2: Prefix scan prevents false positives with trailing slash.
///
/// Store two subjects:
/// - S:code://rust/citadeldb/auth
/// - S:code://rust/citadeldb/authentication
///
/// Verify:
/// - Prefix "code://rust/citadeldb/auth/" matches 0 keys (trailing slash)
/// - Prefix "code://rust/citadeldb/auth" matches 2 keys (both match)
///
/// This validates that trailing `/` prevents "auth" from matching "authentication".
#[tokio::test]
async fn test_prefix_scan_no_false_positives() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Store two subjects that share a common prefix
    let key1 = key_codec::subject_index_key("code://rust/citadeldb/auth");
    let key2 = key_codec::subject_index_key("code://rust/citadeldb/authentication");

    store.put(&key1, b"hash_a").await.expect("put key1");
    store.put(&key2, b"hash_b").await.expect("put key2");

    // Test 1: Prefix scan with trailing slash should match 0 keys
    // Keys are stored as "code://rust/citadeldb/auth\x00S:" and "code://rust/citadeldb/authentication\x00S:"
    // Scanning with "code://rust/citadeldb/auth/" will not match either
    let prefix_with_slash = b"code://rust/citadeldb/auth/";
    let results_with_slash = store.scan_prefix(prefix_with_slash).await.expect("scan with slash");
    assert_eq!(
        results_with_slash.len(),
        0,
        "Prefix 'code://rust/citadeldb/auth/' with trailing slash should match 0 keys \
         (prevents 'auth' from matching 'authentication')"
    );

    // Test 2: Prefix scan without trailing slash should match 2 keys
    // Scanning with "code://rust/citadeldb/auth" will match both keys
    let prefix_without_slash = b"code://rust/citadeldb/auth";
    let results_without_slash =
        store.scan_prefix(prefix_without_slash).await.expect("scan without slash");
    assert_eq!(
        results_without_slash.len(),
        2,
        "Prefix 'code://rust/citadeldb/auth' without trailing slash should match 2 keys \
         (both 'auth' and 'authentication' share the prefix)"
    );
}

/// Test 5.3: Prefix scan with SP: compound keys containing ConceptPath subjects.
///
/// Store SP: keys (subject+predicate) where subject is a ConceptPath:
/// - SP:code://rust/citadeldb/auth/jwt/aud_validation:config_value
/// - SP:code://rust/citadeldb/auth/jwt/expiry:config_value
///
/// Verify:
/// - Prefix scan for "code://rust/citadeldb/auth/jwt/" matches 2 SP: keys
///
/// This tests that hierarchical subject paths work correctly in compound SP: keys.
#[tokio::test]
async fn test_prefix_scan_sp_keys_with_concept_paths() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Store SP: keys with ConceptPath-shaped subjects
    // Keys are formatted as: {subject}\x00SP:{predicate}
    let key1 = key_codec::subject_predicate_key(
        "code://rust/citadeldb/auth/jwt/aud_validation",
        "config_value",
    );
    let key2 =
        key_codec::subject_predicate_key("code://rust/citadeldb/auth/jwt/expiry", "config_value");

    store.put(&key1, b"hash_a").await.expect("put key1");
    store.put(&key2, b"hash_b").await.expect("put key2");

    // Prefix scan for SP: keys matching the auth/jwt/ hierarchy
    // Use raw prefix of the subject path (before the \x00SP: separator)
    let prefix = b"code://rust/citadeldb/auth/jwt/";
    let results = store.scan_prefix(prefix).await.expect("scan SP:");

    assert_eq!(
        results.len(),
        2,
        "Prefix 'code://rust/citadeldb/auth/jwt/' should match 2 SP: keys"
    );

    // Verify the keys returned contain the expected predicate
    for (key, _value) in &results {
        let key_str = String::from_utf8_lossy(key);
        assert!(
            key_str.contains("config_value"),
            "SP: key should contain predicate 'config_value', got: {}",
            key_str
        );
    }
}
