//! Specialized storage for assertion indexes.
//!
//! Following the architecture spec, we maintain multiple indexes for efficient queries:
//! - `S:{subject}` - Subject-only index (list of assertion hashes)
//! - `SP:{subject}:{predicate}` - Compound subject+predicate index (list of assertion hashes)
//!
//! # Design Philosophy
//!
//! This module follows the "Deep Module" principle from Philosophy of Software Design:
//! - Simple interface hiding complex index management
//! - O(1) lookups when both subject and predicate are known
//! - Efficient append-only semantics (no overwrites)
//!
//! # Storage Layout
//!
//! | Key Pattern | Value | Purpose |
//! |-------------|-------|---------|
//! | `S:{subject}` | Vec<Hash> (rkyv) | Subject adjacency index |
//! | `SP:{subject}:{predicate}` | Vec<Hash> (rkyv) | Compound index for fast lookups |
//!
//! All operations are append-only and content-addressed.

use crate::error::Result;
use crate::key_codec;
use crate::traits::KVStore;
use async_trait::async_trait;
use stemedb_core::types::Hash;
use tracing::{debug, instrument};

/// Specialized storage trait for assertion index operations.
///
/// This trait provides index-specific operations on top of a generic KVStore,
/// enabling efficient assertion lookup by subject and/or predicate.
///
/// # Example
///
/// ```ignore
/// let index_store = GenericIndexStore::new(kv_store);
///
/// // Add assertion to indexes
/// index_store.add_to_indexes("Tesla", "revenue", assertion_hash).await?;
///
/// // Lookup by subject only
/// let hashes = index_store.get_by_subject("Tesla").await?;
///
/// // Lookup by subject and predicate (faster)
/// let hashes = index_store.get_by_subject_predicate("Tesla", "revenue").await?;
/// ```
#[async_trait]
pub trait IndexStore: Send + Sync {
    /// Add an assertion hash to both subject and compound indexes.
    ///
    /// This operation:
    /// 1. Appends hash to `S:{subject}` index
    /// 2. Appends hash to `SP:{subject}:{predicate}` index
    ///
    /// # Arguments
    /// * `subject` - The assertion subject
    /// * `predicate` - The assertion predicate
    /// * `assertion_hash` - The content-addressed hash of the assertion
    async fn add_to_indexes(
        &self,
        subject: &str,
        predicate: &str,
        assertion_hash: &Hash,
    ) -> Result<()>;

    /// Get all assertion hashes for a given subject.
    ///
    /// Reads from the `S:{subject}` index.
    ///
    /// # Returns
    /// Vector of assertion hashes, empty if no assertions exist.
    async fn get_by_subject(&self, subject: &str) -> Result<Vec<Hash>>;

    /// Get all assertion hashes for a given subject and predicate.
    ///
    /// Reads from the `SP:{subject}:{predicate}` index.
    /// This is more efficient than `get_by_subject` when the predicate is known.
    ///
    /// # Returns
    /// Vector of assertion hashes, empty if no assertions exist.
    async fn get_by_subject_predicate(&self, subject: &str, predicate: &str) -> Result<Vec<Hash>>;

    /// Check if any assertions exist for a given subject.
    async fn has_subject(&self, subject: &str) -> Result<bool>;

    /// Check if any assertions exist for a given subject and predicate.
    async fn has_subject_predicate(&self, subject: &str, predicate: &str) -> Result<bool>;

    /// Add an assertion to the source reverse index.
    ///
    /// This enables cascade impact analysis by mapping source hashes
    /// to the assertions that cite them.
    ///
    /// # Arguments
    /// * `source_hash` - The hash of the source being cited
    /// * `assertion_hash` - The hash of the assertion citing this source
    async fn add_to_source_index(&self, source_hash: &Hash, assertion_hash: &Hash) -> Result<()>;

    /// Get all assertion hashes citing a given source.
    ///
    /// Reads from the `\x00SRC:{source_hash}` index.
    ///
    /// # Returns
    /// Vector of assertion hashes, empty if no assertions cite this source.
    async fn get_by_source(&self, source_hash: &Hash) -> Result<Vec<Hash>>;

    /// Count assertions citing a source (efficient for impact preview).
    ///
    /// # Returns
    /// Number of assertions citing this source.
    async fn count_by_source(&self, source_hash: &Hash) -> Result<usize>;
}

/// IndexStore implementation backed by a generic KVStore.
///
/// This implementation stores indexes as rkyv-serialized Vec<Hash> for efficient
/// zero-copy deserialization.
pub struct GenericIndexStore<S> {
    store: S,
}

impl<S: KVStore> GenericIndexStore<S> {
    /// Create a new IndexStore backed by the given KVStore.
    pub fn new(store: S) -> Self {
        Self { store }
    }

    /// Serialize a hash list using the canonical serde helpers.
    fn serialize_hash_list(hashes: &Vec<Hash>) -> Result<Vec<u8>> {
        crate::serde_helpers::serialize(hashes)
    }

    /// Deserialize a hash list using the canonical serde helpers.
    fn deserialize_hash_list(data: &[u8]) -> Result<Vec<Hash>> {
        crate::serde_helpers::deserialize(data)
    }

    /// Append a hash to an index (read-modify-write).
    ///
    /// This is not lock-free, but is safe for the current use case where
    /// ingestion is single-threaded per assertion.
    async fn append_to_index(&self, key: Vec<u8>, new_hash: &Hash) -> Result<()> {
        let mut hashes = match self.store.get(&key).await? {
            Some(data) => Self::deserialize_hash_list(&data)?,
            None => Vec::new(),
        };

        // Append only if not already present (idempotency)
        if !hashes.contains(new_hash) {
            hashes.push(*new_hash);
            let serialized = Self::serialize_hash_list(&hashes)?;
            self.store.put(&key, &serialized).await?;
        }

        Ok(())
    }
}

#[async_trait]
impl<S: KVStore + 'static> IndexStore for GenericIndexStore<S> {
    #[instrument(skip(self, assertion_hash), fields(subject = %subject, predicate = %predicate, hash = %hex::encode(assertion_hash)))]
    async fn add_to_indexes(
        &self,
        subject: &str,
        predicate: &str,
        assertion_hash: &Hash,
    ) -> Result<()> {
        // Update subject index
        let subject_key = key_codec::subject_index_key(subject);
        self.append_to_index(subject_key, assertion_hash).await?;

        // Update compound index
        let sp_key = key_codec::subject_predicate_key(subject, predicate);
        self.append_to_index(sp_key, assertion_hash).await?;

        // Update subjects discovery index
        let subjects_index_key = key_codec::subjects_index_key(subject);
        self.store.put(&subjects_index_key, &[]).await?;

        debug!(
            subject,
            predicate,
            hash = %hex::encode(assertion_hash),
            "Added to indexes"
        );

        Ok(())
    }

    #[instrument(skip(self), fields(subject = %subject))]
    async fn get_by_subject(&self, subject: &str) -> Result<Vec<Hash>> {
        let key = key_codec::subject_index_key(subject);
        match self.store.get(&key).await? {
            Some(data) => {
                let hashes = Self::deserialize_hash_list(&data)?;
                debug!(subject, count = hashes.len(), "Retrieved by subject");
                Ok(hashes)
            }
            None => {
                debug!(subject, "No subject index found");
                Ok(Vec::new())
            }
        }
    }

    #[instrument(skip(self), fields(subject = %subject, predicate = %predicate))]
    async fn get_by_subject_predicate(&self, subject: &str, predicate: &str) -> Result<Vec<Hash>> {
        let key = key_codec::subject_predicate_key(subject, predicate);
        match self.store.get(&key).await? {
            Some(data) => {
                let hashes = Self::deserialize_hash_list(&data)?;
                debug!(subject, predicate, count = hashes.len(), "Retrieved by subject+predicate");
                Ok(hashes)
            }
            None => {
                debug!(subject, predicate, "No compound index found");
                Ok(Vec::new())
            }
        }
    }

    #[instrument(skip(self), fields(subject = %subject))]
    async fn has_subject(&self, subject: &str) -> Result<bool> {
        let key = key_codec::subject_index_key(subject);
        Ok(self.store.get(&key).await?.is_some())
    }

    #[instrument(skip(self), fields(subject = %subject, predicate = %predicate))]
    async fn has_subject_predicate(&self, subject: &str, predicate: &str) -> Result<bool> {
        let key = key_codec::subject_predicate_key(subject, predicate);
        Ok(self.store.get(&key).await?.is_some())
    }

    #[instrument(skip(self, assertion_hash), fields(source_hash = %hex::encode(source_hash), assertion_hash = %hex::encode(assertion_hash)))]
    async fn add_to_source_index(&self, source_hash: &Hash, assertion_hash: &Hash) -> Result<()> {
        let source_hex = hex::encode(source_hash);
        let key = key_codec::source_index_key(&source_hex);
        self.append_to_index(key, assertion_hash).await?;

        debug!(
            source_hash = %source_hex,
            assertion_hash = %hex::encode(assertion_hash),
            "Added to source index"
        );

        Ok(())
    }

    #[instrument(skip(self), fields(source_hash = %hex::encode(source_hash)))]
    async fn get_by_source(&self, source_hash: &Hash) -> Result<Vec<Hash>> {
        let source_hex = hex::encode(source_hash);
        let key = key_codec::source_index_key(&source_hex);
        match self.store.get(&key).await? {
            Some(data) => {
                let hashes = Self::deserialize_hash_list(&data)?;
                debug!(source_hash = %source_hex, count = hashes.len(), "Retrieved by source");
                Ok(hashes)
            }
            None => {
                debug!(source_hash = %source_hex, "No source index found");
                Ok(Vec::new())
            }
        }
    }

    #[instrument(skip(self), fields(source_hash = %hex::encode(source_hash)))]
    async fn count_by_source(&self, source_hash: &Hash) -> Result<usize> {
        let hashes = self.get_by_source(source_hash).await?;
        Ok(hashes.len())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::HybridStore;
    use std::sync::Arc;

    #[tokio::test]
    async fn test_add_and_get_by_subject() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let subject = "Tesla";
        let predicate = "revenue";
        let hash1 = [1u8; 32];
        let hash2 = [2u8; 32];

        // Add two assertions with same subject
        index_store.add_to_indexes(subject, predicate, &hash1).await.expect("add 1");
        index_store.add_to_indexes(subject, "profit", &hash2).await.expect("add 2");

        // Should retrieve both by subject
        let hashes = index_store.get_by_subject(subject).await.expect("get");
        assert_eq!(hashes.len(), 2);
        assert!(hashes.contains(&hash1));
        assert!(hashes.contains(&hash2));
    }

    #[tokio::test]
    async fn test_add_and_get_by_subject_predicate() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let subject = "Tesla";
        let predicate = "revenue";
        let hash1 = [1u8; 32];
        let hash2 = [2u8; 32];
        let hash3 = [3u8; 32];

        // Add assertions
        index_store.add_to_indexes(subject, predicate, &hash1).await.expect("add 1");
        index_store.add_to_indexes(subject, predicate, &hash2).await.expect("add 2");
        index_store.add_to_indexes(subject, "profit", &hash3).await.expect("add 3");

        // Compound index should only return hashes for exact subject+predicate match
        let hashes = index_store.get_by_subject_predicate(subject, predicate).await.expect("get");
        assert_eq!(hashes.len(), 2);
        assert!(hashes.contains(&hash1));
        assert!(hashes.contains(&hash2));
        assert!(!hashes.contains(&hash3));
    }

    #[tokio::test]
    async fn test_idempotent_insert() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let subject = "Tesla";
        let predicate = "revenue";
        let hash = [1u8; 32];

        // Add same hash multiple times
        index_store.add_to_indexes(subject, predicate, &hash).await.expect("add 1");
        index_store.add_to_indexes(subject, predicate, &hash).await.expect("add 2");
        index_store.add_to_indexes(subject, predicate, &hash).await.expect("add 3");

        // Should only appear once
        let hashes = index_store.get_by_subject_predicate(subject, predicate).await.expect("get");
        assert_eq!(hashes.len(), 1);
        assert_eq!(hashes[0], hash);
    }

    #[tokio::test]
    async fn test_empty_index_returns_empty_vec() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let hashes = index_store.get_by_subject("Nonexistent").await.expect("get");
        assert!(hashes.is_empty(), "Should return empty vec, not error");

        let hashes =
            index_store.get_by_subject_predicate("Nonexistent", "property").await.expect("get");
        assert!(hashes.is_empty(), "Should return empty vec, not error");
    }

    #[tokio::test]
    async fn test_has_subject() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let subject = "Tesla";
        let predicate = "revenue";
        let hash = [1u8; 32];

        // Initially false
        assert!(!index_store.has_subject(subject).await.expect("has"));

        // Add assertion
        index_store.add_to_indexes(subject, predicate, &hash).await.expect("add");

        // Now true
        assert!(index_store.has_subject(subject).await.expect("has"));
    }

    #[tokio::test]
    async fn test_has_subject_predicate() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let subject = "Tesla";
        let predicate = "revenue";
        let hash = [1u8; 32];

        // Initially false
        assert!(!index_store.has_subject_predicate(subject, predicate).await.expect("has"));

        // Add assertion
        index_store.add_to_indexes(subject, predicate, &hash).await.expect("add");

        // Now true for exact match
        assert!(index_store.has_subject_predicate(subject, predicate).await.expect("has"));

        // Still false for different predicate
        assert!(!index_store.has_subject_predicate(subject, "profit").await.expect("has"));
    }

    #[tokio::test]
    async fn test_multiple_subjects_isolated() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let hash1 = [1u8; 32];
        let hash2 = [2u8; 32];

        index_store.add_to_indexes("Tesla", "revenue", &hash1).await.expect("add");
        index_store.add_to_indexes("Apple", "revenue", &hash2).await.expect("add");

        // Each subject should only see its own assertions
        let tesla_hashes = index_store.get_by_subject("Tesla").await.expect("get");
        assert_eq!(tesla_hashes.len(), 1);
        assert_eq!(tesla_hashes[0], hash1);

        let apple_hashes = index_store.get_by_subject("Apple").await.expect("get");
        assert_eq!(apple_hashes.len(), 1);
        assert_eq!(apple_hashes[0], hash2);
    }

    #[tokio::test]
    async fn test_serialization_round_trip() {
        // Property test: ensure serialization is lossless
        let hashes = vec![[1u8; 32], [2u8; 32], [3u8; 32]];

        let serialized =
            GenericIndexStore::<HybridStore>::serialize_hash_list(&hashes).expect("serialize");
        let deserialized = GenericIndexStore::<HybridStore>::deserialize_hash_list(&serialized)
            .expect("deserialize");

        assert_eq!(hashes, deserialized);
    }

    #[tokio::test]
    async fn test_source_index_add_and_get() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let source_hash = [10u8; 32];
        let assertion_hash1 = [1u8; 32];
        let assertion_hash2 = [2u8; 32];

        // Add two assertions citing the same source
        index_store.add_to_source_index(&source_hash, &assertion_hash1).await.expect("add 1");
        index_store.add_to_source_index(&source_hash, &assertion_hash2).await.expect("add 2");

        // Should retrieve both
        let hashes = index_store.get_by_source(&source_hash).await.expect("get");
        assert_eq!(hashes.len(), 2);
        assert!(hashes.contains(&assertion_hash1));
        assert!(hashes.contains(&assertion_hash2));
    }

    #[tokio::test]
    async fn test_source_index_multiple_sources() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let source_hash1 = [10u8; 32];
        let source_hash2 = [11u8; 32];
        let assertion_hash1 = [1u8; 32];
        let assertion_hash2 = [2u8; 32];

        // Assertion 1 cites source 1
        index_store.add_to_source_index(&source_hash1, &assertion_hash1).await.expect("add");
        // Assertion 2 cites source 2
        index_store.add_to_source_index(&source_hash2, &assertion_hash2).await.expect("add");

        // Each source should have its own assertions
        let hashes1 = index_store.get_by_source(&source_hash1).await.expect("get");
        assert_eq!(hashes1.len(), 1);
        assert_eq!(hashes1[0], assertion_hash1);

        let hashes2 = index_store.get_by_source(&source_hash2).await.expect("get");
        assert_eq!(hashes2.len(), 1);
        assert_eq!(hashes2[0], assertion_hash2);
    }

    #[tokio::test]
    async fn test_source_index_count() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let source_hash = [20u8; 32];

        // Empty source should return 0
        assert_eq!(index_store.count_by_source(&source_hash).await.expect("count"), 0);

        // Add 3 assertions
        for i in 1u8..=3 {
            let assertion_hash = [i; 32];
            index_store.add_to_source_index(&source_hash, &assertion_hash).await.expect("add");
        }

        assert_eq!(index_store.count_by_source(&source_hash).await.expect("count"), 3);
    }

    #[tokio::test]
    async fn test_source_index_idempotent() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let index_store = GenericIndexStore::new(store);

        let source_hash = [30u8; 32];
        let assertion_hash = [1u8; 32];

        // Add same assertion multiple times
        index_store.add_to_source_index(&source_hash, &assertion_hash).await.expect("add 1");
        index_store.add_to_source_index(&source_hash, &assertion_hash).await.expect("add 2");
        index_store.add_to_source_index(&source_hash, &assertion_hash).await.expect("add 3");

        // Should only appear once
        let hashes = index_store.get_by_source(&source_hash).await.expect("get");
        assert_eq!(hashes.len(), 1);
    }
}
