//! Merkle tree diff operations for efficient sync.
//!
//! # Design Philosophy
//!
//! The diff algorithm is optimized for StemeDB's append-only model:
//! - **Fast identity check**: O(1) root comparison before expensive traversal
//! - **Minimal data transfer**: Return only hashes that differ
//! - **Set semantics**: Identify assertions in remote but not in local
//!
//! # Use Case
//!
//! When a StemeDB node connects to a peer:
//! 1. Exchange root hashes: O(1) to check if sync needed
//! 2. If roots differ, call `diff()` to find missing assertions
//! 3. Request missing assertions by hash
//! 4. Insert into local store
//!
//! # Example
//!
//! ```
//! use stemedb_merkle::{MerkleTree, roots_equal};
//!
//! let mut local = MerkleTree::new();
//! local.insert([1u8; 32]).expect("insert");
//! local.insert([2u8; 32]).expect("insert");
//!
//! let mut remote = MerkleTree::new();
//! remote.insert([1u8; 32]).expect("insert");
//! remote.insert([2u8; 32]).expect("insert");
//! remote.insert([3u8; 32]).expect("insert");
//!
//! // Quick check: do we need to sync?
//! if !roots_equal(&local, &remote) {
//!     // Find what remote has that local doesn't
//!     let diff = stemedb_merkle::DiffResult::diff(&local, &remote);
//!     // Request missing assertions [3]
//! }
//! ```

use crate::tree::{Hash, MerkleTree};
use std::collections::HashSet;
use tracing::instrument;

/// Check if two trees have identical roots.
///
/// This is an O(1) operation that determines if sync is needed.
/// If roots are equal, trees are identical (due to hash properties).
///
/// # Example
///
/// ```
/// use stemedb_merkle::{MerkleTree, roots_equal};
///
/// let mut tree1 = MerkleTree::new();
/// tree1.insert([1u8; 32]).expect("insert");
///
/// let mut tree2 = MerkleTree::new();
/// tree2.insert([1u8; 32]).expect("insert");
///
/// assert!(roots_equal(&tree1, &tree2));
/// ```
pub fn roots_equal(a: &MerkleTree, b: &MerkleTree) -> bool {
    match (a.root(), b.root()) {
        (Ok(root_a), Ok(root_b)) => root_a == root_b,
        (Err(_), Err(_)) => true, // Both empty
        _ => false,               // One empty, one not
    }
}

/// Result of a Merkle tree diff operation.
///
/// Contains the set of assertion hashes present in the remote tree
/// but missing from the local tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffResult {
    /// Hashes present in remote but not in local.
    pub missing_hashes: Vec<Hash>,
}

impl DiffResult {
    /// Compute the diff between local and remote trees.
    ///
    /// Returns all assertion hashes that exist in `remote` but not in `local`.
    /// This is what the local node needs to fetch to catch up.
    ///
    /// # Algorithm
    ///
    /// For append-only trees, we use a set-based approach:
    /// 1. Build HashSet from local leaves: O(N_local)
    /// 2. Iterate remote leaves, checking membership: O(N_remote)
    /// 3. Return hashes in remote but not in local
    ///
    /// This is simple and correct for append-only semantics where:
    /// - Leaves are never removed
    /// - Order matters only for root hash, not for membership
    ///
    /// # Future Optimization
    ///
    /// For very large trees (millions of assertions), implement subtree-based
    /// diff that exploits tree structure to skip identical subtrees:
    /// - Compare subtree roots before descending
    /// - Skip entire subtrees with matching hashes
    /// - Reduces comparison from O(N) to O(diff_size * log N)
    ///
    /// # Example
    ///
    /// ```
    /// use stemedb_merkle::{MerkleTree, DiffResult};
    ///
    /// let mut local = MerkleTree::new();
    /// local.insert([1u8; 32]).expect("insert");
    /// local.insert([2u8; 32]).expect("insert");
    ///
    /// let mut remote = MerkleTree::new();
    /// remote.insert([1u8; 32]).expect("insert");
    /// remote.insert([2u8; 32]).expect("insert");
    /// remote.insert([3u8; 32]).expect("insert");
    /// remote.insert([4u8; 32]).expect("insert");
    ///
    /// let diff = DiffResult::diff(&local, &remote);
    /// assert_eq!(diff.missing_hashes.len(), 2);
    /// assert!(diff.missing_hashes.contains(&[3u8; 32]));
    /// assert!(diff.missing_hashes.contains(&[4u8; 32]));
    /// ```
    #[instrument(skip(local, remote), fields(
        local_len = local.len(),
        remote_len = remote.len()
    ))]
    pub fn diff(local: &MerkleTree, remote: &MerkleTree) -> Self {
        // Fast path: if roots are equal, no diff needed
        if roots_equal(local, remote) {
            return Self { missing_hashes: Vec::new() };
        }

        // Build set of local hashes for O(1) membership check
        let local_set: HashSet<Hash> = local.leaves().iter().copied().collect();

        // Find hashes in remote but not in local
        let missing_hashes: Vec<Hash> =
            remote.leaves().iter().filter(|hash| !local_set.contains(*hash)).copied().collect();

        tracing::debug!(missing_count = missing_hashes.len(), "Computed Merkle diff");

        Self { missing_hashes }
    }

    /// Check if the diff is empty (trees are identical).
    ///
    /// # Example
    ///
    /// ```
    /// use stemedb_merkle::{MerkleTree, DiffResult};
    ///
    /// let mut tree1 = MerkleTree::new();
    /// tree1.insert([1u8; 32]).expect("insert");
    ///
    /// let mut tree2 = MerkleTree::new();
    /// tree2.insert([1u8; 32]).expect("insert");
    ///
    /// let diff = DiffResult::diff(&tree1, &tree2);
    /// assert!(diff.is_empty());
    /// ```
    pub fn is_empty(&self) -> bool {
        self.missing_hashes.is_empty()
    }

    /// Get the number of missing hashes.
    ///
    /// # Example
    ///
    /// ```
    /// use stemedb_merkle::{MerkleTree, DiffResult};
    ///
    /// let mut local = MerkleTree::new();
    /// local.insert([1u8; 32]).expect("insert");
    ///
    /// let mut remote = MerkleTree::new();
    /// remote.insert([1u8; 32]).expect("insert");
    /// remote.insert([2u8; 32]).expect("insert");
    /// remote.insert([3u8; 32]).expect("insert");
    ///
    /// let diff = DiffResult::diff(&local, &remote);
    /// assert_eq!(diff.len(), 2);
    /// ```
    pub fn len(&self) -> usize {
        self.missing_hashes.len()
    }
}

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

    #[test]
    fn test_roots_equal_empty_trees() {
        let tree1 = MerkleTree::new();
        let tree2 = MerkleTree::new();
        assert!(roots_equal(&tree1, &tree2));
    }

    #[test]
    fn test_roots_equal_identical_trees() {
        let mut tree1 = MerkleTree::new();
        tree1.insert([1u8; 32]).expect("insert");
        tree1.insert([2u8; 32]).expect("insert");

        let mut tree2 = MerkleTree::new();
        tree2.insert([1u8; 32]).expect("insert");
        tree2.insert([2u8; 32]).expect("insert");

        assert!(roots_equal(&tree1, &tree2));
    }

    #[test]
    fn test_roots_not_equal_different_trees() {
        let mut tree1 = MerkleTree::new();
        tree1.insert([1u8; 32]).expect("insert");

        let mut tree2 = MerkleTree::new();
        tree2.insert([2u8; 32]).expect("insert");

        assert!(!roots_equal(&tree1, &tree2));
    }

    #[test]
    fn test_roots_not_equal_empty_vs_nonempty() {
        let tree1 = MerkleTree::new();

        let mut tree2 = MerkleTree::new();
        tree2.insert([1u8; 32]).expect("insert");

        assert!(!roots_equal(&tree1, &tree2));
    }

    #[test]
    fn test_diff_identical_trees() {
        let mut tree1 = MerkleTree::new();
        tree1.insert([1u8; 32]).expect("insert");
        tree1.insert([2u8; 32]).expect("insert");

        let mut tree2 = MerkleTree::new();
        tree2.insert([1u8; 32]).expect("insert");
        tree2.insert([2u8; 32]).expect("insert");

        let diff = DiffResult::diff(&tree1, &tree2);
        assert!(diff.is_empty());
        assert_eq!(diff.len(), 0);
    }

    #[test]
    fn test_diff_remote_has_extra() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");
        local.insert([2u8; 32]).expect("insert");

        let mut remote = MerkleTree::new();
        remote.insert([1u8; 32]).expect("insert");
        remote.insert([2u8; 32]).expect("insert");
        remote.insert([3u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        assert_eq!(diff.len(), 1);
        assert_eq!(diff.missing_hashes, vec![[3u8; 32]]);
    }

    #[test]
    fn test_diff_remote_has_multiple_extra() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");

        let mut remote = MerkleTree::new();
        remote.insert([1u8; 32]).expect("insert");
        remote.insert([2u8; 32]).expect("insert");
        remote.insert([3u8; 32]).expect("insert");
        remote.insert([4u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        assert_eq!(diff.len(), 3);
        assert!(diff.missing_hashes.contains(&[2u8; 32]));
        assert!(diff.missing_hashes.contains(&[3u8; 32]));
        assert!(diff.missing_hashes.contains(&[4u8; 32]));
    }

    #[test]
    fn test_diff_local_has_extra() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");
        local.insert([2u8; 32]).expect("insert");
        local.insert([3u8; 32]).expect("insert");

        let mut remote = MerkleTree::new();
        remote.insert([1u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        // Remote doesn't have [2] or [3], but we only report what remote has that local doesn't
        assert!(diff.is_empty());
    }

    #[test]
    fn test_diff_disjoint_trees() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");
        local.insert([2u8; 32]).expect("insert");

        let mut remote = MerkleTree::new();
        remote.insert([3u8; 32]).expect("insert");
        remote.insert([4u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        assert_eq!(diff.len(), 2);
        assert!(diff.missing_hashes.contains(&[3u8; 32]));
        assert!(diff.missing_hashes.contains(&[4u8; 32]));
    }

    #[test]
    fn test_diff_empty_local() {
        let local = MerkleTree::new();

        let mut remote = MerkleTree::new();
        remote.insert([1u8; 32]).expect("insert");
        remote.insert([2u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        assert_eq!(diff.len(), 2);
        assert!(diff.missing_hashes.contains(&[1u8; 32]));
        assert!(diff.missing_hashes.contains(&[2u8; 32]));
    }

    #[test]
    fn test_diff_empty_remote() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");

        let remote = MerkleTree::new();

        let diff = DiffResult::diff(&local, &remote);
        assert!(diff.is_empty());
    }

    #[test]
    fn test_diff_both_empty() {
        let local = MerkleTree::new();
        let remote = MerkleTree::new();

        let diff = DiffResult::diff(&local, &remote);
        assert!(diff.is_empty());
    }

    #[test]
    fn test_diff_partial_overlap() {
        let mut local = MerkleTree::new();
        local.insert([1u8; 32]).expect("insert");
        local.insert([2u8; 32]).expect("insert");
        local.insert([3u8; 32]).expect("insert");

        let mut remote = MerkleTree::new();
        remote.insert([2u8; 32]).expect("insert");
        remote.insert([3u8; 32]).expect("insert");
        remote.insert([4u8; 32]).expect("insert");
        remote.insert([5u8; 32]).expect("insert");

        let diff = DiffResult::diff(&local, &remote);
        assert_eq!(diff.len(), 2);
        assert!(diff.missing_hashes.contains(&[4u8; 32]));
        assert!(diff.missing_hashes.contains(&[5u8; 32]));
    }
}
