//! Collection types for multi-collection support
//!
//! Provides types for managing multiple collections within a database,
//! each with its own document store, vector index, and graph store.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Default collection name
pub const DEFAULT_COLLECTION: &str = "default";

// ============================================================================
// Foreign Key Types
// ============================================================================

/// Action to take when a referenced record is deleted
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OnDeleteAction {
    /// Prevent deletion if there are referencing records (default)
    #[default]
    Restrict,
    /// Delete all referencing records
    Cascade,
    /// Set the foreign key field to null in referencing records
    SetNull,
    /// Do nothing (no referential integrity check on delete)
    NoAction,
}

impl OnDeleteAction {
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "restrict" => Some(Self::Restrict),
            "cascade" => Some(Self::Cascade),
            "set_null" | "setnull" => Some(Self::SetNull),
            "no_action" | "noaction" => Some(Self::NoAction),
            _ => None,
        }
    }
}

/// Configuration for a foreign key relationship
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForeignKeyConfig {
    /// Field name in this collection that holds the foreign key
    pub field: String,
    
    /// Name of the referenced collection
    pub references: String,
    
    /// Field name in the referenced collection (defaults to "id")
    #[serde(default = "default_reference_field")]
    pub reference_field: String,
    
    /// Action to take when the referenced record is deleted
    #[serde(default)]
    pub on_delete: OnDeleteAction,
    
    /// Optional alias for the populated field (defaults to field name without "Id" suffix)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alias: Option<String>,
}

fn default_reference_field() -> String {
    "id".to_string()
}

impl ForeignKeyConfig {
    /// Create a new foreign key configuration
    pub fn new(field: impl Into<String>, references: impl Into<String>) -> Self {
        Self {
            field: field.into(),
            references: references.into(),
            reference_field: "id".to_string(),
            on_delete: OnDeleteAction::Restrict,
            alias: None,
        }
    }
    
    /// Set the reference field (defaults to "id")
    pub fn with_reference_field(mut self, field: impl Into<String>) -> Self {
        self.reference_field = field.into();
        self
    }
    
    /// Set the on_delete action
    pub fn with_on_delete(mut self, action: OnDeleteAction) -> Self {
        self.on_delete = action;
        self
    }
    
    /// Set an alias for the populated field
    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
        self.alias = Some(alias.into());
        self
    }
    
    /// Get the alias for population, deriving from field name if not set
    pub fn get_alias(&self) -> String {
        if let Some(ref alias) = self.alias {
            alias.clone()
        } else {
            // Remove common suffixes like "Id", "_id", "ID"
            let field = &self.field;
            if field.ends_with("Id") {
                field[..field.len() - 2].to_string()
            } else if field.ends_with("_id") {
                field[..field.len() - 3].to_string()
            } else if field.ends_with("ID") {
                field[..field.len() - 2].to_string()
            } else {
                field.clone()
            }
        }
    }
}

// ============================================================================
// Populate Types (for JOIN queries)
// ============================================================================

/// Configuration for populating (joining) related documents
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PopulateConfig {
    /// Foreign key field to populate
    pub field: String,
    
    /// Target collection to fetch from (optional, inferred from foreign key config if not set)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub from: Option<String>,
    
    /// Alias for the populated field in results (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alias: Option<String>,
    
    /// Fields to select from the referenced document (optional, all if not set)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub select: Option<Vec<String>>,
    
    /// Nested populations (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub populate: Option<Vec<PopulateConfig>>,
}

impl PopulateConfig {
    /// Create a new populate configuration
    pub fn new(field: impl Into<String>) -> Self {
        Self {
            field: field.into(),
            from: None,
            alias: None,
            select: None,
            populate: None,
        }
    }
    
    /// Specify the source collection
    pub fn from_collection(mut self, collection: impl Into<String>) -> Self {
        self.from = Some(collection.into());
        self
    }
    
    /// Set an alias for the result field
    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
        self.alias = Some(alias.into());
        self
    }
    
    /// Select specific fields from the referenced document
    pub fn select_fields(mut self, fields: Vec<String>) -> Self {
        self.select = Some(fields);
        self
    }
    
    /// Add nested population
    pub fn with_nested(mut self, nested: Vec<PopulateConfig>) -> Self {
        self.populate = Some(nested);
        self
    }
}

/// Configuration for a collection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionConfig {
    /// Collection name (used as directory name)
    pub name: String,
    
    /// Vector dimension for this collection
    pub dimension: usize,
    
    /// Optional description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    
    /// Custom metadata
    #[serde(default)]
    pub metadata: HashMap<String, serde_json::Value>,
    
    /// Foreign key constraints for this collection
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub foreign_keys: Vec<ForeignKeyConfig>,
}

impl CollectionConfig {
    /// Create a new collection config with the given name and dimension
    pub fn new(name: impl Into<String>, dimension: usize) -> Self {
        Self {
            name: name.into(),
            dimension,
            description: None,
            metadata: HashMap::new(),
            foreign_keys: Vec::new(),
        }
    }
    
    /// Create a default collection config
    pub fn default_collection(dimension: usize) -> Self {
        Self::new(DEFAULT_COLLECTION, dimension)
    }
    
    /// Set description
    pub fn with_description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }
    
    /// Add metadata
    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata.insert(key.into(), value);
        self
    }
    
    /// Add a foreign key constraint
    pub fn with_foreign_key(mut self, fk: ForeignKeyConfig) -> Self {
        self.foreign_keys.push(fk);
        self
    }
    
    /// Add multiple foreign key constraints
    pub fn with_foreign_keys(mut self, fks: Vec<ForeignKeyConfig>) -> Self {
        self.foreign_keys.extend(fks);
        self
    }
    
    /// Get foreign key config for a field
    pub fn get_foreign_key(&self, field: &str) -> Option<&ForeignKeyConfig> {
        self.foreign_keys.iter().find(|fk| fk.field == field)
    }
    
    /// Get all foreign keys that reference a specific collection
    pub fn get_foreign_keys_referencing(&self, collection: &str) -> Vec<&ForeignKeyConfig> {
        self.foreign_keys.iter()
            .filter(|fk| fk.references == collection)
            .collect()
    }
}

/// Metadata about a collection (persisted to collections.json)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionMetadata {
    /// Collection configuration
    pub config: CollectionConfig,
    
    /// Creation timestamp (Unix milliseconds)
    pub created_at: i64,
    
    /// Last modified timestamp (Unix milliseconds)
    pub updated_at: i64,
    
    /// Number of documents in the collection
    pub document_count: usize,
    
    /// Number of vectors in the collection
    pub vector_count: usize,
}

impl CollectionMetadata {
    /// Create new collection metadata
    pub fn new(config: CollectionConfig) -> Self {
        let now = chrono_now();
        Self {
            config,
            created_at: now,
            updated_at: now,
            document_count: 0,
            vector_count: 0,
        }
    }
    
    /// Update the modified timestamp
    pub fn touch(&mut self) {
        self.updated_at = chrono_now();
    }
    
    /// Update document count
    pub fn set_document_count(&mut self, count: usize) {
        self.document_count = count;
        self.touch();
    }
    
    /// Update vector count
    pub fn set_vector_count(&mut self, count: usize) {
        self.vector_count = count;
        self.touch();
    }
}

/// Database-level metadata (persisted to collections.json)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseMetadata {
    /// Version of the metadata format
    pub version: u32,
    
    /// All collections in the database
    pub collections: HashMap<String, CollectionMetadata>,
    
    /// Database creation timestamp
    pub created_at: i64,
    
    /// Last modified timestamp
    pub updated_at: i64,
}

impl Default for DatabaseMetadata {
    fn default() -> Self {
        Self::new()
    }
}

impl DatabaseMetadata {
    /// Current metadata format version
    pub const CURRENT_VERSION: u32 = 1;
    
    /// Create new database metadata
    pub fn new() -> Self {
        let now = chrono_now();
        Self {
            version: Self::CURRENT_VERSION,
            collections: HashMap::new(),
            created_at: now,
            updated_at: now,
        }
    }
    
    /// Add a collection
    pub fn add_collection(&mut self, metadata: CollectionMetadata) {
        let name = metadata.config.name.clone();
        self.collections.insert(name, metadata);
        self.updated_at = chrono_now();
    }
    
    /// Remove a collection
    pub fn remove_collection(&mut self, name: &str) -> Option<CollectionMetadata> {
        let result = self.collections.remove(name);
        if result.is_some() {
            self.updated_at = chrono_now();
        }
        result
    }
    
    /// Get a collection's metadata
    pub fn get_collection(&self, name: &str) -> Option<&CollectionMetadata> {
        self.collections.get(name)
    }
    
    /// Get a mutable reference to a collection's metadata
    pub fn get_collection_mut(&mut self, name: &str) -> Option<&mut CollectionMetadata> {
        self.collections.get_mut(name)
    }
    
    /// Check if a collection exists
    pub fn has_collection(&self, name: &str) -> bool {
        self.collections.contains_key(name)
    }
    
    /// Get all collection names
    pub fn collection_names(&self) -> Vec<String> {
        self.collections.keys().cloned().collect()
    }
    
    /// Get the number of collections
    pub fn collection_count(&self) -> usize {
        self.collections.len()
    }
}

/// Get current Unix timestamp in milliseconds
fn chrono_now() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as i64
}

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

    #[test]
    fn test_collection_config() {
        let config = CollectionConfig::new("poems", 384)
            .with_description("古诗词集合")
            .with_metadata("language", serde_json::json!("zh"));
        
        assert_eq!(config.name, "poems");
        assert_eq!(config.dimension, 384);
        assert_eq!(config.description, Some("古诗词集合".to_string()));
        assert!(config.metadata.contains_key("language"));
    }

    #[test]
    fn test_database_metadata() {
        let mut db_meta = DatabaseMetadata::new();
        
        let config = CollectionConfig::new("test", 128);
        let collection_meta = CollectionMetadata::new(config);
        
        db_meta.add_collection(collection_meta);
        
        assert!(db_meta.has_collection("test"));
        assert_eq!(db_meta.collection_count(), 1);
        
        let names = db_meta.collection_names();
        assert!(names.contains(&"test".to_string()));
    }
}
