//! Vector similarity index for semantic k-NN queries.
//!
//! This module provides HNSW-based vector indexing for efficient nearest-neighbor
//! search over assertion embeddings. The index enables O(log N) semantic queries
//! instead of O(N) brute-force scans.
//!
//! # Architecture
//!
//! The vector index stores mappings from assertion hashes to embedding vectors.
//! When a query specifies `vector_near`, the QueryEngine uses this index for
//! candidate retrieval instead of the standard SP/S indexes.
//!
//! # Storage Layout
//!
//! | Key Pattern | Value | Purpose |
//! |-------------|-------|---------|
//! | `VI:graph` | HNSW graph (JSON) | The navigable graph structure |
//! | `VI:meta` | Metadata | Dimension, ef_construction, etc. |
//! | `VI:id:{idx}` | Hash | Maps internal HNSW ID to assertion hash |
//!
//! # Example
//!
//! ```ignore
//! use stemedb_storage::{HnswVectorIndex, VectorIndex};
//!
//! let index = HnswVectorIndex::new(128); // 128-dimensional vectors
//!
//! // Insert assertion's embedding
//! index.insert(&assertion_hash, &embedding_vec)?;
//!
//! // Find 5 nearest neighbors
//! let neighbors = index.search(&query_vec, 5)?;
//! for (hash, distance) in neighbors {
//!     println!("Hash: {}, Distance: {}", hex::encode(hash), distance);
//! }
//! ```

mod hnsw;
mod hot_cold;
pub mod persistence;
mod persistent;

use crate::error::Result;
use std::sync::Arc;
use stemedb_core::types::Hash;

pub use hnsw::HnswVectorIndex;
pub use hot_cold::merge_search_results;
pub use persistent::{PersistentVectorIndex, PersistentVectorIndexConfig};

/// Trait for vector similarity indexes.
///
/// Implementations provide O(log N) approximate nearest-neighbor search
/// over high-dimensional embedding vectors.
pub trait VectorIndex: Send + Sync {
    /// Insert a vector associated with an assertion hash.
    ///
    /// # Arguments
    /// * `hash` - The content-addressed hash of the assertion
    /// * `vector` - The embedding vector (must match index dimension)
    ///
    /// # Errors
    /// Returns error if vector dimension doesn't match index dimension.
    fn insert(&self, hash: &Hash, vector: &[f32]) -> Result<()>;

    /// Search for k nearest neighbors to the query vector.
    ///
    /// # Arguments
    /// * `query` - The query embedding vector
    /// * `k` - Number of nearest neighbors to return
    ///
    /// # Returns
    /// Vector of (hash, distance) pairs, sorted by distance ascending.
    /// Distance is L2 (Euclidean) distance.
    fn search(&self, query: &[f32], k: usize) -> Result<Vec<(Hash, f32)>>;

    /// Get the dimension this index was created for.
    fn dimension(&self) -> usize;

    /// Get the number of vectors in the index.
    fn len(&self) -> usize;

    /// Check if the index is empty.
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

// Implement for Arc<T> to enable sharing
impl<T: VectorIndex> VectorIndex for Arc<T> {
    fn insert(&self, hash: &Hash, vector: &[f32]) -> Result<()> {
        (**self).insert(hash, vector)
    }

    fn search(&self, query: &[f32], k: usize) -> Result<Vec<(Hash, f32)>> {
        (**self).search(query, k)
    }

    fn dimension(&self) -> usize {
        (**self).dimension()
    }

    fn len(&self) -> usize {
        (**self).len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn random_vector(dim: usize, seed: u64) -> Vec<f32> {
        // Simple deterministic pseudo-random generator for tests
        let mut state = seed;
        (0..dim)
            .map(|_| {
                state = state.wrapping_mul(1103515245).wrapping_add(12345);
                ((state >> 16) as f32 / 32768.0) - 1.0 // Range [-1, 1]
            })
            .collect()
    }

    #[test]
    fn test_create_index() {
        let index = HnswVectorIndex::new(128);
        assert_eq!(index.dimension(), 128);
        assert!(index.is_empty());
        assert_eq!(index.len(), 0);
    }

    #[test]
    fn test_insert_and_search() {
        let index = HnswVectorIndex::new(4);

        let hash1: Hash = [1u8; 32];
        let hash2: Hash = [2u8; 32];
        let hash3: Hash = [3u8; 32];

        // Insert three vectors
        index.insert(&hash1, &[1.0, 0.0, 0.0, 0.0]).expect("insert 1");
        index.insert(&hash2, &[0.0, 1.0, 0.0, 0.0]).expect("insert 2");
        index.insert(&hash3, &[0.9, 0.1, 0.0, 0.0]).expect("insert 3"); // Close to hash1

        assert_eq!(index.len(), 3);

        // Search for vectors near hash1's vector
        let results = index.search(&[1.0, 0.0, 0.0, 0.0], 2).expect("search");

        assert_eq!(results.len(), 2);
        // Closest should be exact match (hash1)
        assert_eq!(results[0].0, hash1);
        assert!(results[0].1 < 0.01); // Very close to 0 distance

        // Second result should be one of the other vectors
        // Note: HNSW is approximate, so we just verify it's a valid result
        assert!(results[1].0 == hash2 || results[1].0 == hash3);
    }

    #[test]
    fn test_dimension_mismatch() {
        let index = HnswVectorIndex::new(4);
        let hash: Hash = [1u8; 32];

        // Wrong dimension on insert
        let result = index.insert(&hash, &[1.0, 0.0, 0.0]); // 3 instead of 4
        assert!(result.is_err());

        // Insert correct dimension
        index.insert(&hash, &[1.0, 0.0, 0.0, 0.0]).expect("insert");

        // Wrong dimension on search
        let result = index.search(&[1.0, 0.0, 0.0], 1);
        assert!(result.is_err());
    }

    #[test]
    fn test_idempotent_insert() {
        let index = HnswVectorIndex::new(4);
        let hash: Hash = [1u8; 32];

        index.insert(&hash, &[1.0, 0.0, 0.0, 0.0]).expect("insert 1");
        index.insert(&hash, &[1.0, 0.0, 0.0, 0.0]).expect("insert 2"); // Same hash
        index.insert(&hash, &[0.0, 1.0, 0.0, 0.0]).expect("insert 3"); // Same hash, different vector (ignored)

        // Should still have only one entry
        assert_eq!(index.len(), 1);
    }

    #[test]
    fn test_search_empty_index() {
        let index = HnswVectorIndex::new(4);
        let results = index.search(&[1.0, 0.0, 0.0, 0.0], 5).expect("search");
        assert!(results.is_empty());
    }

    #[test]
    fn test_search_k_zero() {
        let index = HnswVectorIndex::new(4);
        let hash: Hash = [1u8; 32];
        index.insert(&hash, &[1.0, 0.0, 0.0, 0.0]).expect("insert");

        let results = index.search(&[1.0, 0.0, 0.0, 0.0], 0).expect("search");
        assert!(results.is_empty());
    }

    #[test]
    fn test_nan_rejection() {
        let index = HnswVectorIndex::new(4);
        let hash: Hash = [1u8; 32];

        // NaN in insert
        let result = index.insert(&hash, &[1.0, f32::NAN, 0.0, 0.0]);
        assert!(result.is_err());

        // Insert valid vector
        index.insert(&hash, &[1.0, 0.0, 0.0, 0.0]).expect("insert");

        // NaN in search
        let result = index.search(&[1.0, f32::NAN, 0.0, 0.0], 1);
        assert!(result.is_err());
    }

    #[test]
    fn test_infinite_rejection() {
        let index = HnswVectorIndex::new(4);
        let hash: Hash = [1u8; 32];

        // Infinite in insert
        let result = index.insert(&hash, &[1.0, f32::INFINITY, 0.0, 0.0]);
        assert!(result.is_err());

        let result = index.insert(&hash, &[1.0, f32::NEG_INFINITY, 0.0, 0.0]);
        assert!(result.is_err());
    }

    #[test]
    fn test_contains() {
        let index = HnswVectorIndex::new(4);
        let hash1: Hash = [1u8; 32];
        let hash2: Hash = [2u8; 32];

        assert!(!index.contains(&hash1));

        index.insert(&hash1, &[1.0, 0.0, 0.0, 0.0]).expect("insert");

        assert!(index.contains(&hash1));
        assert!(!index.contains(&hash2));
    }

    #[test]
    fn test_larger_scale() {
        let dim = 128;
        let index = HnswVectorIndex::new(dim);

        // Insert 100 vectors
        for i in 0..100u64 {
            let mut hash: Hash = [0u8; 32];
            hash[0..8].copy_from_slice(&i.to_le_bytes());
            let vector = random_vector(dim, i);
            index.insert(&hash, &vector).expect("insert");
        }

        assert_eq!(index.len(), 100);

        // Search for nearest to vector 50
        let query = random_vector(dim, 50);
        let results = index.search(&query, 5).expect("search");

        assert_eq!(results.len(), 5);
        // The exact match should be first (or very close)
        let mut expected_hash: Hash = [0u8; 32];
        expected_hash[0..8].copy_from_slice(&50u64.to_le_bytes());
        assert_eq!(results[0].0, expected_hash);
        assert!(results[0].1 < 0.01); // Very close to 0
    }

    #[test]
    fn test_custom_params() {
        let index = HnswVectorIndex::with_params(64, 32, 400);
        assert_eq!(index.dimension(), 64);
        assert!(index.is_empty());
    }

    #[test]
    #[should_panic(expected = "Vector dimension must be positive")]
    fn test_zero_dimension_panics() {
        let _ = HnswVectorIndex::new(0);
    }
}
