//! Announce protocol engine — sends, receives, and propagates mesh announcements.
//!
//! This module ties together [`MeshAnnounce`], [`RoutingTable`], and
//! deduplication logic to form a complete announce processing pipeline.

use std::collections::HashSet;
use std::time::Duration;

use crate::announce::MeshAnnounce;
use crate::identity::MeshIdentity;
use crate::routing_table::RoutingTable;
use crate::transport::TransportAddr;

/// Configuration for the announce protocol.
#[derive(Clone, Debug)]
pub struct AnnounceConfig {
    /// Interval between periodic re-announcements.
    pub announce_interval: Duration,
    /// Maximum age before an announce is considered expired.
    pub max_announce_age: Duration,
    /// Maximum hops for announce propagation.
    pub max_hops: u8,
    /// This node's capabilities.
    pub capabilities: u16,
    /// Interval for routing table garbage collection.
    pub gc_interval: Duration,
}

impl Default for AnnounceConfig {
    fn default() -> Self {
        Self {
            announce_interval: Duration::from_secs(600), // 10 minutes
            max_announce_age: Duration::from_secs(1800), // 30 minutes
            max_hops: 8,
            capabilities: 0,
            gc_interval: Duration::from_secs(60),
        }
    }
}

/// Tracks which announces we've already seen (to prevent re-broadcast loops).
pub struct AnnounceDedup {
    /// Set of (address, sequence) pairs we've seen.
    seen: HashSet<([u8; 16], u64)>,
    /// Maximum entries before pruning.
    max_entries: usize,
}

impl AnnounceDedup {
    /// Create a new dedup tracker with the given capacity.
    pub fn new(max_entries: usize) -> Self {
        Self {
            seen: HashSet::new(),
            max_entries,
        }
    }

    /// Check if this announce is new (not seen before).
    ///
    /// Returns `true` if the (address, sequence) pair has not been seen before,
    /// and adds it to the set. Returns `false` if it was already seen.
    pub fn is_new(&mut self, address: &[u8; 16], sequence: u64) -> bool {
        if self.seen.len() >= self.max_entries {
            self.prune();
        }
        self.seen.insert((*address, sequence))
    }

    /// Remove all entries when the set exceeds capacity.
    ///
    /// Uses a simple clear-all strategy; a more sophisticated implementation
    /// could track insertion order and evict oldest entries.
    pub fn prune(&mut self) {
        self.seen.clear();
    }
}

/// Create this node's own mesh announcement.
pub fn create_announce(
    identity: &MeshIdentity,
    config: &AnnounceConfig,
    sequence: u64,
    reachable_via: Vec<(String, Vec<u8>)>,
) -> MeshAnnounce {
    MeshAnnounce::with_sequence(
        identity,
        config.capabilities,
        reachable_via,
        config.max_hops,
        sequence,
    )
}

/// Process a received mesh announcement.
///
/// Steps:
/// 1. Verify signature — return `None` if invalid.
/// 2. Check if expired — return `None` if stale.
/// 3. Check dedup — return `None` if already seen.
/// 4. Update routing table.
/// 5. If `can_propagate` — return `Some(forwarded)` for re-broadcast.
/// 6. Otherwise return `None`.
pub fn process_received_announce(
    announce: &MeshAnnounce,
    routing_table: &mut RoutingTable,
    dedup: &mut AnnounceDedup,
    received_via: &str,
    received_from: TransportAddr,
    max_age: Duration,
) -> Option<MeshAnnounce> {
    // 1. Verify signature.
    if !announce.verify() {
        return None;
    }

    // 2. Check expiry.
    if announce.is_expired(max_age.as_secs()) {
        return None;
    }

    // 3. Dedup check.
    if !dedup.is_new(&announce.address, announce.sequence) {
        return None;
    }

    // 4. Update routing table.
    routing_table.update(announce, received_via, received_from);

    // 5. Check if the announce can propagate further.
    if announce.hop_count < announce.max_hops && !announce.is_expired(max_age.as_secs()) {
        Some(announce.forwarded())
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::announce::CAP_RELAY;
    use crate::identity::MeshIdentity;

    fn test_identity() -> MeshIdentity {
        MeshIdentity::generate()
    }

    fn default_config() -> AnnounceConfig {
        AnnounceConfig {
            capabilities: CAP_RELAY,
            ..AnnounceConfig::default()
        }
    }

    #[test]
    fn create_announce_is_valid() {
        let id = test_identity();
        let config = default_config();
        let announce = create_announce(
            &id,
            &config,
            1,
            vec![("tcp".into(), b"127.0.0.1:9000".to_vec())],
        );

        assert!(announce.verify());
        assert_eq!(announce.sequence, 1);
        assert_eq!(announce.capabilities, CAP_RELAY);
        assert_eq!(announce.max_hops, 8);
        assert_eq!(announce.hop_count, 0);
    }

    #[test]
    fn process_valid_announce_updates_table() {
        let id = test_identity();
        let config = default_config();
        let announce = create_announce(&id, &config, 1, vec![]);

        let mut table = RoutingTable::new(Duration::from_secs(300));
        let mut dedup = AnnounceDedup::new(1000);
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());

        let result = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr,
            Duration::from_secs(1800),
        );

        // Should propagate (hop_count 0 < max_hops 8).
        assert!(result.is_some());
        // Routing table should have the entry.
        assert_eq!(table.len(), 1);
    }

    #[test]
    fn process_duplicate_ignored() {
        let id = test_identity();
        let config = default_config();
        let announce = create_announce(&id, &config, 1, vec![]);

        let mut table = RoutingTable::new(Duration::from_secs(300));
        let mut dedup = AnnounceDedup::new(1000);
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());

        // First time — accepted.
        let result1 = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr.clone(),
            Duration::from_secs(1800),
        );
        assert!(result1.is_some());

        // Second time — duplicate, ignored.
        let result2 = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr,
            Duration::from_secs(1800),
        );
        assert!(result2.is_none());
    }

    #[test]
    fn process_expired_ignored() {
        let id = test_identity();
        let config = default_config();
        let mut announce = create_announce(&id, &config, 1, vec![]);
        // Set timestamp far in the past.
        announce.timestamp = 0;

        let mut table = RoutingTable::new(Duration::from_secs(300));
        let mut dedup = AnnounceDedup::new(1000);
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());

        let result = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr,
            Duration::from_secs(60),
        );
        assert!(result.is_none(), "expired announce must be ignored");
        assert!(table.is_empty());
    }

    #[test]
    fn process_invalid_sig_ignored() {
        let id = test_identity();
        let config = default_config();
        let mut announce = create_announce(&id, &config, 1, vec![]);
        // Tamper with capabilities to invalidate signature.
        announce.capabilities = 0xFFFF;

        let mut table = RoutingTable::new(Duration::from_secs(300));
        let mut dedup = AnnounceDedup::new(1000);
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());

        let result = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr,
            Duration::from_secs(1800),
        );
        assert!(result.is_none(), "tampered announce must be ignored");
        assert!(table.is_empty());
    }

    #[test]
    fn process_returns_forwarded_for_propagation() {
        let id = test_identity();
        let config = default_config();
        let announce = create_announce(&id, &config, 1, vec![]);
        assert_eq!(announce.hop_count, 0);

        let mut table = RoutingTable::new(Duration::from_secs(300));
        let mut dedup = AnnounceDedup::new(1000);
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());

        let result = process_received_announce(
            &announce,
            &mut table,
            &mut dedup,
            "tcp",
            addr,
            Duration::from_secs(1800),
        );

        let forwarded = result.expect("should return forwarded announce");
        assert_eq!(forwarded.hop_count, 1);
        assert!(forwarded.verify(), "forwarded announce must still verify");
    }
}
