//! Metrics for monitoring high-concurrency write performance
//!
//! Provides real-time statistics on write operations, queue status,
//! and system health.

use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Instant;

/// Write operation metrics
#[derive(Debug, Default)]
pub struct WriteMetrics {
    /// Total number of write requests received
    pub total_requests: AtomicU64,
    
    /// Total number of successful writes
    pub successful_writes: AtomicU64,
    
    /// Total number of failed writes
    pub failed_writes: AtomicU64,
    
    /// Total number of requests rejected due to backpressure
    pub rejected_requests: AtomicU64,
    
    /// Total number of batches flushed
    pub batches_flushed: AtomicU64,
    
    /// Total documents written in batches
    pub batch_documents: AtomicU64,
    
    /// Current queue size across all shards
    pub current_queue_size: AtomicUsize,
    
    /// Maximum queue size observed
    pub peak_queue_size: AtomicUsize,
    
    /// Total bytes written
    pub bytes_written: AtomicU64,
    
    /// Total WAL bytes written
    pub wal_bytes_written: AtomicU64,
    
    /// Total time spent in queue (microseconds)
    pub total_queue_time_us: AtomicU64,
    
    /// Total time spent writing (microseconds)
    pub total_write_time_us: AtomicU64,
    
    /// Number of WAL file rotations
    pub wal_rotations: AtomicU64,
    
    /// Number of compactions performed
    pub compactions: AtomicU64,
}

impl WriteMetrics {
    /// Create new metrics instance
    pub fn new() -> Self {
        Self::default()
    }
    
    /// Record a new write request
    pub fn record_request(&self) {
        self.total_requests.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Record a successful write
    pub fn record_success(&self) {
        self.successful_writes.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Record a failed write
    pub fn record_failure(&self) {
        self.failed_writes.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Record a rejected request (backpressure)
    pub fn record_rejection(&self) {
        self.rejected_requests.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Record a batch flush
    pub fn record_batch_flush(&self, document_count: usize, bytes: usize) {
        self.batches_flushed.fetch_add(1, Ordering::Relaxed);
        self.batch_documents.fetch_add(document_count as u64, Ordering::Relaxed);
        self.bytes_written.fetch_add(bytes as u64, Ordering::Relaxed);
    }
    
    /// Update current queue size
    pub fn update_queue_size(&self, size: usize) {
        self.current_queue_size.store(size, Ordering::Relaxed);
        
        // Update peak if necessary
        let mut peak = self.peak_queue_size.load(Ordering::Relaxed);
        while size > peak {
            match self.peak_queue_size.compare_exchange_weak(
                peak,
                size,
                Ordering::Relaxed,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,
                Err(current) => peak = current,
            }
        }
    }
    
    /// Record queue wait time
    pub fn record_queue_time(&self, duration_us: u64) {
        self.total_queue_time_us.fetch_add(duration_us, Ordering::Relaxed);
    }
    
    /// Record write time
    pub fn record_write_time(&self, duration_us: u64) {
        self.total_write_time_us.fetch_add(duration_us, Ordering::Relaxed);
    }
    
    /// Record WAL bytes written
    pub fn record_wal_bytes(&self, bytes: usize) {
        self.wal_bytes_written.fetch_add(bytes as u64, Ordering::Relaxed);
    }
    
    /// Record WAL rotation
    pub fn record_wal_rotation(&self) {
        self.wal_rotations.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Record compaction
    pub fn record_compaction(&self) {
        self.compactions.fetch_add(1, Ordering::Relaxed);
    }
    
    /// Get snapshot of current metrics
    pub fn snapshot(&self) -> MetricsSnapshot {
        MetricsSnapshot {
            total_requests: self.total_requests.load(Ordering::Relaxed),
            successful_writes: self.successful_writes.load(Ordering::Relaxed),
            failed_writes: self.failed_writes.load(Ordering::Relaxed),
            rejected_requests: self.rejected_requests.load(Ordering::Relaxed),
            batches_flushed: self.batches_flushed.load(Ordering::Relaxed),
            batch_documents: self.batch_documents.load(Ordering::Relaxed),
            current_queue_size: self.current_queue_size.load(Ordering::Relaxed),
            peak_queue_size: self.peak_queue_size.load(Ordering::Relaxed),
            bytes_written: self.bytes_written.load(Ordering::Relaxed),
            wal_bytes_written: self.wal_bytes_written.load(Ordering::Relaxed),
            avg_queue_time_us: self.average_queue_time_us(),
            avg_write_time_us: self.average_write_time_us(),
            wal_rotations: self.wal_rotations.load(Ordering::Relaxed),
            compactions: self.compactions.load(Ordering::Relaxed),
        }
    }
    
    /// Calculate average queue time in microseconds
    fn average_queue_time_us(&self) -> f64 {
        let total = self.total_queue_time_us.load(Ordering::Relaxed);
        let count = self.successful_writes.load(Ordering::Relaxed);
        if count == 0 {
            0.0
        } else {
            total as f64 / count as f64
        }
    }
    
    /// Calculate average write time in microseconds
    fn average_write_time_us(&self) -> f64 {
        let total = self.total_write_time_us.load(Ordering::Relaxed);
        let count = self.batches_flushed.load(Ordering::Relaxed);
        if count == 0 {
            0.0
        } else {
            total as f64 / count as f64
        }
    }
    
    /// Get current throughput (writes per second)
    /// Note: This is a point-in-time calculation based on total writes
    pub fn throughput(&self, elapsed_secs: f64) -> f64 {
        if elapsed_secs <= 0.0 {
            return 0.0;
        }
        self.successful_writes.load(Ordering::Relaxed) as f64 / elapsed_secs
    }
    
    /// Reset all metrics
    pub fn reset(&self) {
        self.total_requests.store(0, Ordering::Relaxed);
        self.successful_writes.store(0, Ordering::Relaxed);
        self.failed_writes.store(0, Ordering::Relaxed);
        self.rejected_requests.store(0, Ordering::Relaxed);
        self.batches_flushed.store(0, Ordering::Relaxed);
        self.batch_documents.store(0, Ordering::Relaxed);
        self.current_queue_size.store(0, Ordering::Relaxed);
        self.peak_queue_size.store(0, Ordering::Relaxed);
        self.bytes_written.store(0, Ordering::Relaxed);
        self.wal_bytes_written.store(0, Ordering::Relaxed);
        self.total_queue_time_us.store(0, Ordering::Relaxed);
        self.total_write_time_us.store(0, Ordering::Relaxed);
        self.wal_rotations.store(0, Ordering::Relaxed);
        self.compactions.store(0, Ordering::Relaxed);
    }
}

/// Snapshot of metrics at a point in time
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MetricsSnapshot {
    pub total_requests: u64,
    pub successful_writes: u64,
    pub failed_writes: u64,
    pub rejected_requests: u64,
    pub batches_flushed: u64,
    pub batch_documents: u64,
    pub current_queue_size: usize,
    pub peak_queue_size: usize,
    pub bytes_written: u64,
    pub wal_bytes_written: u64,
    pub avg_queue_time_us: f64,
    pub avg_write_time_us: f64,
    pub wal_rotations: u64,
    pub compactions: u64,
}

impl MetricsSnapshot {
    /// Calculate success rate as a percentage
    pub fn success_rate(&self) -> f64 {
        if self.total_requests == 0 {
            100.0
        } else {
            (self.successful_writes as f64 / self.total_requests as f64) * 100.0
        }
    }
    
    /// Calculate rejection rate as a percentage
    pub fn rejection_rate(&self) -> f64 {
        if self.total_requests == 0 {
            0.0
        } else {
            (self.rejected_requests as f64 / self.total_requests as f64) * 100.0
        }
    }
    
    /// Calculate average batch size
    pub fn avg_batch_size(&self) -> f64 {
        if self.batches_flushed == 0 {
            0.0
        } else {
            self.batch_documents as f64 / self.batches_flushed as f64
        }
    }
}

/// Timer utility for measuring operation duration
pub struct Timer {
    start: Instant,
}

impl Timer {
    /// Start a new timer
    pub fn start() -> Self {
        Self { start: Instant::now() }
    }
    
    /// Get elapsed time in microseconds
    pub fn elapsed_us(&self) -> u64 {
        self.start.elapsed().as_micros() as u64
    }
    
    /// Get elapsed time in milliseconds
    pub fn elapsed_ms(&self) -> u64 {
        self.start.elapsed().as_millis() as u64
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_metrics_basic() {
        let metrics = WriteMetrics::new();
        
        metrics.record_request();
        metrics.record_request();
        metrics.record_success();
        metrics.record_failure();
        
        let snapshot = metrics.snapshot();
        assert_eq!(snapshot.total_requests, 2);
        assert_eq!(snapshot.successful_writes, 1);
        assert_eq!(snapshot.failed_writes, 1);
    }
    
    #[test]
    fn test_peak_queue_tracking() {
        let metrics = WriteMetrics::new();
        
        metrics.update_queue_size(100);
        metrics.update_queue_size(500);
        metrics.update_queue_size(200);
        
        let snapshot = metrics.snapshot();
        assert_eq!(snapshot.current_queue_size, 200);
        assert_eq!(snapshot.peak_queue_size, 500);
    }
    
    #[test]
    fn test_batch_metrics() {
        let metrics = WriteMetrics::new();
        
        metrics.record_batch_flush(50, 1024);
        metrics.record_batch_flush(100, 2048);
        
        let snapshot = metrics.snapshot();
        assert_eq!(snapshot.batches_flushed, 2);
        assert_eq!(snapshot.batch_documents, 150);
        assert_eq!(snapshot.bytes_written, 3072);
        assert_eq!(snapshot.avg_batch_size(), 75.0);
    }
}
