//! Secured TDM protocol over QUIC transport (ADR-032a).
//!
//! Wraps the existing `TdmCoordinator` and `SyncBeacon` types with
//! QUIC-based authenticated transport. Supports dual-mode operation:
//! QUIC for aggregator-class nodes and manual crypto for ESP32-S3.
//!
//! # Architecture
//!
//! ```text
//! SecureTdmCoordinator
//!   |-- TdmCoordinator (schedule, cycle state)
//!   |-- QuicTransportHandle (optional, for QUIC mode)
//!   |-- SecurityMode (selects QUIC vs manual)
//!   |-- ReplayWindow (nonce-based replay protection for manual mode)
//! ```
//!
//! # Beacon Authentication Flow
//!
//! ## QUIC mode
//! 1. Coordinator calls `begin_secure_cycle()`
//! 2. Beacon serialized to 16-byte wire format (original)
//! 3. Wrapped in `FramedMessage` with type `Beacon`
//! 4. Sent over QUIC stream 0 (encrypted + authenticated by TLS 1.3)
//!
//! ## Manual crypto mode
//! 1. Coordinator calls `begin_secure_cycle()`
//! 2. Beacon serialized to 28-byte authenticated format (ADR-032 Section 2.1)
//! 3. HMAC-SHA256 tag computed over payload + nonce
//! 4. Sent over plain UDP

use super::quic_transport::{
    FramedMessage, MessageType, QuicTransportConfig, QuicTransportError, QuicTransportHandle,
    SecurityMode,
};
use super::tdm::{SyncBeacon, TdmCoordinator, TdmSchedule, TdmSlotCompleted};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::collections::VecDeque;
use std::fmt;

type HmacSha256 = Hmac<Sha256>;

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

/// Size of the HMAC-SHA256 truncated tag (manual crypto mode).
const HMAC_TAG_SIZE: usize = 8;

/// Size of the nonce field (manual crypto mode).
const NONCE_SIZE: usize = 4;

/// Replay window size (number of past nonces to track).
const REPLAY_WINDOW: u32 = 16;

/// Size of the authenticated beacon (manual crypto mode): 16 + 4 + 8 = 28.
pub const AUTHENTICATED_BEACON_SIZE: usize = 16 + NONCE_SIZE + HMAC_TAG_SIZE;

/// Default pre-shared key for testing (16 bytes). In production, this
/// would be loaded from NVS or a secure key store.
const DEFAULT_TEST_KEY: [u8; 16] = [
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
];

// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------

/// Errors from the secure TDM layer.
#[derive(Debug, Clone, PartialEq)]
pub enum SecureTdmError {
    /// The beacon HMAC tag verification failed.
    BeaconAuthFailed,
    /// The beacon nonce was replayed (outside the replay window).
    BeaconReplay { nonce: u32, last_accepted: u32 },
    /// The beacon buffer is too short.
    BeaconTooShort { expected: usize, got: usize },
    /// QUIC transport error.
    Transport(QuicTransportError),
    /// The security mode does not match the incoming packet format.
    ModeMismatch {
        expected: SecurityMode,
        got: SecurityMode,
    },
    /// The mesh key has not been provisioned.
    NoMeshKey,
}

impl fmt::Display for SecureTdmError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SecureTdmError::BeaconAuthFailed => write!(f, "Beacon HMAC verification failed"),
            SecureTdmError::BeaconReplay {
                nonce,
                last_accepted,
            } => {
                write!(
                    f,
                    "Beacon replay: nonce {} <= last_accepted {} - REPLAY_WINDOW",
                    nonce, last_accepted
                )
            }
            SecureTdmError::BeaconTooShort { expected, got } => {
                write!(
                    f,
                    "Beacon too short: expected {} bytes, got {}",
                    expected, got
                )
            }
            SecureTdmError::Transport(e) => write!(f, "Transport error: {}", e),
            SecureTdmError::ModeMismatch { expected, got } => {
                write!(
                    f,
                    "Security mode mismatch: expected {}, got {}",
                    expected, got
                )
            }
            SecureTdmError::NoMeshKey => write!(f, "Mesh key not provisioned"),
        }
    }
}

impl std::error::Error for SecureTdmError {}

impl From<QuicTransportError> for SecureTdmError {
    fn from(e: QuicTransportError) -> Self {
        SecureTdmError::Transport(e)
    }
}

// ---------------------------------------------------------------------------
// Replay window
// ---------------------------------------------------------------------------

/// Replay protection window for manual crypto mode.
///
/// Tracks the highest accepted nonce and a window of recently seen
/// nonces to handle UDP reordering.
#[derive(Debug, Clone)]
pub struct ReplayWindow {
    /// Highest nonce value accepted so far.
    last_accepted: u32,
    /// Window size.
    window_size: u32,
    /// Recently seen nonces within the window (for dedup).
    seen: VecDeque<u32>,
}

impl ReplayWindow {
    /// Create a new replay window with the given size.
    pub fn new(window_size: u32) -> Self {
        Self {
            last_accepted: 0,
            window_size,
            seen: VecDeque::with_capacity(window_size as usize),
        }
    }

    /// Check if a nonce is acceptable (not replayed).
    ///
    /// Returns `true` if the nonce should be accepted.
    pub fn check(&self, nonce: u32) -> bool {
        if nonce == 0 && self.last_accepted == 0 && self.seen.is_empty() {
            // First nonce ever
            return true;
        }
        if self.last_accepted >= self.window_size
            && nonce < self.last_accepted.saturating_sub(self.window_size)
        {
            // Too old
            return false;
        }
        // Check for exact duplicate within window
        !self.seen.contains(&nonce)
    }

    /// Accept a nonce, updating the window state.
    ///
    /// Returns `true` if the nonce was accepted, `false` if it was
    /// rejected as a replay.
    pub fn accept(&mut self, nonce: u32) -> bool {
        if !self.check(nonce) {
            return false;
        }

        self.seen.push_back(nonce);
        if self.seen.len() > self.window_size as usize {
            self.seen.pop_front();
        }

        if nonce > self.last_accepted {
            self.last_accepted = nonce;
        }

        true
    }

    /// Current highest accepted nonce.
    pub fn last_accepted(&self) -> u32 {
        self.last_accepted
    }

    /// Number of nonces currently tracked in the window.
    pub fn window_count(&self) -> usize {
        self.seen.len()
    }
}

// ---------------------------------------------------------------------------
// Authenticated beacon (manual crypto mode)
// ---------------------------------------------------------------------------

/// An authenticated beacon in the manual crypto wire format (28 bytes).
///
/// ```text
/// [0..16]  SyncBeacon payload (cycle_id, period, drift, reserved)
/// [16..20] nonce (LE u32, monotonically increasing)
/// [20..28] hmac_tag (HMAC-SHA256 truncated to 8 bytes)
/// ```
#[derive(Debug, Clone)]
pub struct AuthenticatedBeacon {
    /// The underlying sync beacon.
    pub beacon: SyncBeacon,
    /// Monotonic nonce for replay protection.
    pub nonce: u32,
    /// HMAC-SHA256 truncated tag (8 bytes).
    pub hmac_tag: [u8; HMAC_TAG_SIZE],
}

impl AuthenticatedBeacon {
    /// Serialize to the 28-byte authenticated wire format.
    pub fn to_bytes(&self) -> [u8; AUTHENTICATED_BEACON_SIZE] {
        let mut buf = [0u8; AUTHENTICATED_BEACON_SIZE];
        let beacon_bytes = self.beacon.to_bytes();
        buf[..16].copy_from_slice(&beacon_bytes);
        buf[16..20].copy_from_slice(&self.nonce.to_le_bytes());
        buf[20..28].copy_from_slice(&self.hmac_tag);
        buf
    }

    /// Deserialize from the 28-byte authenticated wire format.
    ///
    /// Does NOT verify the HMAC tag -- call `verify()` separately.
    pub fn from_bytes(buf: &[u8]) -> Result<Self, SecureTdmError> {
        if buf.len() < AUTHENTICATED_BEACON_SIZE {
            return Err(SecureTdmError::BeaconTooShort {
                expected: AUTHENTICATED_BEACON_SIZE,
                got: buf.len(),
            });
        }
        let beacon = SyncBeacon::from_bytes(&buf[..16]).ok_or(SecureTdmError::BeaconTooShort {
            expected: 16,
            got: buf.len(),
        })?;
        let nonce = u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]);
        let mut hmac_tag = [0u8; HMAC_TAG_SIZE];
        hmac_tag.copy_from_slice(&buf[20..28]);
        Ok(Self {
            beacon,
            nonce,
            hmac_tag,
        })
    }

    /// Compute the HMAC-SHA256 tag for this beacon, truncated to 8 bytes.
    ///
    /// Uses the `hmac` + `sha2` crates for cryptographically secure
    /// message authentication (ADR-050, Sprint 1).
    pub fn compute_tag(payload_and_nonce: &[u8], key: &[u8; 16]) -> [u8; HMAC_TAG_SIZE] {
        let mut mac = HmacSha256::new_from_slice(key).expect("HMAC-SHA256 accepts any key length");
        mac.update(payload_and_nonce);
        let result = mac.finalize().into_bytes();
        let mut tag = [0u8; HMAC_TAG_SIZE];
        tag.copy_from_slice(&result[..HMAC_TAG_SIZE]);
        tag
    }

    /// Verify the HMAC tag using the given key.
    pub fn verify(&self, key: &[u8; 16]) -> Result<(), SecureTdmError> {
        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&self.beacon.to_bytes());
        msg[16..20].copy_from_slice(&self.nonce.to_le_bytes());
        let expected = Self::compute_tag(&msg, key);
        if self.hmac_tag == expected {
            Ok(())
        } else {
            Err(SecureTdmError::BeaconAuthFailed)
        }
    }
}

// ---------------------------------------------------------------------------
// Secure TDM coordinator
// ---------------------------------------------------------------------------

/// Security configuration for the secure TDM coordinator.
#[derive(Debug, Clone)]
pub struct SecureTdmConfig {
    /// Security mode (QUIC or manual crypto).
    pub security_mode: SecurityMode,
    /// Pre-shared mesh key (16 bytes) for manual crypto mode.
    pub mesh_key: Option<[u8; 16]>,
    /// QUIC transport configuration (used if mode is QuicTransport).
    pub quic_config: QuicTransportConfig,
    /// Security enforcement level.
    pub sec_level: SecLevel,
}

/// Security enforcement level (ADR-032 Section 2.8).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecLevel {
    /// Accept unauthenticated frames, log warning.
    Permissive = 0,
    /// Accept both authenticated and unauthenticated.
    Transitional = 1,
    /// Reject unauthenticated frames.
    Enforcing = 2,
}

impl Default for SecureTdmConfig {
    fn default() -> Self {
        Self {
            security_mode: SecurityMode::QuicTransport,
            mesh_key: Some(DEFAULT_TEST_KEY),
            quic_config: QuicTransportConfig::default(),
            sec_level: SecLevel::Transitional,
        }
    }
}

/// Secure TDM coordinator that wraps `TdmCoordinator` with authenticated
/// transport.
///
/// Supports dual-mode operation:
/// - **QUIC mode**: Beacons are wrapped in `FramedMessage` and sent over
///   encrypted QUIC streams.
/// - **Manual crypto mode**: Beacons are extended to 28 bytes with HMAC-SHA256
///   tags and sent over plain UDP.
#[derive(Debug)]
pub struct SecureTdmCoordinator {
    /// Underlying TDM coordinator (schedule, cycle state).
    inner: TdmCoordinator,
    /// Security configuration.
    config: SecureTdmConfig,
    /// Monotonic nonce counter (manual crypto mode).
    nonce_counter: u32,
    /// QUIC transport handle (if QUIC mode is active).
    transport: Option<QuicTransportHandle>,
    /// Replay window for received beacons (manual crypto mode).
    replay_window: ReplayWindow,
    /// Total beacons produced.
    beacons_produced: u64,
    /// Total beacons verified.
    beacons_verified: u64,
    /// Total verification failures.
    verification_failures: u64,
}

impl SecureTdmCoordinator {
    /// Create a new secure TDM coordinator.
    pub fn new(schedule: TdmSchedule, config: SecureTdmConfig) -> Result<Self, SecureTdmError> {
        let transport = if config.security_mode == SecurityMode::QuicTransport {
            Some(QuicTransportHandle::new(config.quic_config.clone())?)
        } else {
            None
        };

        Ok(Self {
            inner: TdmCoordinator::new(schedule),
            config,
            nonce_counter: 0,
            transport,
            replay_window: ReplayWindow::new(REPLAY_WINDOW),
            beacons_produced: 0,
            beacons_verified: 0,
            verification_failures: 0,
        })
    }

    /// Begin a new secure sensing cycle.
    ///
    /// Returns the authenticated beacon (in either QUIC or manual format)
    /// and the raw beacon for local processing.
    pub fn begin_secure_cycle(&mut self) -> Result<SecureCycleOutput, SecureTdmError> {
        let beacon = self.inner.begin_cycle();
        self.beacons_produced += 1;

        match self.config.security_mode {
            SecurityMode::ManualCrypto => {
                let key = self.config.mesh_key.ok_or(SecureTdmError::NoMeshKey)?;
                self.nonce_counter = self.nonce_counter.wrapping_add(1);

                let mut msg = [0u8; 20];
                msg[..16].copy_from_slice(&beacon.to_bytes());
                msg[16..20].copy_from_slice(&self.nonce_counter.to_le_bytes());
                let tag = AuthenticatedBeacon::compute_tag(&msg, &key);

                let auth_beacon = AuthenticatedBeacon {
                    beacon: beacon.clone(),
                    nonce: self.nonce_counter,
                    hmac_tag: tag,
                };

                Ok(SecureCycleOutput {
                    beacon,
                    authenticated_bytes: auth_beacon.to_bytes().to_vec(),
                    mode: SecurityMode::ManualCrypto,
                })
            }
            SecurityMode::QuicTransport => {
                let beacon_bytes = beacon.to_bytes();
                let framed = FramedMessage::new(MessageType::Beacon, beacon_bytes.to_vec());
                let wire = framed.to_bytes();

                if let Some(ref mut transport) = self.transport {
                    transport.record_beacon_sent(wire.len());
                }

                Ok(SecureCycleOutput {
                    beacon,
                    authenticated_bytes: wire,
                    mode: SecurityMode::QuicTransport,
                })
            }
        }
    }

    /// Verify a received beacon.
    ///
    /// In manual crypto mode, verifies the HMAC tag and replay window.
    /// In QUIC mode, the transport layer already provides authentication.
    pub fn verify_beacon(&mut self, buf: &[u8]) -> Result<SyncBeacon, SecureTdmError> {
        match self.config.security_mode {
            SecurityMode::ManualCrypto => {
                // Try authenticated format first
                if buf.len() >= AUTHENTICATED_BEACON_SIZE {
                    let auth = AuthenticatedBeacon::from_bytes(buf)?;
                    let key = self.config.mesh_key.ok_or(SecureTdmError::NoMeshKey)?;
                    match auth.verify(&key) {
                        Ok(()) => {
                            if !self.replay_window.accept(auth.nonce) {
                                self.verification_failures += 1;
                                return Err(SecureTdmError::BeaconReplay {
                                    nonce: auth.nonce,
                                    last_accepted: self.replay_window.last_accepted(),
                                });
                            }
                            self.beacons_verified += 1;
                            Ok(auth.beacon)
                        }
                        Err(e) => {
                            self.verification_failures += 1;
                            Err(e)
                        }
                    }
                } else if buf.len() >= 16 && self.config.sec_level != SecLevel::Enforcing {
                    // Accept unauthenticated 16-byte beacon in permissive/transitional
                    let beacon =
                        SyncBeacon::from_bytes(buf).ok_or(SecureTdmError::BeaconTooShort {
                            expected: 16,
                            got: buf.len(),
                        })?;
                    self.beacons_verified += 1;
                    Ok(beacon)
                } else {
                    Err(SecureTdmError::BeaconTooShort {
                        expected: AUTHENTICATED_BEACON_SIZE,
                        got: buf.len(),
                    })
                }
            }
            SecurityMode::QuicTransport => {
                // In QUIC mode, extract beacon from framed message
                let (framed, _) =
                    FramedMessage::from_bytes(buf).ok_or(SecureTdmError::BeaconTooShort {
                        expected: 5 + 16,
                        got: buf.len(),
                    })?;
                if framed.message_type != MessageType::Beacon {
                    return Err(SecureTdmError::ModeMismatch {
                        expected: SecurityMode::QuicTransport,
                        got: SecurityMode::ManualCrypto,
                    });
                }
                let beacon = SyncBeacon::from_bytes(&framed.payload).ok_or(
                    SecureTdmError::BeaconTooShort {
                        expected: 16,
                        got: framed.payload.len(),
                    },
                )?;
                self.beacons_verified += 1;

                if let Some(ref mut transport) = self.transport {
                    transport.record_beacon_received(buf.len());
                }

                Ok(beacon)
            }
        }
    }

    /// Complete a slot in the current cycle (delegates to inner coordinator).
    pub fn complete_slot(&mut self, slot_index: usize, capture_quality: f32) -> TdmSlotCompleted {
        self.inner.complete_slot(slot_index, capture_quality)
    }

    /// Whether the current cycle is complete.
    pub fn is_cycle_complete(&self) -> bool {
        self.inner.is_cycle_complete()
    }

    /// Current cycle ID.
    pub fn cycle_id(&self) -> u64 {
        self.inner.cycle_id()
    }

    /// Active security mode.
    pub fn security_mode(&self) -> SecurityMode {
        self.config.security_mode
    }

    /// Reference to the underlying TDM coordinator.
    pub fn inner(&self) -> &TdmCoordinator {
        &self.inner
    }

    /// Total beacons produced.
    pub fn beacons_produced(&self) -> u64 {
        self.beacons_produced
    }

    /// Total beacons successfully verified.
    pub fn beacons_verified(&self) -> u64 {
        self.beacons_verified
    }

    /// Total verification failures.
    pub fn verification_failures(&self) -> u64 {
        self.verification_failures
    }

    /// Reference to the QUIC transport handle (if available).
    pub fn transport(&self) -> Option<&QuicTransportHandle> {
        self.transport.as_ref()
    }

    /// Mutable reference to the QUIC transport handle (if available).
    pub fn transport_mut(&mut self) -> Option<&mut QuicTransportHandle> {
        self.transport.as_mut()
    }

    /// Current nonce counter value (manual crypto mode).
    pub fn nonce_counter(&self) -> u32 {
        self.nonce_counter
    }

    /// Reference to the replay window.
    pub fn replay_window(&self) -> &ReplayWindow {
        &self.replay_window
    }

    /// Security enforcement level.
    pub fn sec_level(&self) -> SecLevel {
        self.config.sec_level
    }
}

/// Output from `begin_secure_cycle()`.
#[derive(Debug, Clone)]
pub struct SecureCycleOutput {
    /// The underlying sync beacon (for local processing).
    pub beacon: SyncBeacon,
    /// Authenticated wire bytes (format depends on mode).
    pub authenticated_bytes: Vec<u8>,
    /// Security mode used for this beacon.
    pub mode: SecurityMode,
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::esp32::tdm::TdmSchedule;
    use std::time::Duration;

    fn test_schedule() -> TdmSchedule {
        TdmSchedule::default_4node()
    }

    fn manual_config() -> SecureTdmConfig {
        SecureTdmConfig {
            security_mode: SecurityMode::ManualCrypto,
            mesh_key: Some(DEFAULT_TEST_KEY),
            quic_config: QuicTransportConfig::default(),
            sec_level: SecLevel::Transitional,
        }
    }

    fn quic_config() -> SecureTdmConfig {
        SecureTdmConfig {
            security_mode: SecurityMode::QuicTransport,
            mesh_key: Some(DEFAULT_TEST_KEY),
            quic_config: QuicTransportConfig::default(),
            sec_level: SecLevel::Transitional,
        }
    }

    // ---- ReplayWindow tests ----

    #[test]
    fn test_replay_window_new() {
        let rw = ReplayWindow::new(16);
        assert_eq!(rw.last_accepted(), 0);
        assert_eq!(rw.window_count(), 0);
    }

    #[test]
    fn test_replay_window_accept_first() {
        let mut rw = ReplayWindow::new(16);
        assert!(rw.accept(0)); // First nonce accepted
        assert_eq!(rw.window_count(), 1);
    }

    #[test]
    fn test_replay_window_monotonic() {
        let mut rw = ReplayWindow::new(16);
        assert!(rw.accept(1));
        assert!(rw.accept(2));
        assert!(rw.accept(3));
        assert_eq!(rw.last_accepted(), 3);
    }

    #[test]
    fn test_replay_window_reject_duplicate() {
        let mut rw = ReplayWindow::new(16);
        assert!(rw.accept(1));
        assert!(!rw.accept(1)); // Duplicate rejected
    }

    #[test]
    fn test_replay_window_accept_within_window() {
        let mut rw = ReplayWindow::new(16);
        assert!(rw.accept(5));
        assert!(rw.accept(3)); // Out of order but within window
        assert_eq!(rw.last_accepted(), 5);
    }

    #[test]
    fn test_replay_window_reject_too_old() {
        let mut rw = ReplayWindow::new(4);
        for i in 0..20 {
            rw.accept(i);
        }
        // Nonce 0 is way outside the window
        assert!(!rw.accept(0));
    }

    #[test]
    fn test_replay_window_evicts_old() {
        let mut rw = ReplayWindow::new(4);
        for i in 0..10 {
            rw.accept(i);
        }
        assert!(rw.window_count() <= 4);
    }

    // ---- AuthenticatedBeacon tests ----

    #[test]
    fn test_auth_beacon_roundtrip() {
        let beacon = SyncBeacon {
            cycle_id: 42,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: -3,
            generated_at: std::time::Instant::now(),
        };
        let key = DEFAULT_TEST_KEY;
        let nonce = 7u32;

        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&beacon.to_bytes());
        msg[16..20].copy_from_slice(&nonce.to_le_bytes());
        let tag = AuthenticatedBeacon::compute_tag(&msg, &key);

        let auth = AuthenticatedBeacon {
            beacon,
            nonce,
            hmac_tag: tag,
        };

        let bytes = auth.to_bytes();
        assert_eq!(bytes.len(), AUTHENTICATED_BEACON_SIZE);

        let decoded = AuthenticatedBeacon::from_bytes(&bytes).unwrap();
        assert_eq!(decoded.beacon.cycle_id, 42);
        assert_eq!(decoded.nonce, 7);
        assert_eq!(decoded.hmac_tag, tag);
    }

    #[test]
    fn test_auth_beacon_verify_ok() {
        let beacon = SyncBeacon {
            cycle_id: 100,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let key = DEFAULT_TEST_KEY;
        let nonce = 1u32;

        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&beacon.to_bytes());
        msg[16..20].copy_from_slice(&nonce.to_le_bytes());
        let tag = AuthenticatedBeacon::compute_tag(&msg, &key);

        let auth = AuthenticatedBeacon {
            beacon,
            nonce,
            hmac_tag: tag,
        };
        assert!(auth.verify(&key).is_ok());
    }

    #[test]
    fn test_auth_beacon_verify_tampered() {
        let beacon = SyncBeacon {
            cycle_id: 100,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let key = DEFAULT_TEST_KEY;
        let nonce = 1u32;

        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&beacon.to_bytes());
        msg[16..20].copy_from_slice(&nonce.to_le_bytes());
        let mut tag = AuthenticatedBeacon::compute_tag(&msg, &key);
        tag[0] ^= 0xFF; // Tamper with tag

        let auth = AuthenticatedBeacon {
            beacon,
            nonce,
            hmac_tag: tag,
        };
        assert!(matches!(
            auth.verify(&key),
            Err(SecureTdmError::BeaconAuthFailed)
        ));
    }

    #[test]
    fn test_auth_beacon_too_short() {
        let result = AuthenticatedBeacon::from_bytes(&[0u8; 10]);
        assert!(matches!(result, Err(SecureTdmError::BeaconTooShort { .. })));
    }

    #[test]
    fn test_auth_beacon_size_constant() {
        assert_eq!(AUTHENTICATED_BEACON_SIZE, 28);
    }

    // ---- SecureTdmCoordinator tests (manual crypto) ----

    #[test]
    fn test_secure_coordinator_manual_create() {
        let coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        assert_eq!(coord.security_mode(), SecurityMode::ManualCrypto);
        assert_eq!(coord.beacons_produced(), 0);
        assert!(coord.transport().is_none());
    }

    #[test]
    fn test_secure_coordinator_manual_begin_cycle() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        assert_eq!(output.mode, SecurityMode::ManualCrypto);
        assert_eq!(output.authenticated_bytes.len(), AUTHENTICATED_BEACON_SIZE);
        assert_eq!(output.beacon.cycle_id, 0);
        assert_eq!(coord.beacons_produced(), 1);
        assert_eq!(coord.nonce_counter(), 1);
    }

    #[test]
    fn test_secure_coordinator_manual_nonce_increments() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();

        for expected_nonce in 1..=5u32 {
            let _output = coord.begin_secure_cycle().unwrap();
            // Complete all slots
            for i in 0..4 {
                coord.complete_slot(i, 1.0);
            }
            assert_eq!(coord.nonce_counter(), expected_nonce);
        }
    }

    #[test]
    fn test_secure_coordinator_manual_verify_own_beacon() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        // Create a second coordinator to verify
        let mut verifier = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        let beacon = verifier.verify_beacon(&output.authenticated_bytes).unwrap();
        assert_eq!(beacon.cycle_id, 0);
    }

    #[test]
    fn test_secure_coordinator_manual_reject_tampered() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        let mut tampered = output.authenticated_bytes.clone();
        tampered[25] ^= 0xFF; // Tamper with HMAC tag

        let mut verifier = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        assert!(verifier.verify_beacon(&tampered).is_err());
        assert_eq!(verifier.verification_failures(), 1);
    }

    #[test]
    fn test_secure_coordinator_manual_reject_replay() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        let mut verifier = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();

        // First acceptance succeeds
        verifier.verify_beacon(&output.authenticated_bytes).unwrap();

        // Replay of same beacon fails
        let result = verifier.verify_beacon(&output.authenticated_bytes);
        assert!(result.is_err());
    }

    #[test]
    fn test_secure_coordinator_manual_backward_compat_permissive() {
        let mut cfg = manual_config();
        cfg.sec_level = SecLevel::Permissive;
        let mut coord = SecureTdmCoordinator::new(test_schedule(), cfg).unwrap();

        // Send an unauthenticated 16-byte beacon
        let beacon = SyncBeacon {
            cycle_id: 99,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let bytes = beacon.to_bytes();

        let verified = coord.verify_beacon(&bytes).unwrap();
        assert_eq!(verified.cycle_id, 99);
    }

    #[test]
    fn test_secure_coordinator_manual_reject_unauthenticated_enforcing() {
        let mut cfg = manual_config();
        cfg.sec_level = SecLevel::Enforcing;
        let mut coord = SecureTdmCoordinator::new(test_schedule(), cfg).unwrap();

        let beacon = SyncBeacon {
            cycle_id: 99,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let bytes = beacon.to_bytes();

        // 16-byte unauthenticated beacon rejected in enforcing mode
        let result = coord.verify_beacon(&bytes);
        assert!(result.is_err());
    }

    #[test]
    fn test_secure_coordinator_no_mesh_key() {
        let cfg = SecureTdmConfig {
            security_mode: SecurityMode::ManualCrypto,
            mesh_key: None,
            ..Default::default()
        };
        let mut coord = SecureTdmCoordinator::new(test_schedule(), cfg).unwrap();
        let result = coord.begin_secure_cycle();
        assert!(matches!(result, Err(SecureTdmError::NoMeshKey)));
    }

    // ---- SecureTdmCoordinator tests (QUIC mode) ----

    #[test]
    fn test_secure_coordinator_quic_create() {
        let coord = SecureTdmCoordinator::new(test_schedule(), quic_config()).unwrap();
        assert_eq!(coord.security_mode(), SecurityMode::QuicTransport);
        assert!(coord.transport().is_some());
    }

    #[test]
    fn test_secure_coordinator_quic_begin_cycle() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), quic_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        assert_eq!(output.mode, SecurityMode::QuicTransport);
        // QUIC framed: 5-byte header + 16-byte beacon = 21 bytes
        assert_eq!(output.authenticated_bytes.len(), 5 + 16);
        assert_eq!(coord.beacons_produced(), 1);
    }

    #[test]
    fn test_secure_coordinator_quic_verify_own_beacon() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), quic_config()).unwrap();
        let output = coord.begin_secure_cycle().unwrap();

        let mut verifier = SecureTdmCoordinator::new(test_schedule(), quic_config()).unwrap();
        let beacon = verifier.verify_beacon(&output.authenticated_bytes).unwrap();
        assert_eq!(beacon.cycle_id, 0);
    }

    #[test]
    fn test_secure_coordinator_complete_cycle() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();
        coord.begin_secure_cycle().unwrap();

        for i in 0..4 {
            let event = coord.complete_slot(i, 0.95);
            assert_eq!(event.slot_index, i);
        }
        assert!(coord.is_cycle_complete());
    }

    #[test]
    fn test_secure_coordinator_cycle_id_increments() {
        let mut coord = SecureTdmCoordinator::new(test_schedule(), manual_config()).unwrap();

        let out0 = coord.begin_secure_cycle().unwrap();
        assert_eq!(out0.beacon.cycle_id, 0);
        for i in 0..4 {
            coord.complete_slot(i, 1.0);
        }

        let out1 = coord.begin_secure_cycle().unwrap();
        assert_eq!(out1.beacon.cycle_id, 1);
    }

    // ---- SecLevel tests ----

    #[test]
    fn test_sec_level_values() {
        assert_eq!(SecLevel::Permissive as u8, 0);
        assert_eq!(SecLevel::Transitional as u8, 1);
        assert_eq!(SecLevel::Enforcing as u8, 2);
    }

    // ---- Security tests (ADR-050) ----

    #[test]
    fn test_hmac_different_keys_produce_different_tags() {
        let msg = b"test payload with nonce";
        let key1: [u8; 16] = [0x01; 16];
        let key2: [u8; 16] = [0x02; 16];
        let tag1 = AuthenticatedBeacon::compute_tag(msg, &key1);
        let tag2 = AuthenticatedBeacon::compute_tag(msg, &key2);
        assert_ne!(
            tag1, tag2,
            "Different keys must produce different HMAC tags"
        );
    }

    #[test]
    fn test_hmac_different_messages_produce_different_tags() {
        let key: [u8; 16] = DEFAULT_TEST_KEY;
        let tag1 = AuthenticatedBeacon::compute_tag(b"message one", &key);
        let tag2 = AuthenticatedBeacon::compute_tag(b"message two", &key);
        assert_ne!(
            tag1, tag2,
            "Different messages must produce different HMAC tags"
        );
    }

    #[test]
    fn test_hmac_is_deterministic() {
        let key: [u8; 16] = DEFAULT_TEST_KEY;
        let msg = b"determinism test";
        let tag1 = AuthenticatedBeacon::compute_tag(msg, &key);
        let tag2 = AuthenticatedBeacon::compute_tag(msg, &key);
        assert_eq!(tag1, tag2, "Same key + message must produce identical tags");
    }

    #[test]
    fn test_wrong_key_fails_verification() {
        let beacon = SyncBeacon {
            cycle_id: 42,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let correct_key: [u8; 16] = DEFAULT_TEST_KEY;
        let wrong_key: [u8; 16] = [0xFF; 16];
        let nonce = 1u32;

        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&beacon.to_bytes());
        msg[16..20].copy_from_slice(&nonce.to_le_bytes());
        let tag = AuthenticatedBeacon::compute_tag(&msg, &correct_key);

        let auth = AuthenticatedBeacon {
            beacon,
            nonce,
            hmac_tag: tag,
        };
        assert!(
            auth.verify(&wrong_key).is_err(),
            "Wrong key must fail verification"
        );
    }

    #[test]
    fn test_single_bit_flip_in_payload_fails_verification() {
        let beacon = SyncBeacon {
            cycle_id: 42,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        };
        let key: [u8; 16] = DEFAULT_TEST_KEY;
        let nonce = 1u32;

        let mut msg = [0u8; 20];
        msg[..16].copy_from_slice(&beacon.to_bytes());
        msg[16..20].copy_from_slice(&nonce.to_le_bytes());
        let tag = AuthenticatedBeacon::compute_tag(&msg, &key);

        let auth = AuthenticatedBeacon {
            beacon,
            nonce,
            hmac_tag: tag,
        };
        let mut wire = auth.to_bytes();
        // Flip one bit in the beacon payload
        wire[0] ^= 0x01;
        let tampered = AuthenticatedBeacon::from_bytes(&wire).unwrap();
        assert!(
            tampered.verify(&key).is_err(),
            "Single bit flip must fail verification"
        );
    }

    #[test]
    fn test_enforcing_mode_rejects_unauthenticated() {
        let mut cfg = manual_config();
        cfg.sec_level = SecLevel::Enforcing;
        let mut coord = SecureTdmCoordinator::new(test_schedule(), cfg).unwrap();

        // Raw 16-byte beacon without HMAC
        let raw = SyncBeacon {
            cycle_id: 1,
            cycle_period: Duration::from_millis(50),
            drift_correction_us: 0,
            generated_at: std::time::Instant::now(),
        }
        .to_bytes();

        assert!(coord.verify_beacon(&raw).is_err());
    }

    // ---- Error display tests ----

    #[test]
    fn test_secure_tdm_error_display() {
        let err = SecureTdmError::BeaconAuthFailed;
        assert!(format!("{}", err).contains("HMAC"));

        let err = SecureTdmError::BeaconReplay {
            nonce: 5,
            last_accepted: 10,
        };
        assert!(format!("{}", err).contains("replay"));

        let err = SecureTdmError::NoMeshKey;
        assert!(format!("{}", err).contains("Mesh key"));
    }
}
