//! Synchronization operation implementation for anti-entropy.
//!
//! Contains the main sync logic including the run loop, sync_once,
//! and helper methods for computing missing hashes and merging data.

use super::types::SyncResult;
use super::worker::AntiEntropyWorker;
use crate::error::Result;
use metrics::{counter, gauge};
use std::collections::HashSet;
use std::sync::atomic::Ordering;
use stemedb_core::serde::deserialize;
use stemedb_core::types::{detect_clock_skew, Assertion, HlcTimestamp};
use stemedb_rpc::proto::{FetchRequest, GetLeavesRequest, RootExchangeRequest};
use stemedb_storage::crdt::AssertionTransfer;
use stemedb_storage::KVStore;
use tokio::time::interval;
use tracing::{debug, error, info, instrument, warn};

impl<S: KVStore + 'static> AntiEntropyWorker<S> {
    /// Run the anti-entropy loop.
    ///
    /// This runs forever (or until shutdown) and syncs periodically.
    /// Can also be triggered immediately via the force sync handle.
    #[instrument(skip(self), fields(peer = %self.peer_addr))]
    pub async fn run(&self) {
        info!(interval_secs = self.interval.as_secs(), "Starting anti-entropy worker");

        let mut ticker = interval(self.interval);

        loop {
            // Wait for either the interval tick or a force sync signal
            tokio::select! {
                _ = ticker.tick() => {
                    debug!("Anti-entropy tick");
                }
                _ = self.force_sync_notify.notified() => {
                    info!("Force sync triggered via admin API");
                }
            }

            if self.is_shutdown() {
                info!("Anti-entropy worker shutting down");
                break;
            }

            match self.sync_once().await {
                Ok(result) => {
                    // Update AtomicU64 counter
                    self.sync_cycles.fetch_add(1, Ordering::Relaxed);
                    // Record Prometheus metric
                    counter!("stemedb_sync_cycles_total", "peer" => self.peer_addr.clone())
                        .increment(1);

                    match result {
                        SyncResult::InSync => {
                            debug!("Anti-entropy: already in sync");
                            // Update last sync time for lag calculation
                            *self.last_sync_at.lock() = Some(std::time::Instant::now());
                        }
                        SyncResult::Synced { count } => {
                            // Update AtomicU64 counter
                            self.assertions_synced.fetch_add(count as u64, Ordering::Relaxed);
                            // Record Prometheus metric
                            counter!("stemedb_assertions_synced_total", "peer" => self.peer_addr.clone())
                                .increment(count as u64);
                            // Update last sync time for lag calculation
                            *self.last_sync_at.lock() = Some(std::time::Instant::now());
                            info!(count, "Anti-entropy: synced assertions");
                        }
                        SyncResult::Failed { error } => {
                            // Update AtomicU64 counter
                            self.sync_failures.fetch_add(1, Ordering::Relaxed);
                            // Record Prometheus metric
                            counter!("stemedb_sync_failures_total", "peer" => self.peer_addr.clone())
                                .increment(1);
                            warn!(error, "Anti-entropy sync failed");
                        }
                    }

                    // Update sync lag gauge
                    if let Some(lag) = self.sync_lag_seconds() {
                        gauge!("stemedb_sync_lag_seconds", "peer" => self.peer_addr.clone())
                            .set(lag);
                    }
                }
                Err(e) => {
                    // Update AtomicU64 counter
                    self.sync_failures.fetch_add(1, Ordering::Relaxed);
                    // Record Prometheus metric
                    counter!("stemedb_sync_failures_total", "peer" => self.peer_addr.clone())
                        .increment(1);
                    error!(error = %e, "Anti-entropy error");
                }
            }
        }
    }

    /// Perform a single sync cycle.
    ///
    /// This is the core sync algorithm:
    /// 1. Exchange Merkle roots
    /// 2. If roots match, done
    /// 3. If roots differ, compute diff and fetch missing
    #[instrument(skip(self), fields(peer = %self.peer_addr))]
    pub async fn sync_once(&self) -> Result<SyncResult> {
        // Step 1: Get local Merkle state
        let local_root = self.merkle_manager.root().await?;
        let local_count = self.merkle_manager.len().await;

        // Step 2: Exchange roots with peer
        let exchange_response = self
            .rpc_client
            .exchange_roots(RootExchangeRequest {
                merkle_root: local_root.map(|r| r.to_vec()).unwrap_or_default(),
                assertion_count: local_count as u64,
            })
            .await?;

        // Step 3: Check if in sync
        if exchange_response.roots_match {
            debug!("Merkle roots match, trees are identical");
            // Record convergence if we were previously diverged
            self.record_convergence();
            return Ok(SyncResult::InSync);
        }

        // Record divergence for convergence timing
        self.record_divergence();

        debug!(
            local_count,
            remote_count = exchange_response.assertion_count,
            "Merkle roots differ, computing diff"
        );

        // Step 4: Build remote tree representation for diff
        // We need to get remote leaves - in a real implementation we'd
        // have a more efficient protocol. For now, we use a simple approach:
        // if our count < remote count, we have missing assertions.

        let local_leaves = self.merkle_manager.leaves().await;

        // For a minimal implementation, we request assertions we don't have.
        // In practice, a proper Merkle diff protocol would be more efficient.
        // For now, we assume the peer can tell us what's missing based on our state.

        // Request missing assertions based on local leaves
        // The peer will return assertions it has that we don't
        let missing_hashes = self.compute_missing_hashes(&local_leaves).await?;

        if missing_hashes.is_empty() {
            debug!("No missing assertions found");
            return Ok(SyncResult::InSync);
        }

        debug!(missing_count = missing_hashes.len(), "Fetching missing assertions");

        // Step 5: Fetch missing assertions
        let fetch_response = self
            .rpc_client
            .fetch_assertions(FetchRequest {
                hashes: missing_hashes.iter().map(|h| h.to_vec()).collect(),
            })
            .await?;

        if fetch_response.assertions.is_empty() {
            debug!("Peer returned no assertions");
            return Ok(SyncResult::InSync);
        }

        // Step 6: Merge fetched assertions
        let transfers: Vec<AssertionTransfer> = fetch_response
            .assertions
            .into_iter()
            .filter_map(|a| {
                if a.hash.len() != 32 {
                    warn!(len = a.hash.len(), "Invalid hash length in fetch response");
                    return None;
                }
                let mut hash = [0u8; 32];
                hash.copy_from_slice(&a.hash);
                Some(AssertionTransfer { hash, data: a.data })
            })
            .collect();

        let mut merged_count = 0usize;

        // Merge into CRDT store (handles deduplication)
        // Group transfers by subject for efficient CRDT merge
        for transfer in transfers {
            // Verify hash matches data
            let computed = blake3::hash(&transfer.data);
            if computed.as_bytes() != &transfer.hash {
                warn!(
                    expected = %hex::encode(&transfer.hash[..8]),
                    computed = %hex::encode(&computed.as_bytes()[..8]),
                    "Hash mismatch, skipping"
                );
                continue;
            }

            // Extract subject and HLC timestamp from the assertion data
            let (subject, remote_hlc) = match deserialize::<Assertion>(&transfer.data) {
                Ok(assertion) => (assertion.subject.clone(), assertion.hlc_timestamp),
                Err(e) => {
                    warn!(
                        hash = %hex::encode(&transfer.hash[..8]),
                        error = %e,
                        "Failed to deserialize assertion, skipping"
                    );
                    continue;
                }
            };

            // Update local HLC with remote's timestamp for causal ordering
            if let Some(uhlc_ts) = remote_hlc.to_uhlc() {
                if self.local_hlc.update_with_timestamp(&uhlc_ts).is_ok() {
                    self.hlc_updates.fetch_add(1, Ordering::Relaxed);
                    debug!(
                        hash = %hex::encode(&transfer.hash[..8]),
                        remote_ntp64 = remote_hlc.time_ntp64,
                        "Updated local HLC from remote assertion"
                    );
                }
            }

            // Detect clock skew
            let local_now = HlcTimestamp::now(&self.local_hlc);
            if let Some(skew_ms) = detect_clock_skew(&local_now, &remote_hlc) {
                self.clock_skew_events.fetch_add(1, Ordering::Relaxed);
                warn!(
                    skew_ms,
                    local_ms = local_now.millis(),
                    remote_ms = remote_hlc.millis(),
                    "Clock skew detected during anti-entropy sync"
                );
            }

            // Merge via CRDT store (handles deduplication and storage)
            match self.crdt_store.merge_with_data(&subject, std::slice::from_ref(&transfer)).await {
                Ok(count) => {
                    merged_count += count;
                    debug!(
                        hash = %hex::encode(&transfer.hash[..8]),
                        subject = %subject,
                        "Merged assertion via CRDT store"
                    );
                }
                Err(e) => {
                    warn!(
                        hash = %hex::encode(&transfer.hash[..8]),
                        error = %e,
                        "Failed to merge assertion via CRDT store"
                    );
                }
            }

            // Update Merkle tree
            self.merkle_manager.insert(transfer.hash).await?;
        }

        info!(count = merged_count, "Merged assertions from peer");

        Ok(SyncResult::Synced { count: merged_count })
    }

    /// Compute hashes we're missing compared to the peer.
    ///
    /// Fetches the peer's Merkle tree leaves and computes the set difference
    /// to find hashes present on the peer but not locally.
    async fn compute_missing_hashes(&self, local_leaves: &[[u8; 32]]) -> Result<Vec<[u8; 32]>> {
        // Fetch remote leaves via RPC
        let response = self.rpc_client.get_leaves(GetLeavesRequest { max_leaves: 10000 }).await?;

        if response.truncated {
            warn!("Remote has more than 10000 leaves, sync may be incomplete");
        }

        // Build local set for O(1) lookup
        let local_set: HashSet<[u8; 32]> = local_leaves.iter().copied().collect();
        let remote_count = response.leaves.len();

        // Find hashes in remote that aren't in local
        let missing: Vec<[u8; 32]> = response
            .leaves
            .into_iter()
            .filter_map(|leaf_bytes| {
                if leaf_bytes.len() != 32 {
                    warn!(len = leaf_bytes.len(), "Invalid leaf length from peer");
                    return None;
                }
                let mut hash = [0u8; 32];
                hash.copy_from_slice(&leaf_bytes);
                if local_set.contains(&hash) {
                    None
                } else {
                    Some(hash)
                }
            })
            .collect();

        debug!(
            local_count = local_leaves.len(),
            remote_count,
            missing_count = missing.len(),
            "Computed missing hashes"
        );

        // Record Prometheus gauge for merkle diff size
        gauge!("stemedb_merkle_diff_size", "peer" => self.peer_addr.clone())
            .set(missing.len() as f64);

        Ok(missing)
    }
}
