//! Store-and-forward message envelope for mesh routing.
//!
//! A [`MeshEnvelope`] wraps an encrypted payload with routing metadata
//! (sender/recipient keys, TTL, hop count) and an Ed25519 signature for
//! integrity. Envelopes are deduplicated by a SHA-256 content ID.

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

use crate::identity::MeshIdentity;

/// Default maximum hops for mesh forwarding.
const DEFAULT_MAX_HOPS: u8 = 5;

/// A signed, routable message envelope for mesh store-and-forward.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MeshEnvelope {
    /// SHA-256 content ID (for deduplication).
    pub id: [u8; 32],
    /// 32-byte Ed25519 public key of the sender.
    pub sender_key: Vec<u8>,
    /// 32-byte Ed25519 public key of the recipient (empty for broadcast).
    pub recipient_key: Vec<u8>,
    /// Encrypted message body (opaque to the mesh layer).
    pub payload: Vec<u8>,
    /// Time-to-live in seconds from `timestamp`.
    pub ttl_secs: u32,
    /// Current hop count (incremented on each forward).
    pub hop_count: u8,
    /// Maximum allowed hops before the envelope is dropped.
    pub max_hops: u8,
    /// Unix timestamp (seconds) of creation.
    pub timestamp: u64,
    /// Ed25519 signature over all fields except `signature` itself.
    pub signature: Vec<u8>,
}

impl MeshEnvelope {
    /// Create and sign a new mesh envelope.
    pub fn new(
        identity: &MeshIdentity,
        recipient_key: &[u8],
        payload: Vec<u8>,
        ttl_secs: u32,
        max_hops: u8,
    ) -> Self {
        let sender_key = identity.public_key().to_vec();
        let recipient_key = recipient_key.to_vec();
        let hop_count = 0u8;
        let max_hops = if max_hops == 0 {
            DEFAULT_MAX_HOPS
        } else {
            max_hops
        };
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        let id = Self::compute_id(
            &sender_key,
            &recipient_key,
            &payload,
            ttl_secs,
            max_hops,
            timestamp,
        );

        let signable = Self::signable_bytes(&id, &sender_key, &recipient_key, &payload, ttl_secs, max_hops, timestamp);
        let signature = identity.sign(&signable).to_vec();

        Self {
            id,
            sender_key,
            recipient_key,
            payload,
            ttl_secs,
            hop_count,
            max_hops,
            timestamp,
            signature,
        }
    }

    /// Compute the content ID from the immutable envelope fields.
    pub fn compute_id(
        sender_key: &[u8],
        recipient_key: &[u8],
        payload: &[u8],
        ttl_secs: u32,
        max_hops: u8,
        timestamp: u64,
    ) -> [u8; 32] {
        let mut hasher = Sha256::new();
        hasher.update(sender_key);
        hasher.update(recipient_key);
        hasher.update(payload);
        hasher.update(ttl_secs.to_le_bytes());
        hasher.update([max_hops]);
        hasher.update(timestamp.to_le_bytes());
        hasher.finalize().into()
    }

    /// Assemble the byte string that is signed / verified.
    ///
    /// `hop_count` is intentionally excluded: forwarding nodes increment it
    /// without re-signing, so including it would invalidate the sender's
    /// original signature on every hop.
    fn signable_bytes(
        id: &[u8; 32],
        sender_key: &[u8],
        recipient_key: &[u8],
        payload: &[u8],
        ttl_secs: u32,
        max_hops: u8,
        timestamp: u64,
    ) -> Vec<u8> {
        let mut buf = Vec::with_capacity(32 + sender_key.len() + recipient_key.len() + payload.len() + 13);
        buf.extend_from_slice(id);
        buf.extend_from_slice(sender_key);
        buf.extend_from_slice(recipient_key);
        buf.extend_from_slice(payload);
        buf.extend_from_slice(&ttl_secs.to_le_bytes());
        buf.push(max_hops);
        buf.extend_from_slice(&timestamp.to_le_bytes());
        buf
    }

    /// Verify the envelope's Ed25519 signature.
    ///
    /// Returns `true` if the signature is valid and the sender key is a valid
    /// Ed25519 public key.
    pub fn verify(&self) -> bool {
        let sender_key: [u8; 32] = match self.sender_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(
            &self.id,
            &self.sender_key,
            &self.recipient_key,
            &self.payload,
            self.ttl_secs,
            self.max_hops,
            self.timestamp,
        );
        quicprochat_core::IdentityKeypair::verify_raw(&sender_key, &signable, &sig).is_ok()
    }

    /// Check whether this envelope has expired (TTL elapsed since timestamp).
    pub fn is_expired(&self) -> bool {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        now.saturating_sub(self.timestamp) > self.ttl_secs as u64
    }

    /// Whether this envelope can be forwarded (not expired and under hop limit).
    pub fn can_forward(&self) -> bool {
        self.hop_count < self.max_hops && !self.is_expired()
    }

    /// Create a forwarded copy with `hop_count` incremented by one.
    ///
    /// The signature remains the sender's original signature — 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
    }

    /// 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 env: Self = ciborium::from_reader(bytes)?;
        Ok(env)
    }

    /// Deserialize from wire format, trying CBOR first then JSON fallback.
    pub fn from_wire_or_json(bytes: &[u8]) -> anyhow::Result<Self> {
        Self::from_wire(bytes).or_else(|_| Self::from_bytes(bytes))
    }

    /// Serialize to bytes (JSON). Kept for backward compatibility and debugging.
    pub fn to_bytes(&self) -> Vec<u8> {
        // serde_json::to_vec should not fail on a well-formed envelope.
        serde_json::to_vec(self).expect("envelope serialization should not fail")
    }

    /// Deserialize from bytes (JSON). Kept for backward compatibility and debugging.
    pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
        let env: Self = serde_json::from_slice(bytes)?;
        Ok(env)
    }
}

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

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

    #[test]
    fn create_and_verify() {
        let id = test_identity();
        let recipient = [0xBBu8; 32];
        let env = MeshEnvelope::new(&id, &recipient, b"hello mesh".to_vec(), 3600, 5);

        assert!(env.verify(), "freshly created envelope must verify");
        assert!(!env.is_expired());
        assert!(env.can_forward());
        assert_eq!(env.hop_count, 0);
        assert_eq!(env.sender_key, id.public_key().to_vec());
        assert_eq!(env.recipient_key, recipient.to_vec());
    }

    #[test]
    fn tampered_payload_fails_verify() {
        let id = test_identity();
        let mut env = MeshEnvelope::new(&id, &[0xCC; 32], b"original".to_vec(), 60, 3);
        env.payload = b"tampered".to_vec();
        assert!(!env.verify(), "tampered envelope must fail verification");
    }

    #[test]
    fn expired_envelope() {
        let id = test_identity();
        let mut env = MeshEnvelope::new(&id, &[0xDD; 32], b"old".to_vec(), 0, 5);
        // Set timestamp to the past so TTL of 0 guarantees expiry.
        env.timestamp = 0;
        assert!(env.is_expired());
        assert!(!env.can_forward());
    }

    #[test]
    fn forward_increments_hop() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[0xEE; 32], b"hop".to_vec(), 3600, 2);
        assert_eq!(env.hop_count, 0);

        let fwd1 = env.forwarded();
        assert_eq!(fwd1.hop_count, 1);
        assert!(fwd1.can_forward());

        let fwd2 = fwd1.forwarded();
        assert_eq!(fwd2.hop_count, 2);
        assert!(!fwd2.can_forward()); // hop_count == max_hops
    }

    #[test]
    fn forwarded_envelope_still_verifies() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[0xAA; 32], b"fwd-verify".to_vec(), 3600, 5);
        assert!(env.verify(), "original must verify");

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

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

    #[test]
    fn verify_with_wrong_key_fails() {
        let id = test_identity();
        let mut env = MeshEnvelope::new(&id, &[0xBB; 32], b"wrong-key".to_vec(), 3600, 5);
        // Replace sender_key with a different key
        let other = test_identity();
        env.sender_key = other.public_key().to_vec();
        assert!(!env.verify(), "wrong sender key must fail verification");
    }

    #[test]
    fn serialization_roundtrip() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[0xFF; 32], b"roundtrip".to_vec(), 300, 4);
        let bytes = env.to_bytes();
        let restored = MeshEnvelope::from_bytes(&bytes).expect("deserialize");
        assert_eq!(env.id, restored.id);
        assert_eq!(env.payload, restored.payload);
        assert!(restored.verify());
    }

    #[test]
    fn default_max_hops_when_zero() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[0x11; 32], b"defaults".to_vec(), 60, 0);
        assert_eq!(env.max_hops, 5); // DEFAULT_MAX_HOPS
    }

    #[test]
    fn broadcast_envelope_empty_recipient() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[], b"broadcast".to_vec(), 60, 3);
        assert!(env.recipient_key.is_empty());
        assert!(env.verify());
    }

    #[test]
    fn cbor_roundtrip() {
        let id = test_identity();
        let recipient = [0xABu8; 32];
        let env = MeshEnvelope::new(&id, &recipient, b"cbor roundtrip".to_vec(), 3600, 5);

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

        assert_eq!(env.id, restored.id);
        assert_eq!(env.sender_key, restored.sender_key);
        assert_eq!(env.recipient_key, restored.recipient_key);
        assert_eq!(env.payload, restored.payload);
        assert_eq!(env.ttl_secs, restored.ttl_secs);
        assert_eq!(env.hop_count, restored.hop_count);
        assert_eq!(env.max_hops, restored.max_hops);
        assert_eq!(env.timestamp, restored.timestamp);
        assert_eq!(env.signature, restored.signature);
        assert!(restored.verify());
    }

    #[test]
    fn cbor_smaller_than_json() {
        let id = test_identity();
        let recipient = [0xCCu8; 32];
        let payload = b"a typical chat message for size comparison testing".to_vec();
        let env = MeshEnvelope::new(&id, &recipient, payload, 3600, 5);

        let wire_len = env.to_wire().len();
        let json_len = env.to_bytes().len();

        println!("CBOR wire size: {wire_len} bytes");
        println!("JSON size:      {json_len} bytes");
        println!("Ratio:          {:.1}x smaller", json_len as f64 / wire_len as f64);

        assert!(
            json_len * 2 > wire_len * 3,
            "CBOR ({wire_len}B) should be materially smaller than JSON ({json_len}B)"
        );
    }

    #[test]
    fn cbor_backward_compat() {
        let id = test_identity();
        let env = MeshEnvelope::new(&id, &[0xDD; 32], b"json compat".to_vec(), 60, 3);

        // Serialize as JSON (old format).
        let json_bytes = env.to_bytes();

        // from_wire_or_json should fall back to JSON parsing.
        let restored = MeshEnvelope::from_wire_or_json(&json_bytes)
            .expect("from_wire_or_json should handle JSON");
        assert_eq!(env.id, restored.id);
        assert_eq!(env.payload, restored.payload);
        assert!(restored.verify());
    }

    #[test]
    fn cbor_from_wire_rejects_garbage() {
        let garbage = [0xFF, 0xFE, 0x00, 0x42, 0x99, 0x01, 0x02, 0x03];
        let result = MeshEnvelope::from_wire(&garbage);
        assert!(result.is_err(), "garbage input must return Err, not panic");
    }

    /// Measure MeshEnvelope overhead for various payload sizes.
    /// This informs constrained link feasibility planning.
    #[test]
    fn measure_mesh_envelope_overhead() {
        let id = test_identity();
        let recipient = [0xAAu8; 32];

        println!("=== MeshEnvelope Wire Overhead (CBOR) ===");

        // Empty payload
        let env_empty = MeshEnvelope::new(&id, &recipient, vec![], 3600, 5);
        let wire_empty = env_empty.to_wire();
        println!("Payload   0B: wire {} bytes (overhead: {} bytes)", wire_empty.len(), wire_empty.len());
        let base_overhead = wire_empty.len();

        // 1-byte payload
        let env_1 = MeshEnvelope::new(&id, &recipient, vec![0x42], 3600, 5);
        let wire_1 = env_1.to_wire();
        println!("Payload   1B: wire {} bytes (overhead: {} bytes)", wire_1.len(), wire_1.len() - 1);

        // 10-byte payload ("hello mesh")
        let env_10 = MeshEnvelope::new(&id, &recipient, b"hello mesh".to_vec(), 3600, 5);
        let wire_10 = env_10.to_wire();
        println!("Payload  10B: wire {} bytes (overhead: {} bytes)", wire_10.len(), wire_10.len() - 10);

        // 50-byte payload
        let env_50 = MeshEnvelope::new(&id, &recipient, vec![0x42; 50], 3600, 5);
        let wire_50 = env_50.to_wire();
        println!("Payload  50B: wire {} bytes (overhead: {} bytes)", wire_50.len(), wire_50.len() - 50);

        // 100-byte payload (typical short message)
        let env_100 = MeshEnvelope::new(&id, &recipient, vec![0x42; 100], 3600, 5);
        let wire_100 = env_100.to_wire();
        println!("Payload 100B: wire {} bytes (overhead: {} bytes)", wire_100.len(), wire_100.len() - 100);

        // Broadcast (empty recipient) - saves 32 bytes
        let env_bc = MeshEnvelope::new(&id, &[], b"broadcast".to_vec(), 3600, 5);
        let wire_bc = env_bc.to_wire();
        println!("Broadcast 9B: wire {} bytes (no recipient)", wire_bc.len());

        println!("\n=== LoRa Feasibility (SF12/BW125, MTU=51 bytes) ===");
        println!("Empty envelope:    {} fragments", (wire_empty.len() + 50) / 51);
        println!("10B payload:       {} fragments", (wire_10.len() + 50) / 51);
        println!("100B payload:      {} fragments", (wire_100.len() + 50) / 51);

        // Baseline overhead is fixed fields:
        // - id: 32 bytes
        // - sender_key: 32 bytes
        // - recipient_key: 32 bytes (or 0 for broadcast)
        // - signature: 64 bytes
        // - ttl_secs: 4 bytes
        // - hop_count: 1 byte
        // - max_hops: 1 byte
        // - timestamp: 8 bytes
        // Total fixed: ~174 bytes raw, CBOR adds overhead for field names/types
        // Actual measured: ~400+ bytes with CBOR (field names add significant overhead)
        assert!(base_overhead < 500, "Base overhead should be under 500 bytes");
        assert!(base_overhead > 100, "Base overhead should be over 100 bytes (sanity check)");
    }
}
