//! Gossip broadcast trait for distributed replication.
//!
//! This module defines the `GossipBroadcast` trait that allows the IngestWorker
//! to broadcast newly ingested assertions to peer nodes.
//!
//! # Design
//!
//! The trait is defined here in stemedb-ingest to avoid a cyclic dependency:
//! - stemedb-ingest needs the trait for IngestWorker
//! - stemedb-sync implements the trait (and depends on stemedb-ingest would cause cycle)
//!
//! By defining the trait here, stemedb-sync can implement it without the cycle.

use async_trait::async_trait;
use stemedb_core::types::HlcTimestamp;
use thiserror::Error;

/// Error type for gossip operations.
#[derive(Debug, Error)]
pub enum GossipError {
    /// Network error during broadcast.
    #[error("Network error: {0}")]
    Network(String),

    /// Serialization error.
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// All peers failed to receive the message.
    #[error("All peers failed")]
    AllPeersFailed,
}

/// Trait for broadcasting assertions to peer nodes.
///
/// Implementations should be:
/// - **Non-blocking**: Don't wait for all peers to acknowledge
/// - **Best-effort**: Log failures but don't block the ingestion pipeline
/// - **Idempotent-friendly**: Receivers handle duplicates gracefully
///
/// # Example
///
/// ```ignore
/// use stemedb_ingest::gossip::GossipBroadcast;
///
/// struct MyBroadcaster { /* ... */ }
///
/// #[async_trait]
/// impl GossipBroadcast for MyBroadcaster {
///     async fn broadcast(&self, hash: &[u8; 32], data: &[u8], hlc: &HlcTimestamp) -> Result<(), GossipError> {
///         // Send to peers...
///         Ok(())
///     }
///
///     fn is_enabled(&self) -> bool { true }
///     fn enable(&self) {}
///     fn disable(&self) {}
/// }
/// ```
#[async_trait]
pub trait GossipBroadcast: Send + Sync {
    /// Broadcast an assertion to peer nodes.
    ///
    /// # Arguments
    ///
    /// * `hash` - BLAKE3 hash of the assertion (32 bytes)
    /// * `data` - Serialized assertion data (rkyv format)
    /// * `hlc` - HLC timestamp for causal ordering
    ///
    /// # Returns
    ///
    /// `Ok(())` if at least one peer received the message, or if no peers
    /// are configured. The method should not fail the ingestion pipeline.
    async fn broadcast(
        &self,
        hash: &[u8; 32],
        data: &[u8],
        hlc: &HlcTimestamp,
    ) -> Result<(), GossipError>;

    /// Check if broadcasting is currently enabled.
    fn is_enabled(&self) -> bool;

    /// Enable broadcasting.
    fn enable(&self);

    /// Disable broadcasting (e.g., for testing or during recovery).
    fn disable(&self);
}

/// A no-op implementation for single-node deployments or testing.
pub struct NoOpGossipBroadcast;

#[async_trait]
impl GossipBroadcast for NoOpGossipBroadcast {
    async fn broadcast(
        &self,
        _hash: &[u8; 32],
        _data: &[u8],
        _hlc: &HlcTimestamp,
    ) -> Result<(), GossipError> {
        // Do nothing
        Ok(())
    }

    fn is_enabled(&self) -> bool {
        false
    }

    fn enable(&self) {}
    fn disable(&self) {}
}

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

    #[tokio::test]
    async fn test_noop_broadcast() {
        let broadcaster = NoOpGossipBroadcast;
        let hash = [1u8; 32];
        let data = vec![1, 2, 3];
        let hlc = HlcTimestamp::new(1000, [1u8; 16]);

        // Should always succeed
        broadcaster.broadcast(&hash, &data, &hlc).await.expect("broadcast");
        assert!(!broadcaster.is_enabled());
    }
}
