//! Write Queue for high-concurrency write operations
//!
//! Provides a bounded async channel-based queue that decouples producers
//! (write requests) from consumers (batch processors).

use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
use tokio::sync::oneshot;

use crate::types::MemoryNode;
use super::metrics::WriteMetrics;

/// Configuration for the write queue
#[derive(Debug, Clone)]
pub struct WriteQueueConfig {
    /// Maximum number of pending write requests
    pub capacity: usize,
    
    /// Batch size for processing
    pub batch_size: usize,
    
    /// Maximum time to wait before flushing a partial batch (ms)
    pub flush_interval_ms: u64,
    
    /// Timeout for sending to queue (ms), 0 = no timeout
    pub send_timeout_ms: u64,
}

impl Default for WriteQueueConfig {
    fn default() -> Self {
        Self {
            capacity: 10_000,
            batch_size: 100,
            flush_interval_ms: 50,
            send_timeout_ms: 1000,
        }
    }
}

impl WriteQueueConfig {
    /// Create config optimized for high throughput
    pub fn high_throughput() -> Self {
        Self {
            capacity: 50_000,
            batch_size: 500,
            flush_interval_ms: 100,
            send_timeout_ms: 5000,
        }
    }
    
    /// Create config optimized for low latency
    pub fn low_latency() -> Self {
        Self {
            capacity: 5_000,
            batch_size: 50,
            flush_interval_ms: 10,
            send_timeout_ms: 500,
        }
    }
}

/// Write request with optional completion notification
pub struct WriteRequest {
    /// The document to write
    pub node: MemoryNode,
    
    /// Optional channel to notify completion
    pub completion: Option<oneshot::Sender<WriteResult>>,
    
    /// Timestamp when request was created
    pub created_at: Instant,
    
    /// Sequence number for ordering
    pub sequence: u64,
}

impl WriteRequest {
    /// Create a new write request without completion notification
    pub fn new(node: MemoryNode, sequence: u64) -> Self {
        Self {
            node,
            completion: None,
            created_at: Instant::now(),
            sequence,
        }
    }
    
    /// Create a write request with completion notification
    pub fn with_completion(node: MemoryNode, sequence: u64) -> (Self, oneshot::Receiver<WriteResult>) {
        let (tx, rx) = oneshot::channel();
        let request = Self {
            node,
            completion: Some(tx),
            created_at: Instant::now(),
            sequence,
        };
        (request, rx)
    }
    
    /// Get time spent waiting in queue
    pub fn queue_time(&self) -> Duration {
        self.created_at.elapsed()
    }
    
    /// Notify completion (consumes the completion sender)
    pub fn notify_completion(self, result: WriteResult) {
        if let Some(tx) = self.completion {
            let _ = tx.send(result);
        }
    }
}

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

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

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

/// Write Queue for buffering and batching write requests
pub struct WriteQueue {
    /// Sender for enqueueing requests
    sender: mpsc::Sender<WriteRequest>,
    
    /// Receiver for processing requests (moved to worker)
    /// Wrapped in Mutex for interior mutability
    receiver: parking_lot::Mutex<Option<mpsc::Receiver<WriteRequest>>>,
    
    /// Configuration
    config: WriteQueueConfig,
    
    /// Metrics reference
    metrics: Arc<WriteMetrics>,
    
    /// Sequence counter
    sequence: std::sync::atomic::AtomicU64,
}

impl WriteQueue {
    /// Create a new write queue
    pub fn new(config: WriteQueueConfig, metrics: Arc<WriteMetrics>) -> Self {
        let (sender, receiver) = mpsc::channel(config.capacity);
        
        Self {
            sender,
            receiver: parking_lot::Mutex::new(Some(receiver)),
            config,
            metrics,
            sequence: std::sync::atomic::AtomicU64::new(0),
        }
    }
    
    /// Get the next sequence number
    pub fn next_sequence(&self) -> u64 {
        self.sequence.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
    }
    
    /// Get current queue length (approximate)
    pub fn len(&self) -> usize {
        self.config.capacity - self.sender.capacity()
    }
    
    /// Check if queue is empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
    
    /// Get queue capacity
    pub fn capacity(&self) -> usize {
        self.config.capacity
    }
    
    /// Get queue fill ratio (0.0 - 1.0)
    pub fn fill_ratio(&self) -> f64 {
        self.len() as f64 / self.capacity() as f64
    }
    
    /// Get configuration
    pub fn config(&self) -> &WriteQueueConfig {
        &self.config
    }
    
    /// Enqueue a write request (async, may block if queue is full)
    pub async fn enqueue(&self, node: MemoryNode) -> Result<u64, WriteQueueError> {
        let sequence = self.next_sequence();
        let request = WriteRequest::new(node, sequence);
        
        self.metrics.record_request();
        self.metrics.update_queue_size(self.len());
        
        if self.config.send_timeout_ms > 0 {
            match tokio::time::timeout(
                Duration::from_millis(self.config.send_timeout_ms),
                self.sender.send(request)
            ).await {
                Ok(Ok(())) => Ok(sequence),
                Ok(Err(_)) => {
                    self.metrics.record_failure();
                    Err(WriteQueueError::QueueClosed)
                }
                Err(_) => {
                    self.metrics.record_rejection();
                    Err(WriteQueueError::Timeout)
                }
            }
        } else {
            self.sender.send(request).await
                .map(|_| sequence)
                .map_err(|_| {
                    self.metrics.record_failure();
                    WriteQueueError::QueueClosed
                })
        }
    }
    
    /// Enqueue a write request with completion notification
    pub async fn enqueue_sync(&self, node: MemoryNode) -> Result<WriteResult, WriteQueueError> {
        let sequence = self.next_sequence();
        let (request, rx) = WriteRequest::with_completion(node, sequence);
        
        self.metrics.record_request();
        self.metrics.update_queue_size(self.len());
        
        // Send to queue
        if self.config.send_timeout_ms > 0 {
            match tokio::time::timeout(
                Duration::from_millis(self.config.send_timeout_ms),
                self.sender.send(request)
            ).await {
                Ok(Ok(())) => {}
                Ok(Err(_)) => {
                    self.metrics.record_failure();
                    return Err(WriteQueueError::QueueClosed);
                }
                Err(_) => {
                    self.metrics.record_rejection();
                    return Err(WriteQueueError::Timeout);
                }
            }
        } else {
            self.sender.send(request).await.map_err(|_| {
                self.metrics.record_failure();
                WriteQueueError::QueueClosed
            })?;
        }
        
        // Wait for completion
        rx.await.map_err(|_| WriteQueueError::CompletionFailed)
    }
    
    /// Try to enqueue without blocking
    pub fn try_enqueue(&self, node: MemoryNode) -> Result<u64, WriteQueueError> {
        let sequence = self.next_sequence();
        let request = WriteRequest::new(node, sequence);
        
        self.metrics.record_request();
        
        match self.sender.try_send(request) {
            Ok(()) => {
                self.metrics.update_queue_size(self.len());
                Ok(sequence)
            }
            Err(mpsc::error::TrySendError::Full(_)) => {
                self.metrics.record_rejection();
                Err(WriteQueueError::QueueFull)
            }
            Err(mpsc::error::TrySendError::Closed(_)) => {
                self.metrics.record_failure();
                Err(WriteQueueError::QueueClosed)
            }
        }
    }
    
    /// Take the receiver (for worker thread)
    /// Uses interior mutability so this can be called on &self
    pub fn take_receiver(&self) -> Option<mpsc::Receiver<WriteRequest>> {
        self.receiver.lock().take()
    }
    
    /// Create a new sender handle (for cloning to multiple producers)
    pub fn sender(&self) -> mpsc::Sender<WriteRequest> {
        self.sender.clone()
    }
    
    /// Close the queue (no more sends allowed)
    pub fn close(&self) {
        // Dropping all senders will close the channel
        // The receiver will continue to drain remaining items
    }
}

/// Errors that can occur with the write queue
#[derive(Debug, Clone, thiserror::Error)]
pub enum WriteQueueError {
    #[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,
}

/// Batch of write requests ready for processing
pub struct WriteBatch {
    /// Requests in this batch
    pub requests: Vec<WriteRequest>,
    
    /// Batch creation time
    pub created_at: Instant,
    
    /// Total bytes in batch (approximate)
    pub total_bytes: usize,
}

impl WriteBatch {
    /// Create a new empty batch
    pub fn new() -> Self {
        Self {
            requests: Vec::new(),
            created_at: Instant::now(),
            total_bytes: 0,
        }
    }
    
    /// Create batch with pre-allocated capacity
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            requests: Vec::with_capacity(capacity),
            created_at: Instant::now(),
            total_bytes: 0,
        }
    }
    
    /// Add a request to the batch
    pub fn push(&mut self, request: WriteRequest) {
        // Estimate bytes (content + metadata overhead)
        let bytes = request.node.content.len() + 256;
        self.total_bytes += bytes;
        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) -> Duration {
        self.created_at.elapsed()
    }
    
    /// Clear and return all requests
    pub fn drain(&mut self) -> Vec<WriteRequest> {
        self.total_bytes = 0;
        self.created_at = Instant::now();
        std::mem::take(&mut self.requests)
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    
    #[tokio::test]
    async fn test_write_queue_basic() {
        let config = WriteQueueConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let queue = WriteQueue::new(config, metrics.clone());
        
        let node = MemoryNode::new("test content");
        let seq = queue.enqueue(node).await.unwrap();
        
        assert_eq!(seq, 0);
        assert_eq!(queue.len(), 1);
    }
    
    #[tokio::test]
    async fn test_write_queue_sequence() {
        let config = WriteQueueConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let queue = WriteQueue::new(config, metrics);
        
        for i in 0..10 {
            let node = MemoryNode::new(&format!("content {}", i));
            let seq = queue.enqueue(node).await.unwrap();
            assert_eq!(seq, i);
        }
        
        assert_eq!(queue.len(), 10);
    }
    
    #[test]
    fn test_try_enqueue() {
        let config = WriteQueueConfig {
            capacity: 2,
            ..Default::default()
        };
        let metrics = Arc::new(WriteMetrics::new());
        let queue = WriteQueue::new(config, metrics);
        
        // First two should succeed
        let node1 = MemoryNode::new("content 1");
        assert!(queue.try_enqueue(node1).is_ok());
        
        let node2 = MemoryNode::new("content 2");
        assert!(queue.try_enqueue(node2).is_ok());
        
        // Third should fail (queue full)
        let node3 = MemoryNode::new("content 3");
        assert!(matches!(queue.try_enqueue(node3), Err(WriteQueueError::QueueFull)));
    }
    
    #[test]
    fn test_write_batch() {
        let mut batch = WriteBatch::with_capacity(10);
        
        for i in 0..5 {
            let node = MemoryNode::new(&format!("content {}", i));
            let request = WriteRequest::new(node, i);
            batch.push(request);
        }
        
        assert_eq!(batch.len(), 5);
        assert!(!batch.is_empty());
        assert!(batch.total_bytes > 0);
        
        let requests = batch.drain();
        assert_eq!(requests.len(), 5);
        assert!(batch.is_empty());
    }
}
