//! Announces — the protocol's nervous system (`ARCHITECTURE.md` §3.5).

use plexus_core::AddressHash;

use crate::destination::destination_address;
use crate::error::Error;
use crate::identity::PublicIdentity;

const PUB_LEN: usize = 32;
const NAME_HASH_LEN: usize = 10;
const RANDOM_HASH_LEN: usize = 10;
const RATCHET_LEN: usize = 32;
const SIG_LEN: usize = 64;

/// The decoded body of an announce packet.
///
/// Wire order (§3.5): `enc_pub(32) || sig_pub(32) || name_hash(10) ||
/// random_hash(10) || [ratchet(32)] || signature(64) || app_data(..)`. The
/// ratchet is present iff the carrying packet's context flag is set.
pub struct AnnouncePayload {
    /// X25519 public encryption key.
    pub encryption_pub: [u8; 32],
    /// Ed25519 public signing key.
    pub signing_pub: [u8; 32],
    /// Destination name hash.
    pub name_hash: [u8; 10],
    /// Per-announce random hash.
    pub random_hash: [u8; 10],
    /// Optional ratchet public key (§3.9).
    pub ratchet: Option<[u8; 32]>,
    /// Ed25519 signature over the signed material.
    pub signature: [u8; 64],
    /// Opaque application data (may carry an extension TLV; §6.1).
    pub app_data: Vec<u8>,
}

impl AnnouncePayload {
    /// The data covered by the signature (§3.5):
    /// `dest_hash || enc_pub || sig_pub || name_hash || random_hash ||
    /// [ratchet] || app_data`.
    #[must_use]
    pub fn signed_data(&self, destination_hash: &AddressHash) -> Vec<u8> {
        let mut v = Vec::with_capacity(16 + 2 * PUB_LEN + NAME_HASH_LEN + RANDOM_HASH_LEN);
        v.extend_from_slice(destination_hash.as_bytes());
        v.extend_from_slice(&self.encryption_pub);
        v.extend_from_slice(&self.signing_pub);
        v.extend_from_slice(&self.name_hash);
        v.extend_from_slice(&self.random_hash);
        if let Some(ratchet) = &self.ratchet {
            v.extend_from_slice(ratchet);
        }
        v.extend_from_slice(&self.app_data);
        v
    }

    /// Verify the announce:
    /// 1. recompute the destination hash from `name_hash` + identity hash and
    ///    check it matches `destination_hash`;
    /// 2. verify the Ed25519 signature with `signing_pub`.
    ///
    /// # Errors
    /// [`Error::AddressMismatch`] if the recomputed address differs;
    /// [`Error::BadSignature`] if the signature does not verify.
    pub fn verify(&self, destination_hash: &AddressHash) -> Result<(), Error> {
        let identity = PublicIdentity::from_parts(self.encryption_pub, self.signing_pub);
        let expected = destination_address(&self.name_hash, &identity.hash());
        if expected != *destination_hash {
            return Err(Error::AddressMismatch);
        }
        let signed = self.signed_data(destination_hash);
        identity.verify(&signed, &self.signature)
    }

    /// Encode the announce body onto `buf`.
    pub fn encode(&self, buf: &mut Vec<u8>) {
        buf.extend_from_slice(&self.encryption_pub);
        buf.extend_from_slice(&self.signing_pub);
        buf.extend_from_slice(&self.name_hash);
        buf.extend_from_slice(&self.random_hash);
        if let Some(ratchet) = &self.ratchet {
            buf.extend_from_slice(ratchet);
        }
        buf.extend_from_slice(&self.signature);
        buf.extend_from_slice(&self.app_data);
    }

    /// Decode an announce body. `has_ratchet` comes from the carrying packet's
    /// context flag and determines whether a 32-byte ratchet is present.
    ///
    /// # Errors
    /// [`Error::Truncated`] if `bytes` is shorter than the fixed fields require.
    pub fn decode(bytes: &[u8], has_ratchet: bool) -> Result<Self, Error> {
        let ratchet_len = if has_ratchet { RATCHET_LEN } else { 0 };
        let fixed = 2 * PUB_LEN + NAME_HASH_LEN + RANDOM_HASH_LEN + ratchet_len + SIG_LEN;
        if bytes.len() < fixed {
            return Err(Error::Truncated);
        }
        let mut o = 0;
        let encryption_pub: [u8; PUB_LEN] = bytes[o..o + PUB_LEN].try_into().expect("PUB_LEN");
        o += PUB_LEN;
        let signing_pub: [u8; PUB_LEN] = bytes[o..o + PUB_LEN].try_into().expect("PUB_LEN");
        o += PUB_LEN;
        let name_hash: [u8; NAME_HASH_LEN] = bytes[o..o + NAME_HASH_LEN]
            .try_into()
            .expect("NAME_HASH_LEN");
        o += NAME_HASH_LEN;
        let random_hash: [u8; RANDOM_HASH_LEN] = bytes[o..o + RANDOM_HASH_LEN]
            .try_into()
            .expect("RANDOM_HASH_LEN");
        o += RANDOM_HASH_LEN;
        let ratchet = if has_ratchet {
            let r: [u8; RATCHET_LEN] = bytes[o..o + RATCHET_LEN].try_into().expect("RATCHET_LEN");
            o += RATCHET_LEN;
            Some(r)
        } else {
            None
        };
        let signature: [u8; SIG_LEN] = bytes[o..o + SIG_LEN].try_into().expect("SIG_LEN");
        o += SIG_LEN;
        let app_data = bytes[o..].to_vec();
        Ok(Self {
            encryption_pub,
            signing_pub,
            name_hash,
            random_hash,
            ratchet,
            signature,
            app_data,
        })
    }
}

/// Outbound announce queue plus dedup of announces already seen.
#[derive(Default)]
pub struct AnnounceTable {}

#[cfg(test)]
mod tests {
    use super::AnnouncePayload;
    use crate::error::Error;
    use hex_literal::hex;
    use plexus_core::AddressHash;

    // A real announce captured from RNS 1.3.1: identity 0x00..0x3F, destination
    // "plexus.test", app_data "hello", no ratchet (header context flag clear).
    const DEST: [u8; 16] = hex!("7dc45220f1867c50ead139c7e4083324");
    const ANNDATA: [u8; 153] = hex!(
        "8f40c5adb68f25624ae5b214ea767a6ec94d829d3d7b5e1ad1ba6f3e2138285f"
        "29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7"
        "038b878cc925d841c2c5"
        "807c57e2ff006a13360b"
        "a271fe3b5e15eab7525c18201eed47ea5f6dbf4197ae27f5169c0210c58001b5"
        "e33a27f3b2493405e52565225ca5f32a39df2e3e95a7ddf0df2e72fbb23fb008"
        "68656c6c6f"
    );

    #[test]
    fn decode_verify_reencode_reference() {
        let ann = AnnouncePayload::decode(&ANNDATA, false).unwrap();
        assert_eq!(ann.app_data, b"hello");
        assert!(ann.ratchet.is_none());
        // Verifies both the recomputed address and the Ed25519 signature.
        assert!(ann.verify(&AddressHash::new(DEST)).is_ok());
        // Byte-exact re-encode of a real reference announce.
        let mut buf = Vec::new();
        ann.encode(&mut buf);
        assert_eq!(buf, ANNDATA);
    }

    #[test]
    fn wrong_destination_is_address_mismatch() {
        let ann = AnnouncePayload::decode(&ANNDATA, false).unwrap();
        let mut wrong = DEST;
        wrong[0] ^= 0x01;
        assert_eq!(
            ann.verify(&AddressHash::new(wrong)),
            Err(Error::AddressMismatch)
        );
    }

    #[test]
    fn tampered_app_data_fails_signature() {
        let mut ann = AnnouncePayload::decode(&ANNDATA, false).unwrap();
        ann.app_data = b"HELLO".to_vec();
        assert_eq!(
            ann.verify(&AddressHash::new(DEST)),
            Err(Error::BadSignature)
        );
    }

    #[test]
    fn truncated_is_rejected() {
        assert!(matches!(
            AnnouncePayload::decode(&ANNDATA[..50], false),
            Err(Error::Truncated)
        ));
    }
}
