//! Tests for Query, QueryBuilder, and QueryResult.

use stemedb_core::testing::AssertionBuilder;
use stemedb_core::types::{Assertion, LifecycleStage, PHash};

use super::{hamming_distance, parse_hex_phash, Query, QueryResult};

fn create_test_assertion(subject: &str, predicate: &str, lifecycle: LifecycleStage) -> Assertion {
    AssertionBuilder::new()
        .subject(subject)
        .predicate(predicate)
        .object_text("test")
        .confidence(0.95)
        .lifecycle(lifecycle)
        .build()
}
#[test]
fn test_query_builder() {
    let query = Query::builder()
        .subject("Tesla")
        .predicate("revenue")
        .lifecycle(LifecycleStage::Approved)
        .limit(10)
        .build();

    assert_eq!(query.subject, Some("Tesla".to_string()));
    assert_eq!(query.predicate, Some("revenue".to_string()));
    assert_eq!(query.lifecycle, Some(LifecycleStage::Approved));
    assert_eq!(query.limit, Some(10));
}

#[test]
fn test_query_matches_all_filters() {
    let assertion = create_test_assertion("Tesla", "has_revenue", LifecycleStage::Approved);

    // Query that matches
    let query = Query::builder()
        .subject("Tesla")
        .predicate("has_revenue")
        .lifecycle(LifecycleStage::Approved)
        .build();

    assert!(query.matches(&assertion));
}

#[test]
fn test_query_matches_subject_only() {
    let assertion = create_test_assertion("Tesla", "has_revenue", LifecycleStage::Proposed);

    let query = Query::builder().subject("Tesla").build();

    assert!(query.matches(&assertion));
}

#[test]
fn test_query_rejects_wrong_subject() {
    let assertion = create_test_assertion("Apple", "has_revenue", LifecycleStage::Approved);

    let query = Query::builder().subject("Tesla").build();

    assert!(!query.matches(&assertion));
}

#[test]
fn test_query_rejects_wrong_lifecycle() {
    let assertion = create_test_assertion("Tesla", "has_revenue", LifecycleStage::Proposed);

    let query = Query::builder().subject("Tesla").lifecycle(LifecycleStage::Approved).build();

    assert!(!query.matches(&assertion));
}

#[test]
fn test_empty_query_matches_all() {
    let assertion = create_test_assertion("Tesla", "has_revenue", LifecycleStage::Proposed);

    let query = Query::new();

    assert!(query.matches(&assertion));
}

#[test]
fn test_query_result_empty() {
    let result = QueryResult::empty();
    assert!(result.assertions.is_empty());
    assert_eq!(result.total_count, 0);
    assert!(!result.has_more);
}

// ========================================================================
// Visual Hash Query Tests
// ========================================================================

#[test]
fn test_hamming_distance_zero() {
    let a: PHash = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
    let b: PHash = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
    assert_eq!(hamming_distance(&a, &b), 0);
}

#[test]
fn test_hamming_distance_max() {
    // All zeros vs all ones = 64 bits different
    let a: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let b: PHash = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
    assert_eq!(hamming_distance(&a, &b), 64);
}

#[test]
fn test_hamming_distance_partial() {
    // 0x08 = 0000 1000, 0x0F = 0000 1111
    // XOR  = 0000 0111 = 3 bits different
    let a: PHash = [0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let b: PHash = [0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    assert_eq!(hamming_distance(&a, &b), 3);
}

#[test]
fn test_hamming_distance_single_bit() {
    let a: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let b: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
    assert_eq!(hamming_distance(&a, &b), 1);
}

#[test]
fn test_parse_hex_phash_valid() {
    let result = parse_hex_phash("a3f2b1c4d5e6f708");
    assert!(result.is_some());
    let hash = result.unwrap();
    assert_eq!(hash, [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08]);
}

#[test]
fn test_parse_hex_phash_invalid_length() {
    // Too short
    assert!(parse_hex_phash("a3f2b1c4d5e6f7").is_none());
    // Too long
    assert!(parse_hex_phash("a3f2b1c4d5e6f70800").is_none());
    // Empty
    assert!(parse_hex_phash("").is_none());
}

#[test]
fn test_parse_hex_phash_invalid_chars() {
    // Contains 'g' which is not valid hex
    assert!(parse_hex_phash("a3f2b1c4d5e6f70g").is_none());
}

#[test]
fn test_parse_hex_phash_case_insensitive() {
    let lower = parse_hex_phash("a3f2b1c4d5e6f708");
    let upper = parse_hex_phash("A3F2B1C4D5E6F708");
    let mixed = parse_hex_phash("A3f2B1c4D5e6F708");

    assert!(lower.is_some());
    assert_eq!(lower, upper);
    assert_eq!(lower, mixed);
}

#[test]
fn test_visual_near_exact_match() {
    let visual_hash: PHash = [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Exact match with threshold 0
    let query = Query::builder().visual_near("a3f2b1c4d5e6f708", 0).build();

    assert!(query.matches(&assertion));
}

#[test]
fn test_visual_near_within_threshold() {
    // Hash with 3 bits different in first byte
    let visual_hash: PHash = [0xa0, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Query for a3... but assertion has a0...
    // 0xa3 = 1010 0011, 0xa0 = 1010 0000
    // XOR  = 0000 0011 = 2 bits different
    let query = Query::builder()
        .visual_near("a3f2b1c4d5e6f708", 5) // threshold 5, distance 2
        .build();

    assert!(query.matches(&assertion));
}

#[test]
fn test_visual_near_exceeds_threshold() {
    // Hash with many bits different
    let visual_hash: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Query for a3f2... (many bits set) vs 0000... = large distance
    // a3f2b1c4d5e6f708 = many 1 bits, distance will be >> 5
    let query = Query::builder().visual_near("a3f2b1c4d5e6f708", 5).build();

    assert!(!query.matches(&assertion));
}

#[test]
fn test_visual_near_skips_assertions_without_hash() {
    // Assertion without visual_hash
    let assertion = AssertionBuilder::new().subject("test").build();

    assert!(assertion.visual_hash.is_none());

    let query = Query::builder().visual_near("a3f2b1c4d5e6f708", 8).build();

    // Should not match because assertion has no visual_hash
    assert!(!query.matches(&assertion));
}

#[test]
fn test_visual_near_invalid_hex() {
    let visual_hash: PHash = [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Invalid hex string in query
    let query = Query::builder().visual_near("not_valid_hex!!!", 8).build();

    // Should not match due to invalid hex
    assert!(!query.matches(&assertion));
}

#[test]
fn test_visual_near_wrong_length_hex() {
    let visual_hash: PHash = [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Hex string too short
    let query = Query::builder().visual_near("a3f2b1c4", 8).build();

    assert!(!query.matches(&assertion));
}

#[test]
fn test_visual_near_combines_with_subject() {
    let visual_hash: PHash = [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("Tesla").visual_hash(visual_hash).build();

    // Both filters must pass (AND semantics)
    let query = Query::builder().subject("Tesla").visual_near("a3f2b1c4d5e6f708", 0).build();
    assert!(query.matches(&assertion));

    // Wrong subject, right visual
    let query = Query::builder().subject("Apple").visual_near("a3f2b1c4d5e6f708", 0).build();
    assert!(!query.matches(&assertion));

    // Right subject, wrong visual (exceeds threshold)
    let query = Query::builder().subject("Tesla").visual_near("0000000000000000", 5).build();
    assert!(!query.matches(&assertion));
}

#[test]
fn test_visual_near_default_threshold() {
    let visual_hash: PHash = [0xa3, 0xf2, 0xb1, 0xc4, 0xd5, 0xe6, 0xf7, 0x08];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Query without explicit threshold - should use default of 8
    let mut query = Query::new();
    query.visual_near = Some("a3f2b1c4d5e6f708".to_string());
    // No threshold set, should default to 8

    assert!(query.matches(&assertion)); // Exact match, distance 0 < 8
}

#[test]
fn test_query_builder_visual_near() {
    let query = Query::builder().subject("Tesla").visual_near("a3f2b1c4d5e6f708", 10).build();

    assert_eq!(query.subject, Some("Tesla".to_string()));
    assert_eq!(query.visual_near, Some("a3f2b1c4d5e6f708".to_string()));
    assert_eq!(query.visual_threshold, Some(10));
}

#[test]
fn test_visual_near_max_threshold_matches_any() {
    // Max threshold of 64 should match any visual_hash (max hamming distance is 64)
    let visual_hash: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // Query with totally different hash (all 1s vs all 0s = 64 bits different)
    // but max threshold of 64 should still match
    let query = Query::builder().visual_near("ffffffffffffffff", 64).build();

    assert!(query.matches(&assertion));
}

#[test]
fn test_visual_near_threshold_63_rejects_max_distance() {
    // Threshold of 63 should reject assertions with max distance (64)
    let visual_hash: PHash = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let assertion = AssertionBuilder::new().subject("test").visual_hash(visual_hash).build();

    // All 1s vs all 0s = 64 bits different, threshold 63 should reject
    let query = Query::builder().visual_near("ffffffffffffffff", 63).build();

    assert!(!query.matches(&assertion));
}

// ========================================================================
// Time-Travel (as_of) Query Tests
// ========================================================================

#[test]
fn test_as_of_excludes_future_assertions() {
    let mut a1 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a1.timestamp = 1000;

    let mut a2 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a2.timestamp = 2000;

    let mut a3 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a3.timestamp = 3000;

    let query = Query::builder().subject("Tesla").as_of(2500).build();

    assert!(query.matches(&a1)); // 1000 <= 2500
    assert!(query.matches(&a2)); // 2000 <= 2500
    assert!(!query.matches(&a3)); // 3000 > 2500
}

#[test]
fn test_as_of_none_matches_all_timestamps() {
    let mut a1 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a1.timestamp = 1000;

    let mut a2 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a2.timestamp = u64::MAX; // Far future

    // Query without as_of should match all timestamps
    let query = Query::builder().subject("Tesla").build();

    assert!(query.matches(&a1));
    assert!(query.matches(&a2));
}

#[test]
fn test_as_of_zero_matches_only_zero_timestamp() {
    let mut a1 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a1.timestamp = 0;

    let mut a2 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a2.timestamp = 1;

    let query = Query::builder().subject("Tesla").as_of(0).build();

    assert!(query.matches(&a1)); // 0 <= 0
    assert!(!query.matches(&a2)); // 1 > 0
}

#[test]
fn test_as_of_combines_with_other_filters() {
    let mut a1 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a1.timestamp = 1000;

    let mut a2 = create_test_assertion("Apple", "revenue", LifecycleStage::Approved);
    a2.timestamp = 1000;

    let mut a3 = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    a3.timestamp = 3000; // After as_of

    // Query with subject filter AND as_of
    let query = Query::builder().subject("Tesla").as_of(2000).build();

    assert!(query.matches(&a1)); // Tesla, 1000 <= 2000
    assert!(!query.matches(&a2)); // Apple (wrong subject)
    assert!(!query.matches(&a3)); // Tesla, but 3000 > 2000
}

#[test]
fn test_query_builder_as_of() {
    let query = Query::builder().subject("Tesla").predicate("revenue").as_of(1704067200).build();

    assert_eq!(query.subject, Some("Tesla".to_string()));
    assert_eq!(query.predicate, Some("revenue".to_string()));
    assert_eq!(query.as_of, Some(1704067200));
}

// ========================================================================
// Conflict Score Filter Tests
// ========================================================================

#[test]
fn test_query_builder_min_conflict_score() {
    let query = Query::builder()
        .subject("Semaglutide")
        .predicate("muscle_effect")
        .min_conflict_score(0.7)
        .build();

    assert_eq!(query.subject, Some("Semaglutide".to_string()));
    assert_eq!(query.predicate, Some("muscle_effect".to_string()));
    assert_eq!(query.min_conflict_score, Some(0.7));
}

#[test]
fn test_query_builder_max_conflict_score() {
    let query = Query::builder()
        .subject("Semaglutide")
        .predicate("muscle_effect")
        .max_conflict_score(0.2)
        .build();

    assert_eq!(query.subject, Some("Semaglutide".to_string()));
    assert_eq!(query.predicate, Some("muscle_effect".to_string()));
    assert_eq!(query.max_conflict_score, Some(0.2));
}

#[test]
fn test_query_builder_both_conflict_scores() {
    let query = Query::builder()
        .subject("Semaglutide")
        .predicate("muscle_effect")
        .min_conflict_score(0.3)
        .max_conflict_score(0.7)
        .build();

    assert_eq!(query.min_conflict_score, Some(0.3));
    assert_eq!(query.max_conflict_score, Some(0.7));
}

#[test]
fn test_query_default_no_conflict_filters() {
    let query = Query::new();

    assert_eq!(query.min_conflict_score, None);
    assert_eq!(query.max_conflict_score, None);
}
