//! Unified Types for Heterogeneous Storage Federation
//!
//! This module defines all the common types used across the federation layer.

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

// Re-export Visibility from core types (shared between types/ and federation/)
pub use crate::types::ownership::Visibility;

// ============================================================================
// Ownership & Permission Types (§8.5.7)
// ============================================================================

// RecordPermission, Collaborator, ProtectionLevel are defined in crate::types::ownership
// (shared with core types MemoryNode/Entity to avoid circular dependencies).
pub use crate::types::ownership::{RecordPermission, Collaborator, ProtectionLevel};

/// Reserved owner_id for system-generated data
pub const SYSTEM_OWNER_ID: &str = "__system__";

/// Record ownership metadata (attached to each data record)
///
/// When this is `Some(...)` on a Record, record-level permission checks
/// are activated (ABAC layer). When `None`, only RBAC is used (backward compat).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnershipMeta {
    /// Owner's user ID (system data uses `SYSTEM_OWNER_ID`)
    pub owner_id: String,
    /// Visibility setting
    pub visibility: Visibility,
    /// Collaborator list
    #[serde(default)]
    pub collaborators: Vec<Collaborator>,
    /// Data protection level
    #[serde(default)]
    pub protection: ProtectionLevel,
    /// Creation timestamp (Unix ms)
    pub created_at: i64,
    /// Last modification timestamp (Unix ms)
    pub updated_at: i64,
    /// Last modified by (user ID)
    pub updated_by: String,
}

impl OwnershipMeta {
    /// Create a new OwnershipMeta for a user-created record
    pub fn new_for_user(owner_id: impl Into<String>) -> Self {
        let now = chrono_now();
        let owner = owner_id.into();
        Self {
            owner_id: owner.clone(),
            visibility: Visibility::default(),
            collaborators: Vec::new(),
            protection: ProtectionLevel::None,
            created_at: now,
            updated_at: now,
            updated_by: owner,
        }
    }

    /// Create a new OwnershipMeta for system data
    pub fn new_system(protection: ProtectionLevel) -> Self {
        let now = chrono_now();
        Self {
            owner_id: SYSTEM_OWNER_ID.to_string(),
            visibility: Visibility::Public,
            collaborators: Vec::new(),
            protection,
            created_at: now,
            updated_at: now,
            updated_by: SYSTEM_OWNER_ID.to_string(),
        }
    }

    /// Create with specific visibility
    pub fn with_visibility(mut self, visibility: Visibility) -> Self {
        self.visibility = visibility;
        self
    }

    /// Create with specific protection level
    pub fn with_protection(mut self, protection: ProtectionLevel) -> Self {
        self.protection = protection;
        self
    }

    /// Check if this is system data
    pub fn is_system_data(&self) -> bool {
        self.owner_id == SYSTEM_OWNER_ID
    }
}

/// 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
}

// ============================================================================
// Storage Types
// ============================================================================

/// Storage type enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum StoreType {
    /// Graph database (SocialStoreV2)
    #[default]
    Graph,
    /// Document database (EnhancedDocumentStore)
    Document,
    /// Vector database (ConcurrentVectorStore) - Future
    Vector,
}

impl std::fmt::Display for StoreType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StoreType::Graph => write!(f, "graph"),
            StoreType::Document => write!(f, "document"),
            StoreType::Vector => write!(f, "vector"),
        }
    }
}

/// Unified record type for cross-store query results
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Record {
    /// Graph entity
    GraphEntity {
        id: String,
        entity_type: String,
        name: String,
        properties: HashMap<String, serde_json::Value>,
        created_at: i64,
        /// Record ownership metadata (None = no record-level permission check)
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        ownership: Option<OwnershipMeta>,
    },
    
    /// Graph relation
    GraphRelation {
        id: String,
        source: String,
        target: String,
        relation_type: String,
        metadata: HashMap<String, serde_json::Value>,
        /// Record ownership metadata
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        ownership: Option<OwnershipMeta>,
    },
    
    /// Document
    Document {
        id: String,
        doc_type: String,
        content: serde_json::Value,
        tags: Vec<String>,
        created_at: u64,
        updated_at: u64,
        /// Record ownership metadata
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        ownership: Option<OwnershipMeta>,
    },
    
    /// Vector
    Vector {
        id: String,
        vector: Vec<f32>,
        metadata: HashMap<String, serde_json::Value>,
        score: Option<f32>,
        /// Record ownership metadata
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        ownership: Option<OwnershipMeta>,
    },
}

impl Record {
    /// Get the record ID as owned String
    pub fn id(&self) -> String {
        match self {
            Record::GraphEntity { id, .. } => id.clone(),
            Record::GraphRelation { id, .. } => id.to_string(),
            Record::Document { id, .. } => id.clone(),
            Record::Vector { id, .. } => id.clone(),
        }
    }
    
    /// Get the store type this record belongs to
    pub fn store_type(&self) -> StoreType {
        match self {
            Record::GraphEntity { .. } | Record::GraphRelation { .. } => StoreType::Graph,
            Record::Document { .. } => StoreType::Document,
            Record::Vector { .. } => StoreType::Vector,
        }
    }
    
    /// Get the ownership metadata (if any)
    ///
    /// Returns `None` for records without ownership control (backward compatible).
    /// When `Some`, record-level permission checks (ABAC) should be applied.
    pub fn ownership(&self) -> Option<&OwnershipMeta> {
        match self {
            Record::GraphEntity { ownership, .. } => ownership.as_ref(),
            Record::GraphRelation { ownership, .. } => ownership.as_ref(),
            Record::Document { ownership, .. } => ownership.as_ref(),
            Record::Vector { ownership, .. } => ownership.as_ref(),
        }
    }
    
    /// Set ownership metadata on this record
    pub fn with_ownership(mut self, meta: OwnershipMeta) -> Self {
        match &mut self {
            Record::GraphEntity { ownership, .. } => *ownership = Some(meta),
            Record::GraphRelation { ownership, .. } => *ownership = Some(meta),
            Record::Document { ownership, .. } => *ownership = Some(meta),
            Record::Vector { ownership, .. } => *ownership = Some(meta),
        }
        self
    }
}

/// Unified write operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WriteOp {
    // ===== Graph Operations =====
    
    /// Create a graph entity
    CreateEntity {
        entity_type: String,
        id: String,
        name: String,
        attributes: HashMap<String, serde_json::Value>,
        /// Owner user ID (enables record-level permission control)
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        owner_id: Option<String>,
        /// Initial visibility (defaults to Public if not specified)
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        visibility: Option<Visibility>,
    },
    
    /// Update a graph entity
    UpdateEntity {
        id: String,
        name: Option<String>,
        attributes: Option<HashMap<String, serde_json::Value>>,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
    
    /// Delete a graph entity
    DeleteEntity {
        id: String,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
    
    /// Create a graph relation
    CreateRelation {
        source_id: String,
        target_id: String,
        relation_type: String,
        attributes: HashMap<String, serde_json::Value>,
        /// Owner user ID
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        owner_id: Option<String>,
    },
    
    /// Delete a graph relation
    DeleteRelation {
        relation_id: String,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
    
    // ===== Document Operations =====
    
    /// Create a document
    CreateDocument {
        id: String,
        doc_type: String,
        content: serde_json::Value,
        tags: Vec<String>,
        /// Owner user ID
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        owner_id: Option<String>,
        /// Initial visibility
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        visibility: Option<Visibility>,
    },
    
    /// Update a document
    UpdateDocument {
        id: String,
        content: Option<serde_json::Value>,
        tags: Option<Vec<String>>,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
    
    /// Delete a document
    DeleteDocument {
        id: String,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
    
    // ===== Vector Operations =====
    
    /// Upsert a vector
    UpsertVector {
        id: String,
        vector: Vec<f32>,
        metadata: HashMap<String, serde_json::Value>,
        /// Owner user ID
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        owner_id: Option<String>,
        /// Initial visibility
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        visibility: Option<Visibility>,
    },
    
    /// Delete a vector
    DeleteVector {
        id: String,
        /// Caller ID for ownership verification
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        caller_id: Option<String>,
    },
}

impl WriteOp {
    /// Get the record ID affected by this operation
    pub fn record_id(&self) -> &str {
        match self {
            WriteOp::CreateEntity { id, .. } => id,
            WriteOp::UpdateEntity { id, .. } => id,
            WriteOp::DeleteEntity { id, .. } => id,
            WriteOp::CreateRelation { source_id, .. } => {
                // Relations don't have a pre-assigned ID; return the source as best-effort.
                // Callers should handle relation IDs separately.
                source_id
            }
            WriteOp::DeleteRelation { relation_id, .. } => relation_id,
            WriteOp::CreateDocument { id, .. } => id,
            WriteOp::UpdateDocument { id, .. } => id,
            WriteOp::DeleteDocument { id, .. } => id,
            WriteOp::UpsertVector { id, .. } => id,
            WriteOp::DeleteVector { id, .. } => id,
        }
    }

    /// Returns true if this is a create/upsert operation (new record)
    pub fn is_create(&self) -> bool {
        matches!(
            self,
            WriteOp::CreateEntity { .. }
                | WriteOp::CreateRelation { .. }
                | WriteOp::CreateDocument { .. }
                | WriteOp::UpsertVector { .. }
        )
    }

    /// Returns true if this is an update operation (existing record)
    pub fn is_update(&self) -> bool {
        matches!(
            self,
            WriteOp::UpdateEntity { .. } | WriteOp::UpdateDocument { .. }
        )
    }

    /// Returns true if this is a delete operation
    pub fn is_delete(&self) -> bool {
        matches!(
            self,
            WriteOp::DeleteEntity { .. }
                | WriteOp::DeleteRelation { .. }
                | WriteOp::DeleteDocument { .. }
                | WriteOp::DeleteVector { .. }
        )
    }

    /// Get the owner_id from create operations (if set)
    pub fn owner_id(&self) -> Option<&str> {
        match self {
            WriteOp::CreateEntity { owner_id, .. } => owner_id.as_deref(),
            WriteOp::CreateRelation { owner_id, .. } => owner_id.as_deref(),
            WriteOp::CreateDocument { owner_id, .. } => owner_id.as_deref(),
            WriteOp::UpsertVector { owner_id, .. } => owner_id.as_deref(),
            _ => None,
        }
    }

    /// Check if this operation is compatible with the given store type
    pub fn is_compatible_with(&self, store_type: StoreType) -> bool {
        match (self, store_type) {
            (WriteOp::CreateEntity { .. }, StoreType::Graph) => true,
            (WriteOp::UpdateEntity { .. }, StoreType::Graph) => true,
            (WriteOp::DeleteEntity { .. }, StoreType::Graph) => true,
            (WriteOp::CreateRelation { .. }, StoreType::Graph) => true,
            (WriteOp::DeleteRelation { .. }, StoreType::Graph) => true,
            (WriteOp::CreateDocument { .. }, StoreType::Document) => true,
            (WriteOp::UpdateDocument { .. }, StoreType::Document) => true,
            (WriteOp::DeleteDocument { .. }, StoreType::Document) => true,
            (WriteOp::UpsertVector { .. }, StoreType::Vector) => true,
            (WriteOp::DeleteVector { .. }, StoreType::Vector) => true,
            _ => false,
        }
    }
}

/// Write result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WriteResult {
    /// Record created successfully
    Created(String),
    /// Record updated successfully
    Updated(String),
    /// Record deleted successfully
    Deleted(String),
    /// No operation performed
    NoOp,
}

/// Batch write result
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchWriteResult {
    /// Number of successful operations
    pub success_count: usize,
    /// Number of failed operations
    pub failed_count: usize,
    /// Individual results
    pub results: Vec<Result<WriteResult, String>>,
    /// Execution time in milliseconds
    pub execution_time_ms: u64,
}

/// Unified query syntax
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UniversalQuery {
    /// Query target type (entity_type / doc_type)
    pub target: String,
    
    /// Filter conditions
    pub filters: Vec<Filter>,
    
    /// Order by
    pub order_by: Option<(String, SortOrder)>,
    
    /// Limit
    pub limit: Option<usize>,
    
    /// Offset
    pub offset: Option<usize>,
    
    /// Graph-specific: relation traversal
    pub traverse: Option<TraverseSpec>,
    
    /// Vector-specific: similarity search
    pub vector_search: Option<VectorSearchSpec>,
}

/// Filter condition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Filter {
    /// field = value
    Eq(String, serde_json::Value),
    /// field != value
    Ne(String, serde_json::Value),
    /// field > value
    Gt(String, serde_json::Value),
    /// field >= value
    Gte(String, serde_json::Value),
    /// field < value
    Lt(String, serde_json::Value),
    /// field <= value
    Lte(String, serde_json::Value),
    /// field IN (values)
    In(String, Vec<serde_json::Value>),
    /// field CONTAINS text (full-text search)
    Contains(String, String),
    /// AND combination
    And(Vec<Filter>),
    /// OR combination
    Or(Vec<Filter>),
}

/// Sort order
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SortOrder {
    Asc,
    Desc,
}

impl Default for SortOrder {
    fn default() -> Self {
        SortOrder::Asc
    }
}

/// Graph traversal specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraverseSpec {
    /// Starting entity ID
    pub start_id: String,
    /// Traversal direction
    pub direction: TraverseDirection,
    /// Relation types to follow (None = all)
    pub relation_types: Option<Vec<String>>,
    /// Maximum depth
    pub max_depth: usize,
}

/// Traversal direction
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TraverseDirection {
    Outgoing,
    Incoming,
    Both,
}

/// Vector search specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchSpec {
    /// Query vector
    pub query_vector: Vec<f32>,
    /// Number of results
    pub top_k: usize,
    /// Minimum score threshold
    pub min_score: Option<f32>,
    /// Metadata filters (post-filtering on vector metadata)
    #[serde(default)]
    pub metadata_filters: Vec<Filter>,
}

/// Entity/document location information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityLocation {
    /// Entity/Document ID
    pub entity_id: String,
    /// Store alias
    pub store_alias: String,
    /// Store type
    pub store_type: StoreType,
}

/// Cross-type relation (references between heterogeneous stores)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeCrossRelation {
    /// Source entity ID
    pub source_id: String,
    /// Source store type
    pub source_type: StoreType,
    /// Target entity ID
    pub target_id: String,
    /// Target store type
    pub target_type: StoreType,
    /// Relation type name
    pub relation_type: String,
}

/// Query target
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QueryTarget {
    /// Store alias
    #[serde(default)]
    pub db_alias: String,
    /// Store type
    #[serde(default)]
    pub store_type: StoreType,
    /// Type name (entity_type / doc_type)
    #[serde(default)]
    pub type_name: String,
    /// Internal query (optional)
    #[serde(default)]
    pub query: Option<UniversalQuery>,
}

impl QueryTarget {
    /// Create a new query target
    pub fn new(alias: &str, store_type: StoreType, type_name: &str) -> Self {
        Self {
            db_alias: alias.to_string(),
            store_type,
            type_name: type_name.to_string(),
            query: None,
        }
    }
    
    /// Create a graph query target
    pub fn graph(alias: &str, entity_type: &str) -> Self {
        Self::new(alias, StoreType::Graph, entity_type)
    }
    
    /// Create a document query target
    pub fn document(alias: &str, doc_type: &str) -> Self {
        Self::new(alias, StoreType::Document, doc_type)
    }
    
    /// Create a vector query target
    pub fn vector(alias: &str, collection_name: &str) -> Self {
        Self::new(alias, StoreType::Vector, collection_name)
    }
}

/// JOIN type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum JoinType {
    #[default]
    Left,
    Right,
    Inner,
    Full,
}

/// JOIN clause
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JoinClause {
    /// JOIN target
    pub target: QueryTarget,
    /// JOIN condition (e.g., "article.author_id = person.id")
    pub on: String,
    /// JOIN type
    pub join_type: JoinType,
}

/// JOIN strategy - determines how cross-store/cross-type joins are executed
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum JoinStrategy {
    /// Direct JOIN - same type stores, most efficient
    DirectJoin {
        /// Whether to use index for the join
        use_index: bool,
    },
    /// Index lookup - using CrossDbIndex for cross-type joins
    IndexLookup {
        /// Which index store to use
        index_store: String,
        /// Type of lookup (e.g., "type_cross_relation", "similarity")
        lookup_type: String,
    },
    /// Hash JOIN - build hash table on smaller side
    HashJoin {
        /// Which side to build the hash table on ("left" or "right")
        build_side: String,
    },
    /// Nested loop - fallback for small datasets
    NestedLoop {
        /// Batch size for nested loop processing
        batch_size: usize,
    },
}

impl JoinStrategy {
    /// Select the best strategy based on store types (simple heuristic)
    pub fn select(left: StoreType, right: StoreType) -> Self {
        match (left, right) {
            (StoreType::Graph, StoreType::Graph) => JoinStrategy::DirectJoin {
                use_index: true,
            },
            (StoreType::Document, StoreType::Document) => JoinStrategy::HashJoin {
                build_side: "left".to_string(),
            },
            (StoreType::Vector, StoreType::Vector) => JoinStrategy::DirectJoin {
                use_index: true,
            },
            (StoreType::Graph, StoreType::Document) | (StoreType::Document, StoreType::Graph) => {
                JoinStrategy::IndexLookup {
                    index_store: "cross_db_index".to_string(),
                    lookup_type: "type_cross_relation".to_string(),
                }
            }
            _ => JoinStrategy::NestedLoop {
                batch_size: 100,
            },
        }
    }
}

/// Cross-database query configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CrossDbQuery {
    /// Query targets (one or more stores to query across)
    pub targets: Vec<QueryTarget>,
    
    /// JOIN clauses for cross-store joins
    pub joins: Vec<JoinClause>,
    
    /// WHERE conditions / filters
    pub filters: Vec<Filter>,
    
    /// SELECT fields (None = all)
    pub select: Option<Vec<String>>,
    
    /// ORDER BY
    pub order_by: Option<(String, SortOrder)>,
    
    /// Pagination
    pub limit: Option<usize>,
    pub offset: Option<usize>,
    
    /// Use streaming
    pub streaming: bool,
    
    /// Stream batch size
    pub batch_size: Option<usize>,
}

/// Query result
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QueryResult {
    /// Records
    pub records: Vec<Record>,
    
    /// Total count (for pagination)
    pub total_count: Option<usize>,
    
    /// Has more data
    pub has_more: bool,
    
    /// Query statistics
    pub stats: QueryStats,
}

/// Query statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QueryStats {
    /// Execution time in milliseconds
    pub execution_time_ms: u64,
    /// Number of records scanned
    pub records_scanned: usize,
    /// Number of records returned
    pub records_returned: usize,
    /// JOIN strategy used
    pub join_strategy: Option<JoinStrategy>,
    /// Stores involved
    pub stores_involved: Vec<String>,
}
