//! WAL Writer for durable append-only logging
//!
//! Provides efficient append-only writes with configurable sync modes.

use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::io::{self, Write, BufWriter};
use std::fs::{File, OpenOptions};

use tokio::io::{AsyncWriteExt, BufWriter as AsyncBufWriter};
use tokio::fs::File as AsyncFile;

use super::format::{WALEntry, WALHeader, WALFooter};
use crate::storage::concurrent::metrics::WriteMetrics;
use std::sync::Arc;

/// Sync mode for WAL writes
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncMode {
    /// No explicit sync - fastest but may lose data on crash
    None,
    
    /// Sync after each batch - good balance of safety and performance
    Batch,
    
    /// Sync after every write - safest but slowest
    Every,
}

impl Default for SyncMode {
    fn default() -> Self {
        SyncMode::Batch
    }
}

/// Configuration for WAL writer
#[derive(Debug, Clone)]
pub struct WALWriterConfig {
    /// Base directory for WAL files
    pub path: PathBuf,
    
    /// Maximum file size before rotation (bytes)
    pub max_file_size: usize,
    
    /// Sync mode
    pub sync_mode: SyncMode,
    
    /// Number of old WAL files to retain
    pub retention_count: usize,
    
    /// Buffer size for writes
    pub buffer_size: usize,
}

impl Default for WALWriterConfig {
    fn default() -> Self {
        Self {
            path: PathBuf::from("wal"),
            max_file_size: 64 * 1024 * 1024, // 64MB
            sync_mode: SyncMode::Batch,
            retention_count: 10,
            buffer_size: 64 * 1024, // 64KB buffer
        }
    }
}

impl WALWriterConfig {
    /// Create config with specified path
    pub fn new(path: impl Into<PathBuf>) -> Self {
        Self {
            path: path.into(),
            ..Default::default()
        }
    }
    
    /// Set sync mode
    pub fn with_sync_mode(mut self, mode: SyncMode) -> Self {
        self.sync_mode = mode;
        self
    }
    
    /// Set max file size
    pub fn with_max_file_size(mut self, size: usize) -> Self {
        self.max_file_size = size;
        self
    }
}

/// Synchronous WAL Writer
pub struct WALWriter {
    /// Configuration
    config: WALWriterConfig,
    
    /// Shard ID
    shard_id: usize,
    
    /// Current file path
    current_file_path: PathBuf,
    
    /// Current file writer
    writer: Option<BufWriter<File>>,
    
    /// Current file size
    current_size: usize,
    
    /// Sequence counter
    sequence: AtomicU64,
    
    /// Entry count in current file
    entry_count: u32,
    
    /// Running checksum
    running_checksum: u32,
    
    /// Metrics
    metrics: Arc<WriteMetrics>,
}

impl WALWriter {
    /// Create a new WAL writer
    pub fn new(config: WALWriterConfig, shard_id: usize, metrics: Arc<WriteMetrics>) -> io::Result<Self> {
        // Create WAL directory if needed
        std::fs::create_dir_all(&config.path)?;
        
        let mut writer = Self {
            config,
            shard_id,
            current_file_path: PathBuf::new(),
            writer: None,
            current_size: 0,
            sequence: AtomicU64::new(0),
            entry_count: 0,
            running_checksum: 0,
            metrics,
        };
        
        // Find highest existing sequence number
        writer.recover_sequence()?;
        
        // Open new file
        writer.rotate()?;
        
        Ok(writer)
    }
    
    /// Recover sequence number from existing WAL files
    fn recover_sequence(&mut self) -> io::Result<()> {
        let mut max_seq: u64 = 0;
        
        if let Ok(entries) = std::fs::read_dir(&self.config.path) {
            for entry in entries.filter_map(|e| e.ok()) {
                let path = entry.path();
                if path.extension().map(|e| e == "wal").unwrap_or(false) {
                    // Parse sequence from filename (wal_SHARD_SEQ.wal)
                    if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                        let parts: Vec<&str> = stem.split('_').collect();
                        if parts.len() >= 3 {
                            if let Ok(seq) = parts[2].parse::<u64>() {
                                max_seq = max_seq.max(seq);
                            }
                        }
                    }
                }
            }
        }
        
        self.sequence.store(max_seq, Ordering::SeqCst);
        Ok(())
    }
    
    /// Get next sequence number
    pub fn next_sequence(&self) -> u64 {
        self.sequence.fetch_add(1, Ordering::SeqCst)
    }
    
    /// Get current sequence number
    pub fn current_sequence(&self) -> u64 {
        self.sequence.load(Ordering::SeqCst)
    }
    
    /// Generate filename for a WAL file
    fn generate_filename(&self, start_seq: u64) -> PathBuf {
        self.config.path.join(format!("wal_{}_{:016}.wal", self.shard_id, start_seq))
    }
    
    /// Rotate to a new WAL file
    pub fn rotate(&mut self) -> io::Result<()> {
        // Finalize current file if open
        if let Some(mut writer) = self.writer.take() {
            // Write footer
            let footer = WALFooter::new(
                self.entry_count,
                self.current_sequence(),
                self.running_checksum,
            );
            let footer_bytes = serde_json::to_vec(&footer)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            writer.write_all(&(footer_bytes.len() as u32).to_le_bytes())?;
            writer.write_all(&footer_bytes)?;
            writer.flush()?;
            
            self.metrics.record_wal_rotation();
        }
        
        // Open new file
        let start_seq = self.next_sequence();
        let path = self.generate_filename(start_seq);
        
        let file = OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(&path)?;
        
        let mut writer = BufWriter::with_capacity(self.config.buffer_size, file);
        
        // Write header
        let header = WALHeader::new(self.shard_id, start_seq);
        let header_bytes = serde_json::to_vec(&header)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        writer.write_all(&(header_bytes.len() as u32).to_le_bytes())?;
        writer.write_all(&header_bytes)?;
        
        self.current_file_path = path;
        self.writer = Some(writer);
        self.current_size = WALHeader::serialized_size();
        self.entry_count = 0;
        self.running_checksum = 0;
        
        Ok(())
    }
    
    /// Append an entry to the WAL
    pub fn append(&mut self, entry: &WALEntry) -> io::Result<u64> {
        // Check if rotation needed
        let entry_bytes = entry.to_bytes()
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
        
        let entry_size = 4 + entry_bytes.len(); // length prefix + data
        
        if self.current_size + entry_size > self.config.max_file_size {
            self.rotate()?;
        }
        
        // Write entry
        let writer = self.writer.as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "WAL not open"))?;
        
        writer.write_all(&(entry_bytes.len() as u32).to_le_bytes())?;
        writer.write_all(&entry_bytes)?;
        
        // Update state
        self.current_size += entry_size;
        self.entry_count += 1;
        
        // Update running checksum
        let mut hasher = crc32fast::Hasher::new_with_initial(self.running_checksum);
        hasher.update(&entry_bytes);
        self.running_checksum = hasher.finalize();
        
        // Sync based on mode
        match self.config.sync_mode {
            SyncMode::Every => {
                writer.flush()?;
                writer.get_ref().sync_data()?;
            }
            _ => {}
        }
        
        self.metrics.record_wal_bytes(entry_size);
        
        Ok(entry.sequence)
    }
    
    /// Append multiple entries in a batch
    pub fn append_batch(&mut self, entries: &[WALEntry]) -> io::Result<Vec<u64>> {
        let mut sequences = Vec::with_capacity(entries.len());
        
        for entry in entries {
            let seq = self.append(entry)?;
            sequences.push(seq);
        }
        
        // Sync if batch mode
        if self.config.sync_mode == SyncMode::Batch {
            self.sync()?;
        }
        
        Ok(sequences)
    }
    
    /// Flush and sync to disk
    pub fn sync(&mut self) -> io::Result<()> {
        if let Some(ref mut writer) = self.writer {
            writer.flush()?;
            writer.get_ref().sync_data()?;
        }
        Ok(())
    }
    
    /// Flush buffer (no sync)
    pub fn flush(&mut self) -> io::Result<()> {
        if let Some(ref mut writer) = self.writer {
            writer.flush()?;
        }
        Ok(())
    }
    
    /// Close the WAL writer
    pub fn close(&mut self) -> io::Result<()> {
        if let Some(mut writer) = self.writer.take() {
            // Write footer
            let footer = WALFooter::new(
                self.entry_count,
                self.current_sequence(),
                self.running_checksum,
            );
            let footer_bytes = serde_json::to_vec(&footer)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            writer.write_all(&(footer_bytes.len() as u32).to_le_bytes())?;
            writer.write_all(&footer_bytes)?;
            writer.flush()?;
            writer.get_ref().sync_all()?;
        }
        Ok(())
    }
    
    /// Get current file path
    pub fn current_path(&self) -> &Path {
        &self.current_file_path
    }
    
    /// Get current file size
    pub fn current_size(&self) -> usize {
        self.current_size
    }
    
    /// Get entry count in current file
    pub fn entry_count(&self) -> u32 {
        self.entry_count
    }
}

impl Drop for WALWriter {
    fn drop(&mut self) {
        let _ = self.close();
    }
}

/// Async WAL Writer for use with tokio
pub struct AsyncWALWriter {
    /// Configuration
    config: WALWriterConfig,
    
    /// Shard ID
    shard_id: usize,
    
    /// Current file writer
    writer: Option<AsyncBufWriter<AsyncFile>>,
    
    /// Current file size
    current_size: usize,
    
    /// Sequence counter
    sequence: AtomicU64,
    
    /// Entry count
    entry_count: u32,
    
    /// Metrics
    metrics: Arc<WriteMetrics>,
}

impl AsyncWALWriter {
    /// Create a new async WAL writer
    pub async fn new(config: WALWriterConfig, shard_id: usize, metrics: Arc<WriteMetrics>) -> io::Result<Self> {
        tokio::fs::create_dir_all(&config.path).await?;
        
        let mut writer = Self {
            config,
            shard_id,
            writer: None,
            current_size: 0,
            sequence: AtomicU64::new(0),
            entry_count: 0,
            metrics,
        };
        
        writer.rotate().await?;
        
        Ok(writer)
    }
    
    /// Get next sequence number
    pub fn next_sequence(&self) -> u64 {
        self.sequence.fetch_add(1, Ordering::SeqCst)
    }
    
    /// Rotate to new file
    pub async fn rotate(&mut self) -> io::Result<()> {
        // Close current file
        if let Some(mut writer) = self.writer.take() {
            writer.flush().await?;
            self.metrics.record_wal_rotation();
        }
        
        // Open new file
        let start_seq = self.next_sequence();
        let path = self.config.path.join(format!("wal_{}_{:016}.wal", self.shard_id, start_seq));
        
        let file = AsyncFile::create(&path).await?;
        let mut writer = AsyncBufWriter::with_capacity(self.config.buffer_size, file);
        
        // Write header
        let header = WALHeader::new(self.shard_id, start_seq);
        let header_bytes = serde_json::to_vec(&header)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        writer.write_all(&(header_bytes.len() as u32).to_le_bytes()).await?;
        writer.write_all(&header_bytes).await?;
        
        self.writer = Some(writer);
        self.current_size = WALHeader::serialized_size();
        self.entry_count = 0;
        
        Ok(())
    }
    
    /// Append an entry
    pub async fn append(&mut self, entry: &WALEntry) -> io::Result<u64> {
        let entry_bytes = entry.to_bytes()
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
        
        let entry_size = 4 + entry_bytes.len();
        
        if self.current_size + entry_size > self.config.max_file_size {
            self.rotate().await?;
        }
        
        let writer = self.writer.as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "WAL not open"))?;
        
        writer.write_all(&(entry_bytes.len() as u32).to_le_bytes()).await?;
        writer.write_all(&entry_bytes).await?;
        
        self.current_size += entry_size;
        self.entry_count += 1;
        self.metrics.record_wal_bytes(entry_size);
        
        Ok(entry.sequence)
    }
    
    /// Flush and sync
    pub async fn sync(&mut self) -> io::Result<()> {
        if let Some(ref mut writer) = self.writer {
            writer.flush().await?;
            writer.get_ref().sync_data().await?;
        }
        Ok(())
    }
    
    /// Close writer
    pub async fn close(&mut self) -> io::Result<()> {
        if let Some(mut writer) = self.writer.take() {
            writer.flush().await?;
            writer.get_ref().sync_all().await?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use crate::types::MemoryNode;
    
    #[test]
    fn test_wal_writer_basic() {
        let dir = tempdir().unwrap();
        let config = WALWriterConfig::new(dir.path());
        let metrics = Arc::new(WriteMetrics::new());
        
        let mut writer = WALWriter::new(config, 0, metrics).unwrap();
        
        // Write some entries
        let node = MemoryNode::new("test content");
        let entry = WALEntry::insert(writer.next_sequence(), "default".to_string(), node);
        
        let seq = writer.append(&entry).unwrap();
        assert_eq!(seq, entry.sequence);
        
        writer.sync().unwrap();
        writer.close().unwrap();
    }
    
    #[test]
    fn test_wal_writer_rotation() {
        let dir = tempdir().unwrap();
        let config = WALWriterConfig {
            path: dir.path().to_path_buf(),
            max_file_size: 1024, // Small size to trigger rotation
            ..Default::default()
        };
        let metrics = Arc::new(WriteMetrics::new());
        
        let mut writer = WALWriter::new(config, 0, metrics.clone()).unwrap();
        
        // Write many entries to trigger rotation
        for i in 0..100 {
            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();
        
        // Check that multiple WAL files were created
        let wal_files: Vec<_> = std::fs::read_dir(dir.path())
            .unwrap()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().extension().map(|ext| ext == "wal").unwrap_or(false))
            .collect();
        
        assert!(wal_files.len() > 1, "Expected multiple WAL files after rotation");
    }
}
