//! Sender-side chat send status and pacing (chat-hardening Phase 5).
//!
//! Every RECEIVER admits our chat through a per-author token bucket
//! ([`CHAT_AUTHOR_BURST`] then 1/s) and silently drops what exceeds it, with no
//! acknowledgement wire. The only way the sender can be honest about fast
//! bursts is to never exceed that budget in the first place: sends past the
//! burst are queued locally (shown as "queued…") and trickled out at the
//! receivers' sustained rate. The pacer deliberately reuses the receiver
//! gate's own [`TokenBucket`] and constants so the two sides of the policy
//! cannot drift apart.
//!
//! Everything here is pure — `now_ms` is passed in, never read from a clock —
//! so every boundary is unit-testable.

use std::collections::VecDeque;

use crate::network::gossip::{CHAT_AUTHOR_BURST, CHAT_AUTHOR_REFILL_PER_MS, TokenBucket};

/// Send lifecycle of one locally authored chat message. Success is
/// [`SendStatus::Broadcast`] — "our signed frame was handed to the gossip
/// swarm" — deliberately NOT "delivered": PeerSpeak has no peer
/// acknowledgements, so the honest success presentation is no label at all.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendStatus {
    /// Waiting in the local outbound queue for a pacer token.
    Queued,
    /// Handed to the core; the broadcast result has not come back yet.
    Pending,
    /// The signed broadcast reached the gossip swarm.
    Broadcast,
    /// The send failed; carries a short reason. The entry offers a Retry.
    Failed(String),
}

/// Local-only send bookkeeping attached to our own chat entries. The id never
/// goes on the wire; it ties a `ChatSendResult` back to the matching echo.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalSend {
    pub id: u64,
    pub status: SendStatus,
}

/// Sender-side pacer mirroring the receiver's per-author admission budget.
#[derive(Debug, Clone, Copy)]
pub struct SendPacer {
    bucket: TokenBucket,
}

impl SendPacer {
    pub fn new(now_ms: u64) -> Self {
        Self {
            bucket: TokenBucket::full(CHAT_AUTHOR_BURST, now_ms),
        }
    }

    /// Take one send token if the mirrored per-author budget allows it now.
    pub fn try_send(&mut self, now_ms: u64) -> bool {
        self.bucket
            .try_take(CHAT_AUTHOR_BURST, CHAT_AUTHOR_REFILL_PER_MS, now_ms)
    }
}

/// Pop the queued ids that may be dispatched now: strict front-of-queue order,
/// one pacer token each, stopping at the first refusal so a message can never
/// overtake an earlier one.
pub fn release_ready(queue: &mut VecDeque<u64>, pacer: &mut SendPacer, now_ms: u64) -> Vec<u64> {
    let mut ready = Vec::new();
    while !queue.is_empty() && pacer.try_send(now_ms) {
        // The unwrap is safe: the loop condition just checked non-empty.
        ready.push(queue.pop_front().unwrap());
    }
    ready
}

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

    const T0: u64 = 1_000_000;

    #[test]
    fn pacer_allows_the_full_burst_then_refuses() {
        let mut pacer = SendPacer::new(T0);
        for _ in 0..CHAT_AUTHOR_BURST as usize {
            assert!(pacer.try_send(T0));
        }
        assert!(!pacer.try_send(T0));
    }

    #[test]
    fn pacer_refills_at_one_per_second() {
        let mut pacer = SendPacer::new(T0);
        for _ in 0..CHAT_AUTHOR_BURST as usize {
            assert!(pacer.try_send(T0));
        }
        // 999ms is just under one token; 1000ms grants exactly one.
        assert!(!pacer.try_send(T0 + 999));
        assert!(pacer.try_send(T0 + 1000));
        assert!(!pacer.try_send(T0 + 1000));
    }

    #[test]
    fn release_ready_preserves_order_and_stops_at_refusal() {
        let mut pacer = SendPacer::new(T0);
        // Drain the burst so only refill tokens remain.
        for _ in 0..CHAT_AUTHOR_BURST as usize {
            assert!(pacer.try_send(T0));
        }
        let mut queue: VecDeque<u64> = [10, 11, 12].into_iter().collect();
        // 2 seconds of refill = 2 tokens: exactly the first two, in order.
        let ready = release_ready(&mut queue, &mut pacer, T0 + 2000);
        assert_eq!(ready, vec![10, 11]);
        assert_eq!(queue, VecDeque::from([12]));
        // No tokens left at the same instant.
        assert!(release_ready(&mut queue, &mut pacer, T0 + 2000).is_empty());
        assert_eq!(queue, VecDeque::from([12]));
    }

    #[test]
    fn release_ready_empty_queue_consumes_no_tokens() {
        let mut pacer = SendPacer::new(T0);
        let mut queue = VecDeque::new();
        assert!(release_ready(&mut queue, &mut pacer, T0).is_empty());
        // The full burst must still be available.
        for _ in 0..CHAT_AUTHOR_BURST as usize {
            assert!(pacer.try_send(T0));
        }
    }
}
