//! WAL Compactor for merging WAL files into main data
//!
//! Periodically compacts WAL files by:
//! 1. Reading all entries since last compaction
//! 2. Applying them to the main data files
//! 3. Removing compacted WAL files

use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::interval;

use super::reader::WALReader;
use super::format::{WALEntry, WALOperation};
use crate::storage::concurrent::metrics::WriteMetrics;
use crate::storage::DocumentStore;

/// Configuration for WAL compactor
#[derive(Debug, Clone)]
pub struct CompactorConfig {
    /// Path to WAL directory
    pub wal_path: PathBuf,
    
    /// Compaction interval (seconds)
    pub interval_secs: u64,
    
    /// Minimum number of WAL files before compaction triggers
    pub min_wal_files: usize,
    
    /// Minimum total WAL size (bytes) before compaction triggers
    pub min_wal_bytes: usize,
    
    /// Number of old WAL files to retain after compaction
    pub retention_count: usize,
    
    /// Whether to auto-start compaction
    pub auto_compact: bool,
}

impl Default for CompactorConfig {
    fn default() -> Self {
        Self {
            wal_path: PathBuf::from("wal"),
            interval_secs: 60,
            min_wal_files: 3,
            min_wal_bytes: 10 * 1024 * 1024, // 10MB
            retention_count: 2,
            auto_compact: true,
        }
    }
}

impl CompactorConfig {
    /// Create config with specified path
    pub fn new(wal_path: impl Into<PathBuf>) -> Self {
        Self {
            wal_path: wal_path.into(),
            ..Default::default()
        }
    }
    
    /// Set compaction interval
    pub fn with_interval(mut self, secs: u64) -> Self {
        self.interval_secs = secs;
        self
    }
    
    /// Set retention count
    pub fn with_retention(mut self, count: usize) -> Self {
        self.retention_count = count;
        self
    }
}

/// WAL Compactor
pub struct WALCompactor {
    /// Configuration
    config: CompactorConfig,
    
    /// Metrics
    metrics: Arc<WriteMetrics>,
    
    /// Last compacted sequence
    last_compacted_seq: u64,
    
    /// Shutdown flag
    shutdown: bool,
}

impl WALCompactor {
    /// Create a new WAL compactor
    pub fn new(config: CompactorConfig, metrics: Arc<WriteMetrics>) -> Self {
        Self {
            config,
            metrics,
            last_compacted_seq: 0,
            shutdown: false,
        }
    }
    
    /// Check if compaction is needed
    pub fn needs_compaction(&self) -> std::io::Result<bool> {
        let reader = WALReader::new(&self.config.wal_path);
        let files = reader.list_files()?;
        
        // Check file count
        if files.len() >= self.config.min_wal_files {
            return Ok(true);
        }
        
        // Check total size
        let mut total_size = 0;
        for file in &files {
            if let Ok(metadata) = std::fs::metadata(file) {
                total_size += metadata.len() as usize;
            }
        }
        
        if total_size >= self.config.min_wal_bytes {
            return Ok(true);
        }
        
        Ok(false)
    }
    
    /// Run a single compaction cycle
    pub fn compact(&mut self, doc_store: &DocumentStore) -> std::io::Result<CompactionResult> {
        let reader = WALReader::new(&self.config.wal_path);
        let files = reader.list_files()?;
        
        if files.is_empty() {
            return Ok(CompactionResult::default());
        }
        
        let mut result = CompactionResult::default();
        
        // Read all entries since last compaction
        let entries = reader.read_from_sequence(self.last_compacted_seq + 1)?;
        
        tracing::info!(
            "Starting WAL compaction: {} entries from {} files",
            entries.len(),
            files.len()
        );
        
        // Apply entries to document store
        for entry in &entries {
            match self.apply_entry(entry, doc_store) {
                Ok(()) => {
                    result.entries_applied += 1;
                    result.last_sequence = entry.sequence;
                }
                Err(e) => {
                    tracing::warn!("Failed to apply WAL entry {}: {}", entry.sequence, e);
                    result.entries_failed += 1;
                }
            }
        }
        
        // Update last compacted sequence
        if result.last_sequence > 0 {
            self.last_compacted_seq = result.last_sequence;
        }
        
        // Flush document store
        doc_store.flush()?;
        
        // Clean up old WAL files (keep retention_count)
        let files_to_remove = if files.len() > self.config.retention_count {
            files.len() - self.config.retention_count
        } else {
            0
        };
        
        for file in files.iter().take(files_to_remove) {
            match std::fs::remove_file(file) {
                Ok(()) => {
                    result.files_removed += 1;
                    tracing::debug!("Removed WAL file: {:?}", file);
                }
                Err(e) => {
                    tracing::warn!("Failed to remove WAL file {:?}: {}", file, e);
                }
            }
        }
        
        self.metrics.record_compaction();
        
        tracing::info!(
            "WAL compaction complete: applied={}, failed={}, removed={}",
            result.entries_applied,
            result.entries_failed,
            result.files_removed
        );
        
        Ok(result)
    }
    
    /// Apply a single WAL entry to document store
    fn apply_entry(&self, entry: &WALEntry, doc_store: &DocumentStore) -> std::io::Result<()> {
        match entry.operation {
            WALOperation::Insert | WALOperation::Update => {
                if let Some(ref node) = entry.data {
                    doc_store.put(node.clone())?;
                }
            }
            WALOperation::Delete => {
                doc_store.delete(&entry.doc_id);
            }
        }
        Ok(())
    }
    
    /// Run compaction loop (async)
    pub async fn run_loop(mut self, doc_store: Arc<DocumentStore>) {
        let mut timer = interval(Duration::from_secs(self.config.interval_secs));
        
        tracing::info!(
            "WAL compactor started: interval={}s, retention={}",
            self.config.interval_secs,
            self.config.retention_count
        );
        
        loop {
            timer.tick().await;
            
            if self.shutdown {
                break;
            }
            
            // Check if compaction needed
            match self.needs_compaction() {
                Ok(true) => {
                    if let Err(e) = self.compact(&doc_store) {
                        tracing::error!("Compaction failed: {}", e);
                    }
                }
                Ok(false) => {
                    tracing::debug!("Compaction not needed");
                }
                Err(e) => {
                    tracing::error!("Failed to check compaction status: {}", e);
                }
            }
        }
        
        tracing::info!("WAL compactor stopped");
    }
    
    /// Request shutdown
    pub fn shutdown(&mut self) {
        self.shutdown = true;
    }
    
    /// Get last compacted sequence
    pub fn last_compacted_sequence(&self) -> u64 {
        self.last_compacted_seq
    }
    
    /// Set last compacted sequence (for recovery)
    pub fn set_last_compacted_sequence(&mut self, seq: u64) {
        self.last_compacted_seq = seq;
    }
}

/// Result of a compaction operation
#[derive(Debug, Default)]
pub struct CompactionResult {
    /// Number of entries successfully applied
    pub entries_applied: usize,
    
    /// Number of entries that failed to apply
    pub entries_failed: usize,
    
    /// Number of WAL files removed
    pub files_removed: usize,
    
    /// Last sequence number processed
    pub last_sequence: u64,
}

/// Perform crash recovery by replaying WAL
pub fn recover_from_wal(
    wal_path: &Path,
    doc_store: &DocumentStore,
    last_known_seq: u64,
) -> std::io::Result<RecoveryStats> {
    let reader = WALReader::new(wal_path);
    let entries = reader.read_from_sequence(last_known_seq + 1)?;
    
    let mut stats = RecoveryStats::default();
    stats.entries_found = entries.len();
    
    tracing::info!(
        "Starting WAL recovery: {} entries to replay from seq {}",
        entries.len(),
        last_known_seq + 1
    );
    
    for entry in entries {
        if !entry.verify_checksum() {
            tracing::warn!("Skipping entry {} with invalid checksum", entry.sequence);
            stats.entries_skipped += 1;
            continue;
        }
        
        match entry.operation {
            WALOperation::Insert | WALOperation::Update => {
                if let Some(node) = entry.data {
                    if let Err(e) = doc_store.put(node) {
                        tracing::warn!("Failed to replay entry {}: {}", entry.sequence, e);
                        stats.entries_failed += 1;
                        continue;
                    }
                }
            }
            WALOperation::Delete => {
                doc_store.delete(&entry.doc_id);
            }
        }
        
        stats.entries_replayed += 1;
        stats.last_sequence = entry.sequence;
    }
    
    // Flush changes
    doc_store.flush()?;
    
    tracing::info!(
        "WAL recovery complete: replayed={}, skipped={}, failed={}",
        stats.entries_replayed,
        stats.entries_skipped,
        stats.entries_failed
    );
    
    Ok(stats)
}

/// Statistics from WAL recovery
#[derive(Debug, Default)]
pub struct RecoveryStats {
    /// Total entries found in WAL
    pub entries_found: usize,
    
    /// Entries successfully replayed
    pub entries_replayed: usize,
    
    /// Entries skipped (checksum errors)
    pub entries_skipped: usize,
    
    /// Entries that failed to replay
    pub entries_failed: usize,
    
    /// Last sequence number replayed
    pub last_sequence: u64,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::wal::writer::{WALWriter, WALWriterConfig};
    use crate::types::MemoryNode;
    use tempfile::tempdir;
    
    #[test]
    fn test_compactor_basic() {
        let dir = tempdir().unwrap();
        let wal_path = dir.path().join("wal");
        let docs_path = dir.path().join("docs.jsonl");
        
        std::fs::create_dir_all(&wal_path).unwrap();
        
        let metrics = Arc::new(WriteMetrics::new());
        
        // Write some WAL entries
        {
            let config = WALWriterConfig::new(&wal_path);
            let mut writer = WALWriter::new(config, 0, metrics.clone()).unwrap();
            
            for i in 0..10 {
                let node = MemoryNode::new(&format!("content {}", i));
                let entry = WALEntry::insert(writer.next_sequence(), "default".to_string(), node);
                writer.append(&entry).unwrap();
            }
            
            writer.close().unwrap();
        }
        
        // Create document store
        let doc_store = DocumentStore::open_or_create(&docs_path).unwrap();
        
        // Run compaction
        let config = CompactorConfig::new(&wal_path)
            .with_retention(1);
        let mut compactor = WALCompactor::new(config, metrics);
        
        let result = compactor.compact(&doc_store).unwrap();
        
        assert_eq!(result.entries_applied, 10);
        assert_eq!(result.entries_failed, 0);
        
        // Verify documents were written
        assert_eq!(doc_store.len(), 10);
    }
    
    #[test]
    fn test_recovery_from_wal() {
        let dir = tempdir().unwrap();
        let wal_path = dir.path().join("wal");
        let docs_path = dir.path().join("docs.jsonl");
        
        std::fs::create_dir_all(&wal_path).unwrap();
        
        let metrics = Arc::new(WriteMetrics::new());
        
        // Write WAL entries
        {
            let config = WALWriterConfig::new(&wal_path);
            let mut writer = WALWriter::new(config, 0, metrics.clone()).unwrap();
            
            for i in 0..5 {
                let node = MemoryNode::new(&format!("content {}", i));
                let entry = WALEntry::insert(writer.next_sequence(), "default".to_string(), node);
                writer.append(&entry).unwrap();
            }
            
            writer.close().unwrap();
        }
        
        // Create fresh document store (simulating crash recovery)
        let doc_store = DocumentStore::open_or_create(&docs_path).unwrap();
        assert_eq!(doc_store.len(), 0);
        
        // Recover from WAL
        let stats = recover_from_wal(&wal_path, &doc_store, 0).unwrap();
        
        assert_eq!(stats.entries_replayed, 5);
        assert_eq!(doc_store.len(), 5);
    }
}
