//! Type-safe entity keys for entity-level cache invalidation.
//!
//! An `EntityKey` represents a specific entity instance, combining:
//! - `entity_type`: The type of entity (e.g., "User", "Post", "Comment")
//! - `entity_id`: The unique identifier (UUID) of that instance
//!
//! Entity keys are used to track which queries depend on which specific entities,
//! enabling precise invalidation when those entities are modified.
//!
//! # Format
//!
//! Entity keys are serialized as: `"EntityType:uuid"`
//!
//! Example:
//! ```text
//! "User:550e8400-e29b-41d4-a716-446655440000"
//! "Post:e7d7a1a1-b2c3-4d5e-f6g7-h8i9j0k1l2m3"
//! ```
//!
//! # Examples
//!
//! ```
//! use fraiseql_core::cache::EntityKey;
//!
//! let key = EntityKey::new("User", "550e8400-e29b-41d4-a716-446655440000").unwrap();
//! assert_eq!(key.to_cache_key(), "User:550e8400-e29b-41d4-a716-446655440000");
//! ```

use std::{
    fmt,
    hash::{Hash, Hasher},
};

use crate::error::{FraiseQLError, Result};

/// Type-safe entity key for cache invalidation.
///
/// Combines entity type and ID into a single, hashable key for use in
/// dependency tracking and cache invalidation.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct EntityKey {
    /// Entity type (e.g., "User", "Post")
    pub entity_type: String,

    /// Entity ID (UUID)
    pub entity_id: String,
}

impl EntityKey {
    /// Create a new entity key with validation.
    ///
    /// # Arguments
    ///
    /// * `entity_type` - The type of entity (must be non-empty)
    /// * `entity_id` - The entity's unique identifier (must be non-empty)
    ///
    /// # Returns
    ///
    /// - `Ok(EntityKey)` - If both arguments are valid
    /// - `Err(_)` - If either argument is empty
    ///
    /// # Errors
    ///
    /// Returns [`FraiseQLError::Validation`] if `entity_type` or `entity_id` is
    /// empty, or if `entity_type` contains a colon character (reserved as the
    /// cache-key separator).
    ///
    /// # Examples
    ///
    /// ```
    /// use fraiseql_core::cache::EntityKey;
    ///
    /// let key = EntityKey::new("User", "550e8400-e29b-41d4-a716-446655440000").unwrap();
    /// assert_eq!(key.entity_type, "User");
    /// ```
    pub fn new(entity_type: &str, entity_id: &str) -> Result<Self> {
        if entity_type.is_empty() {
            return Err(FraiseQLError::Validation {
                message: "entity_type cannot be empty".to_string(),
                path:    None,
            });
        }

        // A colon in entity_type would corrupt the "Type:id" cache key format.
        // `from_cache_key` uses splitn(2, ':'), so "Foo:Bar:id" would be parsed
        // as type="Foo", id="Bar:id" — silently wrong.
        if entity_type.contains(':') {
            return Err(FraiseQLError::Validation {
                message: format!(
                    "entity_type {entity_type:?} must not contain a colon character; \
                     colons are used as the cache-key separator"
                ),
                path:    None,
            });
        }

        if entity_id.is_empty() {
            return Err(FraiseQLError::Validation {
                message: "entity_id cannot be empty".to_string(),
                path:    None,
            });
        }

        Ok(Self {
            entity_type: entity_type.to_string(),
            entity_id:   entity_id.to_string(),
        })
    }

    /// Convert entity key to cache key format: "`EntityType:entity_id`"
    ///
    /// # Examples
    ///
    /// ```
    /// use fraiseql_core::cache::EntityKey;
    ///
    /// let key = EntityKey::new("User", "550e8400-e29b-41d4-a716-446655440000").unwrap();
    /// assert_eq!(key.to_cache_key(), "User:550e8400-e29b-41d4-a716-446655440000");
    /// ```
    #[must_use]
    pub fn to_cache_key(&self) -> String {
        format!("{}:{}", self.entity_type, self.entity_id)
    }

    /// Parse entity key from cache key format: "`EntityType:entity_id`"
    ///
    /// # Arguments
    ///
    /// * `cache_key` - String in format "Type:id"
    ///
    /// # Returns
    ///
    /// - `Ok(EntityKey)` - If format is valid
    /// - `Err(_)` - If format is invalid
    ///
    /// # Errors
    ///
    /// Returns [`FraiseQLError::Validation`] if `cache_key` does not contain a
    /// colon separator or if the resulting type or id part is invalid (see
    /// [`EntityKey::new`]).
    ///
    /// # Examples
    ///
    /// ```
    /// use fraiseql_core::cache::EntityKey;
    ///
    /// let key = EntityKey::from_cache_key("User:550e8400-e29b-41d4-a716-446655440000").unwrap();
    /// assert_eq!(key.entity_type, "User");
    /// ```
    pub fn from_cache_key(cache_key: &str) -> Result<Self> {
        let parts: Vec<&str> = cache_key.splitn(2, ':').collect();

        if parts.len() != 2 {
            return Err(FraiseQLError::Validation {
                message: format!("Invalid entity key format: {}. Expected 'Type:id'", cache_key),
                path:    None,
            });
        }

        Self::new(parts[0], parts[1])
    }
}

impl fmt::Display for EntityKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_cache_key())
    }
}

impl Hash for EntityKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.entity_type.hash(state);
        self.entity_id.hash(state);
    }
}
