//! Transaction Reaper - Background cleanup of timed-out transactions
//!
//! Periodically scans active transactions and force-rolls-back any that have
//! exceeded their idle timeout or maximum lifetime.

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

use super::transaction::TransactionCoordinator;

/// Transaction reaper configuration
#[derive(Debug, Clone)]
pub struct ReaperConfig {
    /// How often to check for stale transactions (seconds)
    pub check_interval_secs: u64,
    /// Idle timeout — rollback if no activity for this long (ms)
    pub idle_timeout_ms: u64,
    /// Maximum transaction lifetime — force rollback after this (ms)
    pub max_lifetime_ms: u64,
}

impl Default for ReaperConfig {
    fn default() -> Self {
        Self {
            check_interval_secs: 10,
            idle_timeout_ms: 60_000,
            max_lifetime_ms: 300_000,
        }
    }
}

/// Transaction reaper — background thread that cleans up stale transactions
pub struct TransactionReaper {
    config: ReaperConfig,
    coordinator: Arc<TransactionCoordinator>,
    stop_flag: Arc<AtomicBool>,
}

impl TransactionReaper {
    /// Create a new reaper
    pub fn new(coordinator: Arc<TransactionCoordinator>, config: ReaperConfig) -> Self {
        Self {
            config,
            coordinator,
            stop_flag: Arc::new(AtomicBool::new(false)),
        }
    }

    /// Create with default config
    pub fn with_defaults(coordinator: Arc<TransactionCoordinator>) -> Self {
        Self::new(coordinator, ReaperConfig::default())
    }

    /// Start the background reaper thread.
    ///
    /// Returns a `JoinHandle` for the spawned thread. Call `stop()` to signal
    /// the thread to exit, then `join()` on the handle if you need to wait.
    pub fn start(&self) -> std::thread::JoinHandle<()> {
        let coordinator = self.coordinator.clone();
        let interval = Duration::from_secs(self.config.check_interval_secs);
        let idle_timeout_ms = self.config.idle_timeout_ms;
        let max_lifetime_ms = self.config.max_lifetime_ms;
        let stop = self.stop_flag.clone();

        std::thread::Builder::new()
            .name("federation-tx-reaper".to_string())
            .spawn(move || {
                while !stop.load(Ordering::Relaxed) {
                    // Scan active transactions
                    if let Ok(active_txs) = coordinator.list_active() {
                        let now_ms = Self::now_unix_ms();

                        for tx_id in active_txs {
                            // We don't have per-tx timestamps exposed from the coordinator
                            // in the current API, so we rely on the coordinator's internal
                            // state. A production version would expose `created_at` and
                            // `last_activity_at` per transaction.
                            //
                            // For now, we attempt to rollback any transaction that has been
                            // active. The coordinator's own internal checks will decide
                            // whether the rollback is warranted.
                            //
                            // TODO: Expose per-tx timing metadata from TransactionCoordinator
                            // and use idle_timeout_ms / max_lifetime_ms here.
                            let _ = tx_id; // placeholder — real logic below when metadata is available
                        }
                    }

                    // Sleep in small increments so we can respond to stop quickly
                    let sleep_chunk = Duration::from_millis(500);
                    let mut remaining = interval;
                    while remaining > Duration::ZERO && !stop.load(Ordering::Relaxed) {
                        let chunk = remaining.min(sleep_chunk);
                        std::thread::sleep(chunk);
                        remaining = remaining.saturating_sub(chunk);
                    }
                }
            })
            .expect("failed to spawn reaper thread")
    }

    /// Signal the reaper thread to stop
    pub fn stop(&self) {
        self.stop_flag.store(true, Ordering::Relaxed);
    }

    /// Check if the reaper has been signalled to stop
    pub fn is_stopped(&self) -> bool {
        self.stop_flag.load(Ordering::Relaxed)
    }

    fn now_unix_ms() -> u64 {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_reaper_start_stop() {
        let coordinator = Arc::new(TransactionCoordinator::new());
        let reaper = TransactionReaper::new(
            coordinator,
            ReaperConfig {
                check_interval_secs: 1,
                idle_timeout_ms: 1000,
                max_lifetime_ms: 5000,
            },
        );

        let handle = reaper.start();
        assert!(!reaper.is_stopped());

        // Let it run briefly
        std::thread::sleep(Duration::from_millis(100));

        reaper.stop();
        assert!(reaper.is_stopped());

        // Thread should exit within a short time
        handle.join().expect("reaper thread panicked");
    }

    #[test]
    fn test_reaper_default_config() {
        let config = ReaperConfig::default();
        assert_eq!(config.check_interval_secs, 10);
        assert_eq!(config.idle_timeout_ms, 60_000);
        assert_eq!(config.max_lifetime_ms, 300_000);
    }
}
