//! Specialized storage for ConceptPath aliases.
//!
//! The AliasStore enables cross-scheme entity resolution.

use crate::error::Result;
use crate::key_codec;
use crate::traits::KVStore;
use async_trait::async_trait;
use std::collections::HashSet;
use stemedb_core::types::{ConceptAlias, ConceptPath};
use tracing::{debug, instrument, warn};

/// Specialized storage trait for ConceptPath alias operations.
///
/// Enables cross-scheme entity resolution for hierarchical queries.
#[async_trait]
pub trait AliasStore: Send + Sync {
    /// Store a concept alias (bidirectional).
    async fn set_alias(&self, alias: &ConceptAlias) -> Result<()>;
    /// Get the canonical path for an alias.
    async fn get_canonical(&self, alias_path: &str) -> Result<Option<ConceptPath>>;
    /// Get all aliases for a canonical path.
    async fn get_aliases(&self, canonical_path: &str) -> Result<Vec<ConceptPath>>;
    /// Resolve a path to all related paths (transitive, cycle-safe).
    async fn resolve_all(&self, path: &str) -> Result<Vec<String>>;
    /// Delete an alias and update reverse index.
    async fn delete_alias(&self, alias_path: &str) -> Result<bool>;
    /// Suggest aliases based on subject similarity.
    async fn suggest_aliases(
        &self,
        path: &str,
        existing_subjects: &[String],
    ) -> Result<Vec<(String, String)>>;
    /// List all aliases in the store.
    async fn list_all_aliases(&self) -> Result<Vec<(String, String)>>;
}

/// AliasStore implementation backed by a generic KVStore.
pub struct GenericAliasStore<S> {
    store: S,
}

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

    fn serialize_path(path: &ConceptPath) -> Result<Vec<u8>> {
        crate::serde_helpers::serialize(path)
    }

    fn deserialize_path(data: &[u8]) -> Result<ConceptPath> {
        crate::serde_helpers::deserialize(data)
    }

    fn serialize_strings(paths: &Vec<String>) -> Result<Vec<u8>> {
        crate::serde_helpers::serialize(paths)
    }

    fn deserialize_strings(data: &[u8]) -> Result<Vec<String>> {
        crate::serde_helpers::deserialize(data)
    }
}

#[async_trait]
impl<S: KVStore + 'static> AliasStore for GenericAliasStore<S> {
    #[instrument(skip(self, alias), fields(alias_path = %alias.alias, canonical_path = %alias.canonical))]
    async fn set_alias(&self, alias: &ConceptAlias) -> Result<()> {
        let alias_path = alias.alias.to_wire_format();
        let canonical_path = alias.canonical.to_wire_format();

        let forward_key = key_codec::alias_key(&alias_path);
        let canonical_data = Self::serialize_path(&alias.canonical)?;
        self.store.put(&forward_key, &canonical_data).await?;

        let reverse_key = key_codec::alias_reverse_key(&canonical_path);
        let mut aliases = match self.store.get(&reverse_key).await? {
            Some(data) => Self::deserialize_strings(&data)?,
            None => Vec::new(),
        };

        if !aliases.contains(&alias_path) {
            aliases.push(alias_path.clone());
            let aliases_data = Self::serialize_strings(&aliases)?;
            self.store.put(&reverse_key, &aliases_data).await?;
        }

        debug!(origin = %alias.origin, created_by = %hex::encode(alias.created_by), "Stored concept alias");
        Ok(())
    }

    #[instrument(skip(self), fields(alias_path = %alias_path))]
    async fn get_canonical(&self, alias_path: &str) -> Result<Option<ConceptPath>> {
        let key = key_codec::alias_key(alias_path);
        match self.store.get(&key).await? {
            Some(data) => {
                let path = Self::deserialize_path(&data)?;
                debug!(canonical = %path, "Resolved alias to canonical");
                Ok(Some(path))
            }
            None => {
                debug!("No canonical found for alias");
                Ok(None)
            }
        }
    }

    #[instrument(skip(self), fields(canonical_path = %canonical_path))]
    async fn get_aliases(&self, canonical_path: &str) -> Result<Vec<ConceptPath>> {
        let key = key_codec::alias_reverse_key(canonical_path);
        match self.store.get(&key).await? {
            Some(data) => {
                let alias_strings = Self::deserialize_strings(&data)?;
                let mut paths = Vec::with_capacity(alias_strings.len());
                for s in alias_strings {
                    match ConceptPath::parse(&s) {
                        Ok(path) => paths.push(path),
                        Err(e) => {
                            warn!(alias = %s, error = %e, "Failed to parse stored alias path")
                        }
                    }
                }
                debug!(count = paths.len(), "Retrieved aliases for canonical");
                Ok(paths)
            }
            None => {
                debug!("No aliases found for canonical");
                Ok(Vec::new())
            }
        }
    }

    #[instrument(skip(self), fields(path = %path))]
    async fn resolve_all(&self, path: &str) -> Result<Vec<String>> {
        let mut visited: HashSet<String> = HashSet::new();
        let mut result: Vec<String> = Vec::new();
        let mut queue: Vec<String> = vec![path.to_string()];

        while let Some(current) = queue.pop() {
            if visited.contains(&current) {
                continue;
            }
            visited.insert(current.clone());
            result.push(current.clone());

            if let Some(canonical) = self.get_canonical(&current).await? {
                let canonical_str = canonical.to_wire_format();
                if !visited.contains(&canonical_str) {
                    queue.push(canonical_str);
                }
            }

            let aliases = self.get_aliases(&current).await?;
            for alias in aliases {
                let alias_str = alias.to_wire_format();
                if !visited.contains(&alias_str) {
                    queue.push(alias_str);
                }
            }
        }

        debug!(count = result.len(), "Resolved all paths transitively");
        Ok(result)
    }

    #[instrument(skip(self), fields(alias_path = %alias_path))]
    async fn delete_alias(&self, alias_path: &str) -> Result<bool> {
        let forward_key = key_codec::alias_key(alias_path);
        let canonical_path = match self.store.get(&forward_key).await? {
            Some(data) => Self::deserialize_path(&data)?.to_wire_format(),
            None => {
                debug!("Alias not found");
                return Ok(false);
            }
        };

        self.store.delete(&forward_key).await?;

        let reverse_key = key_codec::alias_reverse_key(&canonical_path);
        if let Some(data) = self.store.get(&reverse_key).await? {
            let mut aliases = Self::deserialize_strings(&data)?;
            aliases.retain(|a| a != alias_path);

            if aliases.is_empty() {
                self.store.delete(&reverse_key).await?;
            } else {
                let aliases_data = Self::serialize_strings(&aliases)?;
                self.store.put(&reverse_key, &aliases_data).await?;
            }
        }

        debug!(canonical = %canonical_path, "Deleted alias");
        Ok(true)
    }

    #[instrument(skip(self, existing_subjects), fields(path = %path, existing_count = existing_subjects.len()))]
    async fn suggest_aliases(
        &self,
        path: &str,
        existing_subjects: &[String],
    ) -> Result<Vec<(String, String)>> {
        let mut suggestions = Vec::new();
        let input_path = match ConceptPath::parse(path) {
            Ok(p) => p,
            Err(_) => return Ok(suggestions),
        };

        let input_leaf = input_path.leaf().to_lowercase();
        let input_scheme = &input_path.scheme;

        for subject in existing_subjects {
            if subject == path {
                continue;
            }
            let existing_path = match ConceptPath::parse(subject) {
                Ok(p) => p,
                Err(_) => continue,
            };

            let existing_leaf = existing_path.leaf().to_lowercase();
            if existing_leaf == input_leaf && existing_path.scheme != *input_scheme {
                suggestions.push((
                    subject.clone(),
                    format!(
                        "Same leaf '{}' across schemes ({} vs {})",
                        input_leaf, input_scheme, existing_path.scheme
                    ),
                ));
                continue;
            }

            if (existing_leaf.contains(&input_leaf) || input_leaf.contains(&existing_leaf))
                && existing_path.scheme != *input_scheme
            {
                suggestions.push((
                    subject.clone(),
                    format!(
                        "Similar leaf names ('{}' ~ '{}') across schemes",
                        input_leaf, existing_leaf
                    ),
                ));
            }
        }

        debug!(count = suggestions.len(), "Generated alias suggestions");
        Ok(suggestions)
    }

    #[instrument(skip(self))]
    async fn list_all_aliases(&self) -> Result<Vec<(String, String)>> {
        let prefix = key_codec::alias_scan_prefix();
        let entries = self.store.scan_prefix(&prefix).await?;

        let mut result = Vec::new();
        for (key, value) in entries {
            let alias_path = match key_codec::extract_alias_path(&key) {
                Some(p) => p,
                None => continue,
            };

            let canonical = match Self::deserialize_path(&value) {
                Ok(p) => p.to_wire_format(),
                Err(e) => {
                    warn!(alias = %alias_path, error = %e, "Failed to deserialize");
                    continue;
                }
            };

            result.push((alias_path, canonical));
        }

        debug!(count = result.len(), "Listed all aliases");
        Ok(result)
    }
}

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

    fn create_test_alias(alias: &str, canonical: &str) -> ConceptAlias {
        ConceptAlias::new(
            ConceptPath::parse(alias).expect("valid alias"),
            ConceptPath::parse(canonical).expect("valid canonical"),
            [1u8; 32],
            1000,
            AliasOrigin::Manual,
        )
    }

    #[tokio::test]
    async fn test_set_and_get_alias() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let alias_store = GenericAliasStore::new(store);
        let alias =
            create_test_alias("code://rust/auth/jwt/aud", "rfc://7519/jwt/audience_validation");
        alias_store.set_alias(&alias).await.expect("set alias");
        let canonical =
            alias_store.get_canonical("code://rust/auth/jwt/aud").await.expect("get canonical");
        assert!(canonical.is_some());
        let canonical_path = canonical.expect("canonical exists");
        assert_eq!(canonical_path.scheme, "rfc");
    }

    #[tokio::test]
    async fn test_resolve_all_transitive() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let alias_store = GenericAliasStore::new(store);
        let alias1 = create_test_alias("code://rust/auth/jwt", "rfc://7519/jwt");
        let alias2 = create_test_alias("internal://jwt/impl", "code://rust/auth/jwt");
        alias_store.set_alias(&alias1).await.expect("set alias1");
        alias_store.set_alias(&alias2).await.expect("set alias2");
        let all = alias_store.resolve_all("internal://jwt/impl").await.expect("resolve all");
        assert!(all.contains(&"internal://jwt/impl".to_string()));
        assert!(all.contains(&"code://rust/auth/jwt".to_string()));
        assert!(all.contains(&"rfc://7519/jwt".to_string()));
    }

    #[tokio::test]
    async fn test_delete_alias() {
        let store = Arc::new(HybridStore::open_temp().expect("Failed to create store"));
        let alias_store = GenericAliasStore::new(store);
        let alias = create_test_alias("code://rust/auth", "rfc://7519");
        alias_store.set_alias(&alias).await.expect("set alias");
        assert!(alias_store.get_canonical("code://rust/auth").await.expect("get").is_some());
        let deleted = alias_store.delete_alias("code://rust/auth").await.expect("delete");
        assert!(deleted);
        assert!(alias_store.get_canonical("code://rust/auth").await.expect("get").is_none());
    }
}
