//! Generic SourceRegistry implementation backed by a KVStore.

use async_trait::async_trait;
use stemedb_core::types::{SourceRecord, SourceStatus};
use tracing::{debug, instrument};

use super::SourceRegistry;
use crate::error::{Result, StorageError};
use crate::key_codec;
use crate::serde_helpers::{deserialize, serialize};
use crate::traits::KVStore;

/// Generic SourceRegistry implementation backed by any KVStore.
pub struct GenericSourceRegistry<S> {
    store: S,
}

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

#[async_trait]
impl<S: KVStore + 'static> SourceRegistry for GenericSourceRegistry<S> {
    #[instrument(skip(self, record), fields(hash = %hex::encode(record.hash), label = %record.label, tier = record.tier))]
    async fn register(&self, record: &SourceRecord) -> Result<()> {
        let hash_hex = hex::encode(record.hash);

        // Check if record already exists (for index cleanup)
        let old_record = self.get(&record.hash).await?;

        // Serialize the record
        let serialized = serialize(record)?;

        // Write primary key
        let primary_key = key_codec::source_registry_key(&hash_hex);
        self.store.put(&primary_key, &serialized).await?;

        // Clean up old indexes if updating
        if let Some(old) = &old_record {
            if old.tier != record.tier {
                let old_tier_key = key_codec::source_registry_tier_key(old.tier, &hash_hex);
                self.store.delete(&old_tier_key).await?;
            }
            if old.label.to_lowercase() != record.label.to_lowercase() {
                let old_label_prefix =
                    old.label.to_lowercase().chars().take(10).collect::<String>();
                let old_label_key =
                    key_codec::source_registry_label_key(&old_label_prefix, &hash_hex);
                self.store.delete(&old_label_key).await?;
            }
        }

        // Write tier index (empty value, presence only)
        let tier_key = key_codec::source_registry_tier_key(record.tier, &hash_hex);
        self.store.put(&tier_key, &[]).await?;

        // Write label index (first 10 chars, lowercased)
        let label_prefix = record.label.to_lowercase().chars().take(10).collect::<String>();
        let label_key = key_codec::source_registry_label_key(&label_prefix, &hash_hex);
        self.store.put(&label_key, &[]).await?;

        debug!(
            hash = %hash_hex,
            label = %record.label,
            tier = record.tier,
            status = ?record.status,
            "Registered source"
        );

        Ok(())
    }

    #[instrument(skip(self), fields(hash = %hex::encode(hash)))]
    async fn get(&self, hash: &[u8; 32]) -> Result<Option<SourceRecord>> {
        let hash_hex = hex::encode(hash);
        let key = key_codec::source_registry_key(&hash_hex);

        match self.store.get(&key).await? {
            Some(data) => {
                let record: SourceRecord = deserialize(&data)?;
                Ok(Some(record))
            }
            None => Ok(None),
        }
    }

    #[instrument(skip(self), fields(hash = %hex::encode(hash), new_status = ?status))]
    async fn update_status(
        &self,
        hash: &[u8; 32],
        status: SourceStatus,
        timestamp: u64,
    ) -> Result<()> {
        let mut record = self.get(hash).await?.ok_or_else(|| {
            StorageError::NotFound(format!("Source not found: {}", hex::encode(hash)))
        })?;

        record.status = status;
        record.updated_at = timestamp;

        // Re-register to update
        self.register(&record).await
    }

    #[instrument(skip(self), fields(hash = %hex::encode(hash), new_tier = tier))]
    async fn update_tier(&self, hash: &[u8; 32], tier: u8, timestamp: u64) -> Result<()> {
        let mut record = self.get(hash).await?.ok_or_else(|| {
            StorageError::NotFound(format!("Source not found: {}", hex::encode(hash)))
        })?;

        record.tier = tier.min(5);
        record.updated_at = timestamp;

        // Re-register to update (this handles index cleanup)
        self.register(&record).await
    }

    #[instrument(skip(self), fields(tier = tier, limit = limit))]
    async fn list_by_tier(&self, tier: u8, limit: usize) -> Result<Vec<SourceRecord>> {
        let prefix = key_codec::source_registry_tier_prefix(tier);
        let entries = self.store.scan_prefix(&prefix).await?;

        let mut records = Vec::with_capacity(limit.min(entries.len()));
        for (key, _) in entries.into_iter().take(limit) {
            // Extract hash from key: \x00SREG_TIER:{tier}:{hash_hex}
            if let Some(hash_hex) = key_codec::extract_source_registry_tier_hash(&key) {
                if let Ok(hash_bytes) = hex::decode(&hash_hex) {
                    if hash_bytes.len() == 32 {
                        let mut hash = [0u8; 32];
                        hash.copy_from_slice(&hash_bytes);
                        if let Ok(Some(record)) = self.get(&hash).await {
                            records.push(record);
                        }
                    }
                }
            }
        }

        Ok(records)
    }

    #[instrument(skip(self), fields(query = query, limit = limit))]
    async fn search(&self, query: &str, limit: usize) -> Result<Vec<SourceRecord>> {
        let query_lower = query.to_lowercase();
        let prefix = key_codec::source_registry_label_prefix(&query_lower);
        let entries = self.store.scan_prefix(&prefix).await?;

        let mut records = Vec::with_capacity(limit.min(entries.len()));
        for (key, _) in entries.into_iter().take(limit) {
            // Extract hash from key: \x00SREG_LABEL:{label_prefix}:{hash_hex}
            if let Some(hash_hex) = key_codec::extract_source_registry_label_hash(&key) {
                if let Ok(hash_bytes) = hex::decode(&hash_hex) {
                    if hash_bytes.len() == 32 {
                        let mut hash = [0u8; 32];
                        hash.copy_from_slice(&hash_bytes);
                        if let Ok(Some(record)) = self.get(&hash).await {
                            // Double-check the label actually matches (in case of index stale)
                            if record.label.to_lowercase().starts_with(&query_lower) {
                                records.push(record);
                            }
                        }
                    }
                }
            }
        }

        Ok(records)
    }

    #[instrument(skip(self), fields(hash = %hex::encode(hash)))]
    async fn delete(&self, hash: &[u8; 32]) -> Result<bool> {
        let hash_hex = hex::encode(hash);

        // Get the record first to clean up indexes
        let record = match self.get(hash).await? {
            Some(r) => r,
            None => return Ok(false),
        };

        // Delete primary key
        let primary_key = key_codec::source_registry_key(&hash_hex);
        self.store.delete(&primary_key).await?;

        // Delete tier index
        let tier_key = key_codec::source_registry_tier_key(record.tier, &hash_hex);
        self.store.delete(&tier_key).await?;

        // Delete label index
        let label_prefix = record.label.to_lowercase().chars().take(10).collect::<String>();
        let label_key = key_codec::source_registry_label_key(&label_prefix, &hash_hex);
        self.store.delete(&label_key).await?;

        debug!(hash = %hash_hex, label = %record.label, "Deleted source from registry");

        Ok(true)
    }

    #[instrument(skip(self))]
    async fn count(&self) -> Result<usize> {
        let prefix = key_codec::source_registry_scan_prefix();
        let entries = self.store.scan_prefix(&prefix).await?;
        Ok(entries.len())
    }
}

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

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

        let hash = [1u8; 32];
        let record = SourceRecord::new(
            hash,
            "RFC 7519 - JSON Web Token".to_string(),
            Some("https://tools.ietf.org/html/rfc7519".to_string()),
            0,
            1000,
        );

        registry.register(&record).await.expect("register");

        let retrieved = registry.get(&hash).await.expect("get");
        assert!(retrieved.is_some());
        let retrieved = retrieved.expect("should exist");
        assert_eq!(retrieved.label, "RFC 7519 - JSON Web Token");
        assert_eq!(retrieved.tier, 0);
        assert_eq!(retrieved.status, SourceStatus::Active);
    }

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

        let hash = [2u8; 32];
        let record = SourceRecord::new(hash, "Test Source".to_string(), None, 1, 1000);

        registry.register(&record).await.expect("register");

        registry.update_status(&hash, SourceStatus::Deprecated, 2000).await.expect("update status");

        let retrieved = registry.get(&hash).await.expect("get").expect("should exist");
        assert_eq!(retrieved.status, SourceStatus::Deprecated);
        assert_eq!(retrieved.updated_at, 2000);
    }

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

        let hash = [3u8; 32];
        let record = SourceRecord::new(hash, "Test Source".to_string(), None, 1, 1000);

        registry.register(&record).await.expect("register");

        // Verify it's in tier 1
        let tier1_sources = registry.list_by_tier(1, 100).await.expect("list tier 1");
        assert_eq!(tier1_sources.len(), 1);

        // Update to tier 3
        registry.update_tier(&hash, 3, 2000).await.expect("update tier");

        // Verify tier change
        let retrieved = registry.get(&hash).await.expect("get").expect("should exist");
        assert_eq!(retrieved.tier, 3);

        // Verify old tier index is cleaned up
        let tier1_sources = registry.list_by_tier(1, 100).await.expect("list tier 1");
        assert_eq!(tier1_sources.len(), 0);

        // Verify new tier index exists
        let tier3_sources = registry.list_by_tier(3, 100).await.expect("list tier 3");
        assert_eq!(tier3_sources.len(), 1);
    }

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

        // Register sources with different tiers
        for i in 0u8..5 {
            let mut hash = [0u8; 32];
            hash[0] = i;
            let record =
                SourceRecord::new(hash, format!("Source {}", i), None, i % 3, 1000 + i as u64);
            registry.register(&record).await.expect("register");
        }

        // Tier 0 should have 2 sources (0, 3)
        let tier0 = registry.list_by_tier(0, 100).await.expect("list");
        assert_eq!(tier0.len(), 2);

        // Tier 1 should have 2 sources (1, 4)
        let tier1 = registry.list_by_tier(1, 100).await.expect("list");
        assert_eq!(tier1.len(), 2);

        // Tier 2 should have 1 source (2)
        let tier2 = registry.list_by_tier(2, 100).await.expect("list");
        assert_eq!(tier2.len(), 1);
    }

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

        // Register some RFC sources
        let hash1 = [10u8; 32];
        registry
            .register(&SourceRecord::new(hash1, "RFC 7519 - JWT".to_string(), None, 0, 1000))
            .await
            .expect("register");

        let hash2 = [11u8; 32];
        registry
            .register(&SourceRecord::new(hash2, "RFC 9068 - OAuth".to_string(), None, 0, 1001))
            .await
            .expect("register");

        let hash3 = [12u8; 32];
        registry
            .register(&SourceRecord::new(hash3, "FDA Approval Letter".to_string(), None, 0, 1002))
            .await
            .expect("register");

        // Search for RFC
        let rfc_sources = registry.search("RFC", 100).await.expect("search");
        assert_eq!(rfc_sources.len(), 2);

        // Search for FDA
        let fda_sources = registry.search("FDA", 100).await.expect("search");
        assert_eq!(fda_sources.len(), 1);

        // Case insensitive search
        let rfc_lower = registry.search("rfc", 100).await.expect("search");
        assert_eq!(rfc_lower.len(), 2);
    }

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

        let hash = [20u8; 32];
        let record = SourceRecord::new(hash, "To Delete".to_string(), None, 1, 1000);

        registry.register(&record).await.expect("register");
        assert!(registry.get(&hash).await.expect("get").is_some());

        let deleted = registry.delete(&hash).await.expect("delete");
        assert!(deleted);

        assert!(registry.get(&hash).await.expect("get").is_none());

        // Delete again should return false
        let deleted_again = registry.delete(&hash).await.expect("delete");
        assert!(!deleted_again);
    }

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

        assert_eq!(registry.count().await.expect("count"), 0);

        for i in 0u8..5 {
            let mut hash = [0u8; 32];
            hash[0] = i;
            let record = SourceRecord::new(hash, format!("Source {}", i), None, 0, 1000);
            registry.register(&record).await.expect("register");
        }

        assert_eq!(registry.count().await.expect("count"), 5);
    }

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

        let hash = [99u8; 32];
        let result = registry.update_status(&hash, SourceStatus::Deprecated, 1000).await;
        assert!(result.is_err());
    }
}
