//! Mesh traffic analysis resistance — uniform envelope padding and decoy injection.
//!
//! When the `traffic-resistance` feature is enabled:
//!
//! 1. [`MeshEnvelope`] payloads are padded to a configurable boundary (default 256 bytes)
//!    before signing, so all envelopes on the wire have uniform-sized payloads.
//! 2. A background decoy injector periodically creates fake mesh envelopes and stores
//!    them in the mesh store, making real vs. decoy traffic indistinguishable.

use std::sync::{Arc, Mutex};

use rand::Rng;
use tokio::sync::Notify;

use crate::envelope::MeshEnvelope;
use crate::identity::MeshIdentity;
use crate::store::MeshStore;

/// Configuration for mesh traffic analysis resistance.
#[derive(Clone, Debug)]
pub struct MeshTrafficConfig {
    /// Padding boundary in bytes for envelope payloads.
    pub padding_boundary: usize,

    /// Mean interval in milliseconds between decoy envelope injections.
    /// Set to 0 to disable.
    pub decoy_interval_ms: u64,
}

impl Default for MeshTrafficConfig {
    fn default() -> Self {
        Self {
            padding_boundary: quicproquo_core::padding::DEFAULT_PADDING_BOUNDARY,
            decoy_interval_ms: 5000,
        }
    }
}

/// Pad a mesh payload to the nearest boundary before wrapping in a [`MeshEnvelope`].
pub fn pad_mesh_payload(payload: &[u8], boundary: usize) -> Vec<u8> {
    quicproquo_core::padding::pad_uniform(payload, boundary)
}

/// Create a [`MeshEnvelope`] with a uniformly padded payload.
pub fn padded_envelope(
    identity: &MeshIdentity,
    recipient_key: &[u8],
    payload: Vec<u8>,
    ttl_secs: u32,
    max_hops: u8,
    boundary: usize,
) -> MeshEnvelope {
    let padded = pad_mesh_payload(&payload, boundary);
    MeshEnvelope::new(identity, recipient_key, padded, ttl_secs, max_hops)
}

/// Spawn a background task that injects decoy mesh envelopes into the store.
///
/// Decoy envelopes have random recipient keys and empty (padded) payloads.
/// They are indistinguishable from real padded envelopes on the wire.
pub fn spawn_mesh_decoy_generator(
    identity: MeshIdentity,
    store: Arc<Mutex<MeshStore>>,
    config: MeshTrafficConfig,
    shutdown: Arc<Notify>,
) -> tokio::task::JoinHandle<()> {
    tokio::spawn(async move {
        if config.decoy_interval_ms == 0 {
            shutdown.notified().await;
            return;
        }

        let base_interval = std::time::Duration::from_millis(config.decoy_interval_ms);

        loop {
            let jitter_factor: f64 = rand::thread_rng().gen_range(0.5..1.5);
            let interval = base_interval.mul_f64(jitter_factor);

            tokio::select! {
                () = tokio::time::sleep(interval) => {}
                () = shutdown.notified() => {
                    tracing::debug!("mesh decoy generator shutting down");
                    return;
                }
            }

            // Generate a decoy: padded empty payload with a random recipient.
            let decoy_payload = quicproquo_core::padding::generate_decoy(config.padding_boundary);
            let mut fake_recipient = [0u8; 32];
            rand::thread_rng().fill(&mut fake_recipient);

            let envelope = MeshEnvelope::new(
                &identity,
                &fake_recipient,
                decoy_payload,
                60, // Short TTL.
                0,
            );

            match store.lock() {
                Ok(mut s) => {
                    let _ = s.store(envelope);
                    tracing::trace!("mesh decoy envelope injected");
                }
                Err(e) => {
                    tracing::warn!(error = %e, "mesh store lock poisoned in decoy generator");
                }
            }
        }
    })
}

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

    #[test]
    fn pad_mesh_payload_boundary_aligned() {
        let payload = b"hello mesh";
        let padded = pad_mesh_payload(payload, 256);
        assert_eq!(padded.len() % 256, 0);

        let unpadded = quicproquo_core::padding::unpad_uniform(&padded).unwrap();
        assert_eq!(unpadded, payload);
    }

    #[test]
    fn padded_envelope_has_uniform_payload() {
        let id = MeshIdentity::generate();
        let recipient = [0xAA; 32];
        let payload = b"short".to_vec();

        let env = padded_envelope(&id, &recipient, payload, 3600, 5, 256);
        assert_eq!(env.payload.len() % 256, 0);
        assert!(env.verify());

        // The inner payload should unpad correctly.
        let unpadded = quicproquo_core::padding::unpad_uniform(&env.payload).unwrap();
        assert_eq!(unpadded, b"short");
    }

    #[test]
    fn padded_envelope_large_payload() {
        let id = MeshIdentity::generate();
        let payload = vec![0xBB; 500];

        let env = padded_envelope(&id, &[0xCC; 32], payload.clone(), 60, 3, 256);
        assert_eq!(env.payload.len() % 256, 0);
        assert_eq!(env.payload.len(), 512); // 500 + 4 = 504, rounds to 512

        let unpadded = quicproquo_core::padding::unpad_uniform(&env.payload).unwrap();
        assert_eq!(unpadded, payload);
    }

    #[tokio::test]
    async fn mesh_decoy_generator_injects_envelopes() {
        let id = MeshIdentity::generate();
        let store = Arc::new(Mutex::new(MeshStore::new(100)));
        let shutdown = Arc::new(Notify::new());

        let config = MeshTrafficConfig {
            padding_boundary: 256,
            decoy_interval_ms: 50,
        };

        let handle = spawn_mesh_decoy_generator(
            id,
            Arc::clone(&store),
            config,
            Arc::clone(&shutdown),
        );

        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
        shutdown.notify_one();
        handle.await.unwrap();

        let s = store.lock().unwrap();
        let (total, _) = s.stats();
        assert!(total > 0, "decoy generator should have stored at least one envelope");
    }

    #[tokio::test]
    async fn mesh_decoy_generator_disabled_when_zero() {
        let id = MeshIdentity::generate();
        let store = Arc::new(Mutex::new(MeshStore::new(100)));
        let shutdown = Arc::new(Notify::new());

        let config = MeshTrafficConfig {
            padding_boundary: 256,
            decoy_interval_ms: 0,
        };

        let handle = spawn_mesh_decoy_generator(
            id,
            store,
            config,
            Arc::clone(&shutdown),
        );

        shutdown.notify_one();
        handle.await.unwrap();
    }
}
