//! Type definitions for concurrent graph operations
//!
//! Defines write operations, requests, and results for the concurrent graph store.

use std::time::Instant;
use tokio::sync::oneshot;

use crate::types::graph::{Entity, Relation};

/// Graph write operation types
#[derive(Debug, Clone)]
pub enum GraphWriteOp {
    /// Add or update an entity
    PutEntity(Entity),
    
    /// Delete an entity by ID
    DeleteEntity(String),
    
    /// Add or update a relation
    PutRelation(Relation),
    
    /// Delete a relation by ID
    DeleteRelation(String),
    
    /// Batch of operations (for atomic multi-op writes)
    Batch(Vec<GraphWriteOp>),
}

impl GraphWriteOp {
    /// Get the primary key for routing (entity_id or relation source)
    pub fn routing_key(&self) -> Option<&str> {
        match self {
            GraphWriteOp::PutEntity(e) => Some(&e.id),
            GraphWriteOp::DeleteEntity(id) => Some(id),
            GraphWriteOp::PutRelation(r) => Some(&r.source),
            GraphWriteOp::DeleteRelation(_) => None, // Need lookup for routing
            GraphWriteOp::Batch(ops) => {
                // Use first op's routing key for batch
                ops.first().and_then(|op| op.routing_key())
            }
        }
    }
    
    /// Get operation type name for logging
    pub fn op_type(&self) -> &'static str {
        match self {
            GraphWriteOp::PutEntity(_) => "put_entity",
            GraphWriteOp::DeleteEntity(_) => "delete_entity",
            GraphWriteOp::PutRelation(_) => "put_relation",
            GraphWriteOp::DeleteRelation(_) => "delete_relation",
            GraphWriteOp::Batch(_) => "batch",
        }
    }
    
    /// Count number of operations (for batches)
    pub fn op_count(&self) -> usize {
        match self {
            GraphWriteOp::Batch(ops) => ops.iter().map(|op| op.op_count()).sum(),
            _ => 1,
        }
    }
}

/// Graph write request with completion notification support
pub struct GraphWriteRequest {
    /// The operation to perform
    pub op: GraphWriteOp,
    
    /// Sequence number for ordering
    pub sequence: u64,
    
    /// Optional channel to notify completion
    pub completion: Option<oneshot::Sender<GraphWriteResult>>,
    
    /// Timestamp when request was created
    pub created_at: Instant,
}

impl GraphWriteRequest {
    /// Create a new write request without completion notification
    pub fn new(op: GraphWriteOp, sequence: u64) -> Self {
        Self {
            op,
            sequence,
            completion: None,
            created_at: Instant::now(),
        }
    }
    
    /// Create a write request with completion notification
    pub fn with_completion(op: GraphWriteOp, sequence: u64) -> (Self, oneshot::Receiver<GraphWriteResult>) {
        let (tx, rx) = oneshot::channel();
        let request = Self {
            op,
            sequence,
            completion: Some(tx),
            created_at: Instant::now(),
        };
        (request, rx)
    }
    
    /// Get time spent waiting in queue
    pub fn queue_time(&self) -> std::time::Duration {
        self.created_at.elapsed()
    }
    
    /// Notify completion (consumes the completion sender)
    pub fn notify_completion(self, result: GraphWriteResult) {
        if let Some(tx) = self.completion {
            let _ = tx.send(result);
        }
    }
    
    /// Get routing key for this request
    pub fn routing_key(&self) -> Option<&str> {
        self.op.routing_key()
    }
}

/// Result of a graph write operation
#[derive(Debug, Clone)]
pub struct GraphWriteResult {
    /// ID of the affected entity/relation
    pub id: String,
    
    /// Sequence number assigned
    pub sequence: u64,
    
    /// Shard ID where operation was performed
    pub shard_id: usize,
    
    /// Timestamp of operation
    pub timestamp: i64,
    
    /// Whether the operation was successful
    pub success: bool,
    
    /// Error message if failed
    pub error: Option<String>,
}

impl GraphWriteResult {
    /// Create a successful write result
    pub fn success(id: impl Into<String>, sequence: u64, shard_id: usize) -> Self {
        Self {
            id: id.into(),
            sequence,
            shard_id,
            timestamp: chrono_timestamp(),
            success: true,
            error: None,
        }
    }
    
    /// Create a failed write result
    pub fn failure(id: impl Into<String>, sequence: u64, error: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            sequence,
            shard_id: 0,
            timestamp: chrono_timestamp(),
            success: false,
            error: Some(error.into()),
        }
    }
}

/// Batch of graph write requests
pub struct GraphWriteBatch {
    /// Requests in this batch
    pub requests: Vec<GraphWriteRequest>,
    
    /// Batch creation time
    pub created_at: Instant,
    
    /// Total operation count
    pub op_count: usize,
}

impl GraphWriteBatch {
    /// Create a new empty batch
    pub fn new() -> Self {
        Self {
            requests: Vec::new(),
            created_at: Instant::now(),
            op_count: 0,
        }
    }
    
    /// Create batch with pre-allocated capacity
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            requests: Vec::with_capacity(capacity),
            created_at: Instant::now(),
            op_count: 0,
        }
    }
    
    /// Add a request to the batch
    pub fn push(&mut self, request: GraphWriteRequest) {
        self.op_count += request.op.op_count();
        self.requests.push(request);
    }
    
    /// Get number of requests in batch
    pub fn len(&self) -> usize {
        self.requests.len()
    }
    
    /// Check if batch is empty
    pub fn is_empty(&self) -> bool {
        self.requests.is_empty()
    }
    
    /// Get batch age
    pub fn age(&self) -> std::time::Duration {
        self.created_at.elapsed()
    }
    
    /// Clear and return all requests
    pub fn drain(&mut self) -> Vec<GraphWriteRequest> {
        self.op_count = 0;
        self.created_at = Instant::now();
        std::mem::take(&mut self.requests)
    }
}

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

/// Errors from graph store operations
#[derive(Debug, Clone, thiserror::Error)]
pub enum GraphStoreError {
    #[error("Write queue is full")]
    QueueFull,
    
    #[error("Write queue is closed")]
    QueueClosed,
    
    #[error("Write request timed out")]
    Timeout,
    
    #[error("Failed to receive completion notification")]
    CompletionFailed,
    
    #[error("Backpressure rejected request: queue={0:.2}%")]
    BackpressureRejected(f64),
    
    #[error("Shard {0} error: {1}")]
    ShardError(usize, String),
    
    #[error("Entity not found: {0}")]
    EntityNotFound(String),
    
    #[error("Relation not found: {0}")]
    RelationNotFound(String),
    
    #[error("IO error: {0}")]
    IoError(String),
}

impl From<std::io::Error> for GraphStoreError {
    fn from(e: std::io::Error) -> Self {
        GraphStoreError::IoError(e.to_string())
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::graph::{Entity, Relation, entity_types, relation_types};
    
    #[test]
    fn test_graph_write_op_routing_key() {
        let entity = Entity::new("Test", entity_types::PERSON);
        let op = GraphWriteOp::PutEntity(entity.clone());
        assert_eq!(op.routing_key(), Some(entity.id.as_str()));
        
        let op = GraphWriteOp::DeleteEntity("test-id".to_string());
        assert_eq!(op.routing_key(), Some("test-id"));
        
        let relation = Relation::new("source", "target", relation_types::FRIEND);
        let op = GraphWriteOp::PutRelation(relation);
        assert_eq!(op.routing_key(), Some("source"));
    }
    
    #[test]
    fn test_graph_write_op_count() {
        let entity = Entity::new("Test", entity_types::PERSON);
        let op = GraphWriteOp::PutEntity(entity.clone());
        assert_eq!(op.op_count(), 1);
        
        let batch = GraphWriteOp::Batch(vec![
            GraphWriteOp::PutEntity(entity.clone()),
            GraphWriteOp::DeleteEntity("test".to_string()),
        ]);
        assert_eq!(batch.op_count(), 2);
    }
    
    #[test]
    fn test_graph_write_request() {
        let entity = Entity::new("Test", entity_types::PERSON);
        let op = GraphWriteOp::PutEntity(entity);
        let request = GraphWriteRequest::new(op, 1);
        
        assert_eq!(request.sequence, 1);
        assert!(request.completion.is_none());
    }
    
    #[test]
    fn test_graph_write_result() {
        let result = GraphWriteResult::success("test-id", 1, 0);
        assert!(result.success);
        assert_eq!(result.id, "test-id");
        assert_eq!(result.shard_id, 0);
        
        let result = GraphWriteResult::failure("test-id", 1, "error message");
        assert!(!result.success);
        assert_eq!(result.error, Some("error message".to_string()));
    }
    
    #[test]
    fn test_graph_write_batch() {
        let mut batch = GraphWriteBatch::with_capacity(10);
        assert!(batch.is_empty());
        
        let entity = Entity::new("Test", entity_types::PERSON);
        let op = GraphWriteOp::PutEntity(entity);
        let request = GraphWriteRequest::new(op, 0);
        batch.push(request);
        
        assert_eq!(batch.len(), 1);
        assert_eq!(batch.op_count, 1);
        
        let requests = batch.drain();
        assert_eq!(requests.len(), 1);
        assert!(batch.is_empty());
    }
}
