//! Mesh announce protocol for self-organizing network discovery.
//!
//! Nodes periodically broadcast signed [`MeshAnnounce`] packets. These propagate
//! through the mesh, building each node's [`RoutingTable`](crate::routing_table::RoutingTable).

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::identity::MeshIdentity;

/// Capability flag: node can relay messages for others.
pub const CAP_RELAY: u16 = 0x0001;
/// Capability flag: node has store-and-forward.
pub const CAP_STORE: u16 = 0x0002;
/// Capability flag: node is connected to Internet/server.
pub const CAP_GATEWAY: u16 = 0x0004;
/// Capability flag: node is on a low-bandwidth transport only.
pub const CAP_CONSTRAINED: u16 = 0x0008;

/// A signed mesh node announcement.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MeshAnnounce {
    /// Ed25519 public key of the announcing node (32 bytes).
    pub identity_key: Vec<u8>,
    /// Truncated address: SHA-256(identity_key)[0..16] — used for routing.
    pub address: [u8; 16],
    /// Capability bitfield.
    pub capabilities: u16,
    /// Monotonically increasing sequence number (per node).
    pub sequence: u64,
    /// Unix timestamp of creation.
    pub timestamp: u64,
    /// Transports this node is reachable on: Vec<(transport_name, serialized_addr)>.
    pub reachable_via: Vec<(String, Vec<u8>)>,
    /// Current hop count (incremented on re-broadcast).
    pub hop_count: u8,
    /// Maximum propagation hops.
    pub max_hops: u8,
    /// Ed25519 signature over all fields except signature and hop_count.
    pub signature: Vec<u8>,
}

/// Compute the 16-byte mesh address from an Ed25519 public key.
///
/// The address is the first 16 bytes of SHA-256(identity_key).
pub fn compute_address(identity_key: &[u8]) -> [u8; 16] {
    let hash = Sha256::digest(identity_key);
    let mut addr = [0u8; 16];
    addr.copy_from_slice(&hash[..16]);
    addr
}

impl MeshAnnounce {
    /// Create and sign a new mesh announcement.
    pub fn new(
        identity: &MeshIdentity,
        capabilities: u16,
        reachable_via: Vec<(String, Vec<u8>)>,
        max_hops: u8,
    ) -> Self {
        let identity_key = identity.public_key().to_vec();
        let address = compute_address(&identity_key);
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        let mut announce = Self {
            identity_key,
            address,
            capabilities,
            sequence: 0,
            timestamp,
            reachable_via,
            hop_count: 0,
            max_hops,
            signature: Vec::new(),
        };

        let signable = announce.signable_bytes();
        announce.signature = identity.sign(&signable).to_vec();
        announce
    }

    /// Create and sign with a specific sequence number.
    pub fn with_sequence(
        identity: &MeshIdentity,
        capabilities: u16,
        reachable_via: Vec<(String, Vec<u8>)>,
        max_hops: u8,
        sequence: u64,
    ) -> Self {
        let mut announce = Self::new(identity, capabilities, reachable_via, max_hops);
        announce.sequence = sequence;
        // Re-sign with the correct sequence number.
        let signable = announce.signable_bytes();
        announce.signature = identity.sign(&signable).to_vec();
        announce
    }

    /// Assemble the byte string that is signed / verified.
    ///
    /// `hop_count` and `signature` are excluded: forwarding nodes increment
    /// hop_count without re-signing (same design as [`MeshEnvelope`]).
    fn signable_bytes(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(
            self.identity_key.len() + 16 + 2 + 8 + 8 + self.reachable_via.len() * 32 + 1,
        );
        buf.extend_from_slice(&self.identity_key);
        buf.extend_from_slice(&self.address);
        buf.extend_from_slice(&self.capabilities.to_le_bytes());
        buf.extend_from_slice(&self.sequence.to_le_bytes());
        buf.extend_from_slice(&self.timestamp.to_le_bytes());
        for (name, addr) in &self.reachable_via {
            buf.extend_from_slice(name.as_bytes());
            buf.extend_from_slice(addr);
        }
        buf.push(self.max_hops);
        buf
    }

    /// Verify the Ed25519 signature on this announcement.
    pub fn verify(&self) -> bool {
        let identity_key: [u8; 32] = match self.identity_key.as_slice().try_into() {
            Ok(k) => k,
            Err(_) => return false,
        };
        let sig: [u8; 64] = match self.signature.as_slice().try_into() {
            Ok(s) => s,
            Err(_) => return false,
        };
        let signable = self.signable_bytes();
        quicprochat_core::IdentityKeypair::verify_raw(&identity_key, &signable, &sig).is_ok()
    }

    /// Check whether this announce has expired relative to a maximum age.
    pub fn is_expired(&self, max_age_secs: u64) -> bool {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        now.saturating_sub(self.timestamp) > max_age_secs
    }

    /// Create a forwarded copy with `hop_count` incremented by one.
    ///
    /// The signature remains the original — forwarding nodes do not re-sign.
    pub fn forwarded(&self) -> Self {
        let mut copy = self.clone();
        copy.hop_count = copy.hop_count.saturating_add(1);
        copy
    }

    /// Whether this announce can still propagate (under hop limit and not expired).
    ///
    /// Uses a generous default max age of 1800 seconds (30 minutes) for the
    /// expiry check. Callers that need a different max age should check
    /// [`is_expired`](Self::is_expired) separately.
    pub fn can_propagate(&self) -> bool {
        self.hop_count < self.max_hops && !self.is_expired(1800)
    }

    /// Serialize to compact CBOR binary format (for wire transmission).
    pub fn to_wire(&self) -> Vec<u8> {
        let mut buf = Vec::new();
        ciborium::into_writer(self, &mut buf).expect("CBOR serialization should not fail");
        buf
    }

    /// Deserialize from CBOR binary format.
    pub fn from_wire(bytes: &[u8]) -> anyhow::Result<Self> {
        let announce: Self = ciborium::from_reader(bytes)?;
        Ok(announce)
    }
}

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

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

    #[test]
    fn create_and_verify() {
        let id = test_identity();
        let announce = MeshAnnounce::new(
            &id,
            CAP_RELAY | CAP_STORE,
            vec![("tcp".into(), b"127.0.0.1:9000".to_vec())],
            8,
        );

        assert!(announce.verify(), "freshly created announce must verify");
        assert_eq!(announce.hop_count, 0);
        assert_eq!(announce.identity_key, id.public_key().to_vec());
        assert_eq!(announce.capabilities, CAP_RELAY | CAP_STORE);
        assert_eq!(announce.max_hops, 8);
    }

    #[test]
    fn tampered_fails_verify() {
        let id = test_identity();
        let mut announce = MeshAnnounce::new(&id, CAP_RELAY, vec![], 4);
        announce.capabilities = CAP_GATEWAY; // tamper
        assert!(
            !announce.verify(),
            "tampered announce must fail verification"
        );
    }

    #[test]
    fn forwarded_still_verifies() {
        let id = test_identity();
        let announce = MeshAnnounce::new(&id, CAP_RELAY, vec![], 8);
        assert!(announce.verify());

        let fwd = announce.forwarded();
        assert_eq!(fwd.hop_count, 1);
        assert!(
            fwd.verify(),
            "forwarded announce must still verify (hop_count excluded from signature)"
        );

        let fwd2 = fwd.forwarded();
        assert_eq!(fwd2.hop_count, 2);
        assert!(fwd2.verify(), "double-forwarded must still verify");
    }

    #[test]
    fn expired_announce() {
        let id = test_identity();
        let mut announce = MeshAnnounce::new(&id, 0, vec![], 4);
        // Set timestamp far in the past.
        announce.timestamp = 0;
        assert!(announce.is_expired(60), "announce from epoch should be expired with 60s max age");
    }

    #[test]
    fn address_from_key_deterministic() {
        let key = [42u8; 32];
        let addr1 = compute_address(&key);
        let addr2 = compute_address(&key);
        assert_eq!(addr1, addr2, "same key must produce same address");

        // Different key produces different address.
        let other_key = [99u8; 32];
        let other_addr = compute_address(&other_key);
        assert_ne!(addr1, other_addr);
    }

    #[test]
    fn cbor_roundtrip() {
        let id = test_identity();
        let announce = MeshAnnounce::new(
            &id,
            CAP_RELAY | CAP_GATEWAY,
            vec![
                ("tcp".into(), b"127.0.0.1:9000".to_vec()),
                ("lora".into(), vec![0x01, 0x02, 0x03, 0x04]),
            ],
            6,
        );

        let wire = announce.to_wire();
        let restored = MeshAnnounce::from_wire(&wire).expect("CBOR deserialize");

        assert_eq!(announce.identity_key, restored.identity_key);
        assert_eq!(announce.address, restored.address);
        assert_eq!(announce.capabilities, restored.capabilities);
        assert_eq!(announce.sequence, restored.sequence);
        assert_eq!(announce.timestamp, restored.timestamp);
        assert_eq!(announce.reachable_via, restored.reachable_via);
        assert_eq!(announce.hop_count, restored.hop_count);
        assert_eq!(announce.max_hops, restored.max_hops);
        assert_eq!(announce.signature, restored.signature);
        assert!(restored.verify());
    }
}
