//! Transaction Recovery Manager
//!
//! Implements ARIES-style recovery with three phases:
//! 1. Analysis: Scan WAL to determine transaction states
//! 2. Redo: Replay committed transactions
//! 3. Undo: Rollback uncommitted transactions
//!
//! Target: Recovery time < 2s for 100K records

use crate::txn::version_store::{TransactionId, Timestamp, VersionStore};
use crate::txn::wal::{WALManager, WALRecord, LogSequenceNumber};
use crate::Result;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Instant;

/// Analysis phase result
#[derive(Debug)]
pub struct AnalysisResult {
    /// Transactions that committed
    pub committed_txns: HashSet<TransactionId>,
    
    /// Active transactions at crash time (txn_id -> operations)
    pub active_txns: HashMap<TransactionId, Vec<(LogSequenceNumber, WALRecord)>>,
    
    /// Maximum LSN seen
    pub max_lsn: LogSequenceNumber,
    
    /// Transaction commit timestamps
    pub commit_timestamps: HashMap<TransactionId, Timestamp>,
}

/// Recovery report
#[derive(Debug)]
pub struct RecoveryReport {
    /// Total WAL records processed
    pub total_wal_records: usize,
    
    /// Number of committed transactions recovered
    pub committed_txns: usize,
    
    /// Number of aborted/rolled back transactions
    pub aborted_txns: usize,
    
    /// Number of redo operations
    pub redo_count: usize,
    
    /// Number of undo operations
    pub undo_count: usize,
    
    /// Recovery time in milliseconds
    pub recovery_time_ms: u64,
    
    /// Errors encountered (non-fatal)
    pub errors: Vec<String>,
}

/// Recovery Manager
pub struct RecoveryManager {
    /// WAL manager
    wal: Arc<WALManager>,
    
    /// Version store for applying recovered operations
    version_store: Arc<VersionStore>,
}

impl RecoveryManager {
    /// Create a new recovery manager
    pub fn new(wal: Arc<WALManager>, version_store: Arc<VersionStore>) -> Self {
        Self {
            wal,
            version_store,
        }
    }

    /// Analysis phase: Scan WAL to determine transaction states
    /// 
    /// This phase builds:
    /// - Set of committed transactions
    /// - Set of active (uncommitted) transactions with their operations
    /// - Maximum LSN for checkpoint purposes
    /// - Cached WAL records for redo/undo phases (avoids re-scanning)
    fn analyze_internal(&self) -> Result<(AnalysisResult, Vec<WALRecord>)> {
        let mut committed_txns = HashSet::new();
        let mut active_txns: HashMap<TransactionId, Vec<(LogSequenceNumber, WALRecord)>> = HashMap::new();
        let mut commit_timestamps = HashMap::new();
        let mut max_lsn = 0;
        let mut lsn_counter = 0;
        let mut all_records = Vec::new();

        // Recover records from all partitions (ONE TIME ONLY)
        let recovered = self.wal.recover()?;

        for (_partition_id, records) in recovered {
            for record in records {
                let current_lsn = lsn_counter;
                lsn_counter += 1;
                max_lsn = max_lsn.max(current_lsn);

                match &record {
                    WALRecord::Begin { txn_id, .. } => {
                        // Start tracking this transaction
                        active_txns.insert(*txn_id, Vec::new());
                    }
                    WALRecord::Commit { txn_id, commit_ts } => {
                        // Transaction committed
                        active_txns.remove(txn_id);
                        committed_txns.insert(*txn_id);
                        commit_timestamps.insert(*txn_id, *commit_ts);
                    }
                    WALRecord::Rollback { txn_id } => {
                        // Transaction explicitly rolled back
                        active_txns.remove(txn_id);
                    }
                    WALRecord::Insert { txn_id, .. }
                    | WALRecord::InsertRaw { txn_id, .. }
                    | WALRecord::Update { txn_id, .. }
                    | WALRecord::UpdateRaw { txn_id, .. }
                    | WALRecord::Delete { txn_id, .. }
                    | WALRecord::DeleteRaw { txn_id, .. } => {
                        // Associate data record with its transaction
                        if *txn_id != 0 && active_txns.contains_key(txn_id) {
                            if let Some(ops) = active_txns.get_mut(txn_id) {
                                ops.push((current_lsn, record.clone()));
                            }
                        }
                    }
                    WALRecord::Checkpoint { .. } => {
                        // Checkpoint marker - ignore for now
                    }
                }
                
                // Cache the record
                all_records.push(record);
            }
        }

        Ok((
            AnalysisResult {
                committed_txns,
                active_txns,
                max_lsn,
                commit_timestamps,
            },
            all_records,
        ))
    }
    
    /// Public analysis API (for unit tests)
    pub fn analyze(&self) -> Result<AnalysisResult> {
        let (analysis, _) = self.analyze_internal()?;
        Ok(analysis)
    }

    /// Redo phase: Replay committed transactions
    /// 
    /// Applies all operations from committed transactions to the version store.
    /// This phase is idempotent - safe to run multiple times.
    /// 
    /// Uses cached WAL records to avoid re-scanning the WAL.
    fn redo_internal(&self, analysis: &AnalysisResult, records: &[WALRecord]) -> Result<usize> {
        let mut redo_count = 0;
        let mut current_txn: Option<TransactionId> = None;

        for record in records {
            match record {
                WALRecord::Begin { txn_id, .. } => {
                    current_txn = Some(*txn_id);
                }
                WALRecord::Commit { txn_id, .. } => {
                    current_txn = None;
                    // Ensure we only redo committed transactions
                    if !analysis.committed_txns.contains(txn_id) {
                        continue;
                    }
                }
                WALRecord::Rollback { .. } => {
                    current_txn = None;
                }
                WALRecord::Insert { row_id, data, .. } => {
                    if let Some(txn_id) = current_txn {
                        if analysis.committed_txns.contains(&txn_id) {
                            let commit_ts = analysis.commit_timestamps
                                .get(&txn_id)
                                .copied()
                                .unwrap_or(0);
                            self.version_store.insert_version(
                                *row_id,
                                data.clone(),
                                txn_id,
                                commit_ts,
                            )?;
                            redo_count += 1;
                        }
                    }
                }
                WALRecord::InsertRaw { row_id, raw_data, .. } => {
                    if let Some(txn_id) = current_txn {
                        if analysis.committed_txns.contains(&txn_id) {
                            if let Ok(row) = crate::storage::row_format::decode_any(raw_data) {
                                let commit_ts = analysis.commit_timestamps
                                    .get(&txn_id)
                                    .copied()
                                    .unwrap_or(0);
                                self.version_store.insert_version(
                                    *row_id,
                                    row,
                                    txn_id,
                                    commit_ts,
                                )?;
                                redo_count += 1;
                            }
                        }
                    }
                }
                WALRecord::Update { row_id, new_data, .. } => {
                    if let Some(txn_id) = current_txn {
                        if analysis.committed_txns.contains(&txn_id) {
                            let commit_ts = analysis.commit_timestamps
                                .get(&txn_id)
                                .copied()
                                .unwrap_or(0);
                            self.version_store.insert_version(
                                *row_id,
                                new_data.clone(),
                                txn_id,
                                commit_ts,
                            )?;
                            redo_count += 1;
                        }
                    }
                }
                WALRecord::UpdateRaw { row_id, raw_new, .. } => {
                    if let Some(txn_id) = current_txn {
                        if analysis.committed_txns.contains(&txn_id) {
                            if let Ok(row) = crate::storage::row_format::decode_any(raw_new) {
                                let commit_ts = analysis.commit_timestamps
                                    .get(&txn_id)
                                    .copied()
                                    .unwrap_or(0);
                                self.version_store.insert_version(
                                    *row_id,
                                    row,
                                    txn_id,
                                    commit_ts,
                                )?;
                                redo_count += 1;
                            }
                        }
                    }
                }
                WALRecord::Delete { row_id, .. }
                | WALRecord::DeleteRaw { row_id, .. } => {
                    if let Some(txn_id) = current_txn {
                        if analysis.committed_txns.contains(&txn_id) {
                            let commit_ts = analysis.commit_timestamps
                                .get(&txn_id)
                                .copied()
                                .unwrap_or(0);
                            self.version_store.delete_version(
                                *row_id,
                                txn_id,
                                commit_ts,
                            )?;
                            redo_count += 1;
                        }
                    }
                }
                WALRecord::Checkpoint { .. } => {}
            }
        }

        Ok(redo_count)
    }
    
    /// Public redo API (for unit tests) 
    pub fn redo(&self, analysis: &AnalysisResult) -> Result<usize> {
        // Re-scan WAL for backwards compatibility with tests
        let recovered = self.wal.recover()?;
        let mut all_records = Vec::new();
        for (_partition_id, records) in recovered {
            all_records.extend(records);
        }
        self.redo_internal(analysis, &all_records)
    }

    /// Undo phase: Rollback uncommitted transactions
    /// 
    /// For each active transaction at crash time, undo its operations
    /// in reverse order. Writes compensation log records (CLRs) to WAL.
    /// 
    /// Uses cached WAL records to avoid re-scanning the WAL.
    fn undo_internal(&self, analysis: &AnalysisResult, records: &[WALRecord]) -> Result<usize> {
        let mut undo_count = 0;

        // Build a map of txn_id -> operations
        let mut txn_operations: HashMap<TransactionId, Vec<&WALRecord>> = HashMap::new();
        let mut current_txn: Option<TransactionId> = None;

        for record in records {
            match record {
                WALRecord::Begin { txn_id, .. } => {
                    current_txn = Some(*txn_id);
                    txn_operations.entry(*txn_id).or_default();
                }
                WALRecord::Commit { .. } | WALRecord::Rollback { .. } => {
                    current_txn = None;
                }
                WALRecord::Insert { .. }
                | WALRecord::InsertRaw { .. }
                | WALRecord::Update { .. }
                | WALRecord::UpdateRaw { .. }
                | WALRecord::Delete { .. }
                | WALRecord::DeleteRaw { .. } => {
                    if let Some(txn_id) = current_txn {
                        txn_operations
                            .entry(txn_id)
                            .or_default()
                            .push(record);
                    }
                }
                WALRecord::Checkpoint { .. } => {}
            }
        }

        // Undo active transactions (reverse order)
        for &txn_id in &analysis.active_txns.keys().copied().collect::<Vec<_>>() {
            if let Some(operations) = txn_operations.get(&txn_id) {
                // Process in reverse order
                for operation in operations.iter().rev() {
                    match operation {
                        WALRecord::Insert { row_id, .. }
                        | WALRecord::InsertRaw { row_id, .. } => {
                            // Undo insert = delete the version inserted by this txn
                            let _ = self.version_store.delete_version(
                                *row_id, txn_id, u64::MAX,
                            );
                            undo_count += 1;
                        }
                        WALRecord::Update { row_id, old_data, .. } => {
                            // Undo update = restore old value
                            let _ = self.version_store.insert_version(
                                *row_id, old_data.clone(), txn_id, u64::MAX,
                            );
                            undo_count += 1;
                        }
                        WALRecord::UpdateRaw { row_id, raw_old, .. } => {
                            // Undo update = restore old value (decode raw bytes)
                            if let Ok(row) = crate::storage::row_format::decode_any(raw_old) {
                                let _ = self.version_store.insert_version(
                                    *row_id, row, txn_id, u64::MAX,
                                );
                            }
                            undo_count += 1;
                        }
                        WALRecord::Delete { row_id, old_data, .. } => {
                            // Undo delete = re-insert old data
                            let _ = self.version_store.insert_version(
                                *row_id, old_data.clone(), txn_id, u64::MAX,
                            );
                            undo_count += 1;
                        }
                        WALRecord::DeleteRaw { row_id, raw_old, .. } => {
                            // Undo delete = re-insert old data (decode raw bytes)
                            if let Ok(row) = crate::storage::row_format::decode_any(raw_old) {
                                let _ = self.version_store.insert_version(
                                    *row_id, row, txn_id, u64::MAX,
                                );
                            }
                            undo_count += 1;
                        }
                        _ => {}
                    }
                }

                // Write rollback record
                let _ = self.wal.log_rollback(0, txn_id);
            }
        }

        Ok(undo_count)
    }
    
    /// Public undo API (for unit tests)
    pub fn undo(&self, analysis: &AnalysisResult) -> Result<usize> {
        // Re-scan WAL for backwards compatibility with tests
        let recovered = self.wal.recover()?;
        let mut all_records = Vec::new();
        for (_partition_id, records) in recovered {
            all_records.extend(records);
        }
        self.undo_internal(analysis, &all_records)
    }

    /// Complete recovery process: Analysis -> Redo -> Undo
    /// 
    /// This is the main entry point for crash recovery.
    /// Target: < 2s for 100K records
    /// 
    /// OPTIMIZED: Scans WAL only ONCE and caches records for all phases
    pub fn recover(&self) -> Result<RecoveryReport> {
        let start = Instant::now();

        // Phase 1: Analysis (scans WAL once and caches records)
        let (analysis, cached_records) = self.analyze_internal()?;

        // Phase 2: Redo committed transactions (uses cached records)
        let redo_count = self.redo_internal(&analysis, &cached_records)?;

        // Phase 3: Undo active transactions (uses cached records)
        let undo_count = self.undo_internal(&analysis, &cached_records)?;

        let recovery_time = start.elapsed().as_millis() as u64;

        Ok(RecoveryReport {
            total_wal_records: analysis.max_lsn as usize,
            committed_txns: analysis.committed_txns.len(),
            aborted_txns: analysis.active_txns.len(),
            redo_count,
            undo_count,
            recovery_time_ms: recovery_time,
            errors: Vec::new(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::txn::wal::WALManager;
    use crate::txn::wal::WALConfig;
    use crate::config::DurabilityLevel;
    use crate::txn::version_store::Snapshot;
    use crate::types::{Value, Timestamp};
    use std::collections::HashSet;
    use tempfile::TempDir;

    #[test]
    fn test_analysis_phase() {
        let temp_dir = TempDir::new().unwrap();
        let config = WALConfig { durability_level: DurabilityLevel::Synchronous };
        let wal = Arc::new(WALManager::create_with_config(temp_dir.path(), 2, config).unwrap());
        let version_store = Arc::new(VersionStore::new());

        // Create WAL records
        wal.log_begin(0, 1, 1).unwrap();
        wal.log_insert("test_table", 0, 100, vec![Value::Null], 0).unwrap();
        wal.log_commit(0, 1, 1000).unwrap();

        wal.log_begin(0, 2, 1).unwrap();
        wal.log_insert("test_table", 0, 200, vec![Value::Null], 0).unwrap();
        // No commit - simulates crash

        // Analyze
        let recovery = RecoveryManager::new(wal, version_store);
        let analysis = recovery.analyze().unwrap();

        // Verify
        assert_eq!(analysis.committed_txns.len(), 1);
        assert!(analysis.committed_txns.contains(&1));
        assert_eq!(analysis.active_txns.len(), 1);
        assert!(analysis.active_txns.contains_key(&2));
    }

    #[test]
    fn test_redo_phase() {
        let temp_dir = TempDir::new().unwrap();
        let config = WALConfig { durability_level: DurabilityLevel::Synchronous };
        let wal = Arc::new(WALManager::create_with_config(temp_dir.path(), 2, config).unwrap());
        let version_store = Arc::new(VersionStore::new());

        // Committed transaction
        wal.log_begin(0, 1, 1).unwrap();
        wal.log_insert("test_table", 0, 100, vec![Value::Timestamp(Timestamp::from_micros(1000))], 0).unwrap();
        wal.log_commit(0, 1, 1000).unwrap();

        // Recover
        let recovery = RecoveryManager::new(wal, version_store.clone());
        let analysis = recovery.analyze().unwrap();
        let redo_count = recovery.redo(&analysis).unwrap();

        // Verify - check that version was inserted
        assert_eq!(redo_count, 1);
        
        // Create a snapshot to read the recovered data
        let snapshot = Snapshot {
            timestamp: 2000,  // After commit
            active_txns: HashSet::new(),
        };
        
        let result = version_store.get_visible_version(100, &snapshot).unwrap();
        assert!(result.is_some());
    }

    #[test]
    fn test_complete_recovery() {
        let temp_dir = TempDir::new().unwrap();
        let config = WALConfig { durability_level: DurabilityLevel::Synchronous };
        let wal = Arc::new(WALManager::create_with_config(temp_dir.path(), 2, config).unwrap());
        let version_store = Arc::new(VersionStore::new());

        // T1: Committed
        wal.log_begin(0, 1, 1).unwrap();
        wal.log_insert("test_table", 0, 100, vec![Value::Null], 0).unwrap();
        wal.log_commit(0, 1, 1000).unwrap();

        // T2: Uncommitted (crash)
        wal.log_begin(0, 2, 1).unwrap();
        wal.log_insert("test_table", 0, 200, vec![Value::Null], 0).unwrap();

        // Recover
        let recovery = RecoveryManager::new(wal, version_store);
        let report = recovery.recover().unwrap();

        // Verify
        assert_eq!(report.committed_txns, 1);
        assert_eq!(report.aborted_txns, 1);
        assert!(report.recovery_time_ms < 2000);  // < 2s
    }

    #[test]
    fn test_recovery_with_rollback() {
        let temp_dir = TempDir::new().unwrap();
        let config = WALConfig { durability_level: DurabilityLevel::Synchronous };
        let wal = Arc::new(WALManager::create_with_config(temp_dir.path(), 2, config).unwrap());
        let version_store = Arc::new(VersionStore::new());

        // T1: Explicitly rolled back
        wal.log_begin(0, 1, 1).unwrap();
        wal.log_insert("test_table", 0, 100, vec![Value::Null], 0).unwrap();
        wal.log_rollback(0, 1).unwrap();

        // T2: Committed
        wal.log_begin(0, 2, 1).unwrap();
        wal.log_insert("test_table", 0, 200, vec![Value::Null], 0).unwrap();
        wal.log_commit(0, 2, 2000).unwrap();

        // Recover
        let recovery = RecoveryManager::new(wal, version_store);
        let report = recovery.recover().unwrap();

        // Verify
        assert_eq!(report.committed_txns, 1);
        assert_eq!(report.aborted_txns, 0);  // Explicit rollback doesn't count as aborted
    }

    #[test]
    fn test_recovery_idempotence() {
        let temp_dir = TempDir::new().unwrap();
        let config = WALConfig { durability_level: DurabilityLevel::Synchronous };
        let wal = Arc::new(WALManager::create_with_config(temp_dir.path(), 2, config).unwrap());
        let version_store = Arc::new(VersionStore::new());

        // Create committed transaction
        wal.log_begin(0, 1, 1).unwrap();
        wal.log_insert("test_table", 0, 100, vec![Value::Null], 0).unwrap();
        wal.log_commit(0, 1, 1000).unwrap();

        let recovery = RecoveryManager::new(wal, version_store);

        // Run recovery twice
        let report1 = recovery.recover().unwrap();
        let report2 = recovery.recover().unwrap();

        // Should be idempotent
        assert_eq!(report1.committed_txns, report2.committed_txns);
        assert_eq!(report1.redo_count, report2.redo_count);
    }
}
