//! Anti-entropy worker implementation.
//!
//! The AntiEntropyWorker maintains synchronization state and provides
//! the main run loop for periodic syncing with peers.

use crate::merkle_manager::MerkleTreeManager;
use crate::SyncConfig;
use parking_lot::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use stemedb_rpc::SyncClient;
use stemedb_storage::crdt::CrdtAssertionStore;
use stemedb_storage::KVStore;
use tokio::sync::Notify;
use tracing::debug;

/// Anti-entropy sync worker.
///
/// Runs a background loop that periodically syncs with a peer.
/// Maintains a local HLC clock that is updated when receiving assertions
/// with newer HLC timestamps, ensuring causal ordering is preserved.
pub struct AntiEntropyWorker<S: KVStore + 'static> {
    pub(super) merkle_manager: Arc<MerkleTreeManager<S>>,
    pub(super) crdt_store: Arc<CrdtAssertionStore<Arc<S>>>,
    pub(super) rpc_client: Arc<SyncClient>,
    pub(super) peer_addr: String,
    pub(super) interval: Duration,
    pub(super) shutdown: Arc<AtomicBool>,
    /// Local HLC clock for causal ordering.
    /// Updated when receiving assertions with newer timestamps.
    pub(super) local_hlc: Arc<uhlc::HLC>,
    // Metrics
    pub(super) sync_cycles: AtomicU64,
    pub(super) sync_failures: AtomicU64,
    pub(super) assertions_synced: AtomicU64,
    /// Number of times the local HLC was advanced from remote timestamps.
    pub(super) hlc_updates: AtomicU64,
    /// Number of times clock skew exceeded the threshold (500ms).
    pub(super) clock_skew_events: AtomicU64,
    // Convergence timing metrics
    /// Timestamp when divergence was last detected (roots differ).
    pub(super) last_divergence_at: Mutex<Option<Instant>>,
    /// Timestamp when convergence was last achieved (roots match after divergence).
    pub(super) last_convergence_at: Mutex<Option<Instant>>,
    /// History of convergence durations in milliseconds (last N samples).
    pub(super) convergence_durations_ms: Mutex<Vec<u64>>,
    /// Notify channel for force sync from admin API.
    pub(super) force_sync_notify: Arc<Notify>,
    /// Timestamp of last successful sync for computing sync lag.
    pub(super) last_sync_at: Mutex<Option<Instant>>,
}

impl<S: KVStore + 'static> AntiEntropyWorker<S> {
    /// Create a new anti-entropy worker.
    ///
    /// # Arguments
    ///
    /// * `merkle_manager` - Manager for the local Merkle tree
    /// * `crdt_store` - CRDT store for merging assertions
    /// * `rpc_client` - Client for communicating with the peer
    /// * `local_hlc` - Shared HLC clock for causal ordering
    /// * `config` - Sync configuration
    pub fn new(
        merkle_manager: Arc<MerkleTreeManager<S>>,
        crdt_store: Arc<CrdtAssertionStore<Arc<S>>>,
        rpc_client: Arc<SyncClient>,
        local_hlc: Arc<uhlc::HLC>,
        config: &SyncConfig,
    ) -> Self {
        Self {
            merkle_manager,
            crdt_store,
            peer_addr: rpc_client.peer_addr().to_string(),
            rpc_client,
            interval: config.anti_entropy_interval,
            shutdown: Arc::new(AtomicBool::new(false)),
            local_hlc,
            sync_cycles: AtomicU64::new(0),
            sync_failures: AtomicU64::new(0),
            assertions_synced: AtomicU64::new(0),
            hlc_updates: AtomicU64::new(0),
            clock_skew_events: AtomicU64::new(0),
            last_divergence_at: Mutex::new(None),
            last_convergence_at: Mutex::new(None),
            convergence_durations_ms: Mutex::new(Vec::new()),
            force_sync_notify: Arc::new(Notify::new()),
            last_sync_at: Mutex::new(None),
        }
    }

    /// Get the force sync notify handle for external triggering.
    ///
    /// Call `notify.notify_one()` to trigger an immediate sync cycle.
    pub fn force_sync_handle(&self) -> Arc<Notify> {
        Arc::clone(&self.force_sync_notify)
    }

    /// Get the seconds since last successful sync.
    ///
    /// Returns None if no successful sync has occurred yet.
    pub fn sync_lag_seconds(&self) -> Option<f64> {
        self.last_sync_at.lock().map(|t| t.elapsed().as_secs_f64())
    }

    /// Create with a shared shutdown signal.
    pub fn with_shutdown(mut self, shutdown: Arc<AtomicBool>) -> Self {
        self.shutdown = shutdown;
        self
    }

    /// Check if shutdown has been requested.
    pub fn is_shutdown(&self) -> bool {
        self.shutdown.load(Ordering::Relaxed)
    }

    /// Request shutdown.
    pub fn shutdown(&self) {
        self.shutdown.store(true, Ordering::Relaxed);
    }

    /// Get the number of sync cycles completed.
    pub fn sync_cycles(&self) -> u64 {
        self.sync_cycles.load(Ordering::Relaxed)
    }

    /// Get the number of sync failures.
    pub fn sync_failures(&self) -> u64 {
        self.sync_failures.load(Ordering::Relaxed)
    }

    /// Get the total number of assertions synced.
    pub fn assertions_synced(&self) -> u64 {
        self.assertions_synced.load(Ordering::Relaxed)
    }

    /// Get the number of HLC clock updates from remote timestamps.
    pub fn hlc_updates(&self) -> u64 {
        self.hlc_updates.load(Ordering::Relaxed)
    }

    /// Get the number of clock skew events detected.
    pub fn clock_skew_events(&self) -> u64 {
        self.clock_skew_events.load(Ordering::Relaxed)
    }

    /// Get a reference to the local HLC clock.
    pub fn local_hlc(&self) -> &Arc<uhlc::HLC> {
        &self.local_hlc
    }

    /// Get the last convergence duration in milliseconds.
    ///
    /// Returns the time elapsed between the last divergence detection
    /// and the subsequent convergence (roots matching).
    pub fn last_convergence_duration_ms(&self) -> Option<u64> {
        self.convergence_durations_ms.lock().last().copied()
    }

    /// Get the average convergence duration in milliseconds.
    ///
    /// Computed from the history of convergence events.
    pub fn avg_convergence_duration_ms(&self) -> Option<u64> {
        let durations = self.convergence_durations_ms.lock();
        if durations.is_empty() {
            return None;
        }
        let sum: u64 = durations.iter().sum();
        Some(sum / durations.len() as u64)
    }

    /// Get the number of convergence events recorded.
    pub fn convergence_count(&self) -> usize {
        self.convergence_durations_ms.lock().len()
    }

    /// Record divergence (Merkle roots differ).
    pub(super) fn record_divergence(&self) {
        let mut last_div = self.last_divergence_at.lock();
        if last_div.is_none() {
            *last_div = Some(Instant::now());
            debug!("Divergence detected, starting convergence timer");
        }
    }

    /// Record convergence (Merkle roots match after divergence).
    pub(super) fn record_convergence(&self) {
        let mut last_div = self.last_divergence_at.lock();
        if let Some(div_at) = last_div.take() {
            let duration_ms = div_at.elapsed().as_millis() as u64;

            *self.last_convergence_at.lock() = Some(Instant::now());

            // Keep last 100 samples
            let mut durations = self.convergence_durations_ms.lock();
            if durations.len() >= 100 {
                durations.remove(0);
            }
            durations.push(duration_ms);

            // Record Prometheus histogram (convert ms to seconds)
            metrics::histogram!("stemedb_convergence_latency_seconds", "peer" => self.peer_addr.clone())
                .record(duration_ms as f64 / 1000.0);

            tracing::info!(duration_ms, "Convergence achieved");
        }
    }
}
