//! CRDT (Conflict-free Replicated Data Type) implementations for distributed StemeDB.
//!
//! This module provides CRDT wrappers around existing storage types to enable
//! conflict-free replication across multiple nodes. The key insight is that
//! StemeDB's existing storage operations already have CRDT semantics:
//!
//! - **Votes**: G-Counter semantics (counts only increase)
//! - **Assertions**: G-Set semantics (append-only, never removed)
//!
//! These wrappers add explicit `merge()` operations for replication.
//!
//! # Design Principles
//!
//! 1. **Wrap, don't replace**: CRDT types wrap existing stores rather than
//!    reimplementing them, preserving all existing functionality.
//!
//! 2. **Merge is idempotent**: `merge(A, A) == A` - safe to replay messages.
//!
//! 3. **Merge is commutative**: `merge(A, B) == merge(B, A)` - order doesn't matter.
//!
//! 4. **Merge is associative**: `merge(merge(A, B), C) == merge(A, merge(B, C))`.
//!
//! # State Types
//!
//! Each CRDT defines a state type that can be extracted, transferred over the
//! network, and merged into another replica. States are designed for efficient
//! delta synchronization.
//!
//! # Example
//!
//! ```ignore
//! use stemedb_storage::crdt::{CrdtVoteStore, CrdtMerge, VoteCountState};
//!
//! // Local node
//! let local = CrdtVoteStore::new(store, node_id);
//!
//! // Get state to send to remote
//! let state = local.get_state("Tesla_Inc").await?;
//!
//! // On remote node, merge received state
//! remote.merge("Tesla_Inc", &state).await?;
//! ```

mod assertion_store;
mod traits;
mod vote_store;
#[cfg(test)]
mod vote_store_props;

pub use assertion_store::{AssertionSetState, AssertionTransfer, CrdtAssertionStore};
pub use traits::CrdtMerge;
pub use vote_store::{CrdtVoteStore, VoteCountState};

#[cfg(test)]
mod tests {
    //! Property-based tests for CRDT laws.
    //!
    //! These tests verify the fundamental CRDT properties:
    //! - Commutativity: merge(A, B) == merge(B, A)
    //! - Associativity: merge(merge(A, B), C) == merge(A, merge(B, C))
    //! - Idempotence: merge(A, A) == A

    // Property tests are in the submodules with proptest
}

/// Integration tests demonstrating end-to-end CRDT operations.
#[cfg(test)]
mod integration_tests {
    use super::*;
    use crate::vote_store::VoteStore;
    use crate::HybridStore;
    use std::sync::Arc;
    use stemedb_core::types::Vote;
    use tempfile::tempdir;

    async fn create_test_store() -> Arc<HybridStore> {
        let dir = tempdir().expect("Failed to create temp dir");
        Arc::new(HybridStore::open(dir.path()).expect("Failed to open store"))
    }

    /// Tests concurrent vote ingestion across multiple nodes, then merge.
    ///
    /// Simulates:
    /// 1. Node A receives votes from agents 1, 2, 3
    /// 2. Node B receives votes from agents 4, 5
    /// 3. Nodes exchange state and merge
    /// 4. Both nodes should converge to the same final state
    #[tokio::test]
    async fn test_multi_node_vote_convergence() {
        // Create two independent "nodes" with their own stores
        let store_a = create_test_store().await;
        let store_b = create_test_store().await;

        let node_a = CrdtVoteStore::new(store_a, [1u8; 16]);
        let node_b = CrdtVoteStore::new(store_b, [2u8; 16]);

        let assertion_hash = [42u8; 32];
        let subject = "test_subject";

        // Node A receives 3 votes
        for i in 0..3 {
            let vote = Vote {
                assertion_hash,
                agent_id: [i as u8; 32],
                weight: 0.5,
                signature: [0u8; 64],
                timestamp: 1000 + i as u64,
                source_url: None,
                observed_context: None,
            };
            node_a.put_vote(&vote, subject).await.expect("put vote");
        }

        // Node B receives 2 votes
        for i in 3..5 {
            let vote = Vote {
                assertion_hash,
                agent_id: [i as u8; 32],
                weight: 0.3,
                signature: [0u8; 64],
                timestamp: 1000 + i as u64,
                source_url: None,
                observed_context: None,
            };
            node_b.put_vote(&vote, subject).await.expect("put vote");
        }

        // Verify initial states differ
        let count_a = node_a.get_vote_count(&assertion_hash, subject).await.expect("count");
        let count_b = node_b.get_vote_count(&assertion_hash, subject).await.expect("count");
        assert_eq!(count_a, 3);
        assert_eq!(count_b, 2);

        // Exchange and merge state
        let state_a = node_a.get_state(subject).await.expect("get state");
        let state_b = node_b.get_state(subject).await.expect("get state");

        node_b.merge(subject, &state_a).await.expect("merge a->b");
        node_a.merge(subject, &state_b).await.expect("merge b->a");

        // Verify convergence: both should have max(3, 2) = 3 votes
        let final_count_a = node_a.get_vote_count(&assertion_hash, subject).await.expect("count");
        let final_count_b = node_b.get_vote_count(&assertion_hash, subject).await.expect("count");

        assert_eq!(final_count_a, 3, "Node A should converge to highest count");
        assert_eq!(final_count_b, 3, "Node B should converge to highest count");
    }

    /// Tests assertion set merge across nodes.
    ///
    /// Simulates:
    /// 1. Node A has assertions [A1, A2]
    /// 2. Node B has assertions [A2, A3]
    /// 3. After merge, both should have [A1, A2, A3]
    #[tokio::test]
    async fn test_assertion_set_merge() {
        let store_a = create_test_store().await;
        let store_b = create_test_store().await;

        let node_a = CrdtAssertionStore::new(store_a, [1u8; 16]);
        let node_b = CrdtAssertionStore::new(store_b, [2u8; 16]);

        let subject = "test_subject";

        // Node A: assertions 1 and 2
        let hash_a1 = node_a.put_assertion(subject, b"assertion 1").await.expect("put");
        let hash_a2 = node_a.put_assertion(subject, b"assertion 2").await.expect("put");

        // Node B: assertions 2 and 3 (2 is same content, so same hash)
        let hash_b2 = node_b.put_assertion(subject, b"assertion 2").await.expect("put");
        let hash_b3 = node_b.put_assertion(subject, b"assertion 3").await.expect("put");

        // A2 and B2 should have the same hash (content-addressed)
        assert_eq!(hash_a2, hash_b2, "Same content should produce same hash");

        // Get initial states
        let state_a = node_a.get_state(subject).await.expect("get state");
        let state_b = node_b.get_state(subject).await.expect("get state");

        assert_eq!(state_a.assertion_hashes.len(), 2);
        assert_eq!(state_b.assertion_hashes.len(), 2);

        // Find what B has that A doesn't (should be hash_b3 only)
        let missing_from_a = node_a.find_missing(subject, &state_b).await.expect("find missing");
        assert_eq!(missing_from_a.len(), 1);
        assert_eq!(missing_from_a[0], hash_b3);

        // Find what A has that B doesn't (should be hash_a1 only)
        let missing_from_b = node_b.find_missing(subject, &state_a).await.expect("find missing");
        assert_eq!(missing_from_b.len(), 1);
        assert_eq!(missing_from_b[0], hash_a1);

        // Simulate data transfer and merge
        let transfer_to_a = vec![assertion_store::AssertionTransfer {
            hash: hash_b3,
            data: b"assertion 3".to_vec(),
        }];
        node_a.merge_with_data(subject, &transfer_to_a).await.expect("merge");

        let transfer_to_b = vec![assertion_store::AssertionTransfer {
            hash: hash_a1,
            data: b"assertion 1".to_vec(),
        }];
        node_b.merge_with_data(subject, &transfer_to_b).await.expect("merge");

        // Verify both nodes now have 3 unique assertions
        let final_state_a = node_a.get_state(subject).await.expect("get state");
        let final_state_b = node_b.get_state(subject).await.expect("get state");

        assert_eq!(final_state_a.assertion_hashes.len(), 3);
        assert_eq!(final_state_b.assertion_hashes.len(), 3);

        // Both should have all three hashes
        assert!(final_state_a.contains(&hash_a1));
        assert!(final_state_a.contains(&hash_a2));
        assert!(final_state_a.contains(&hash_b3));
    }
}
