//! Broadcast channel avec TTL et propagation de TopZero.
//! Inspiré de `tokio::sync::broadcast` mais ajoute :
//! - Capacité bornée avec blocage des producteurs quand aucun slot n’est libre.
//! - Expiration automatique des messages (TTL) pour libérer les slots.
//! - Propagation d’un compteur `epoch` incrémenté sur chaque TopZeroSync.

use std::{
    collections::VecDeque,
    fmt,
    sync::{
        atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
        Arc, Mutex, Weak,
    },
    time::{Duration, Instant},
};

use tokio::sync::Notify;
use tracing::{info, trace, warn};

/// Tolérance pour détecter un timestamp à zéro (TopZero).
const TOP_ZERO_EPSILON: f64 = 1e-9;

pub const DEFAULT_BROADCAST_MAX_LEAD_TIME: f64 = 0.5;

/// Paquet diffusé contenant la charge utile + méta timing.
#[derive(Clone)]
pub struct TimedPacket<T> {
    /// Charge utile diffusée aux clients.
    pub payload: T,
    /// Timestamp audio relatif (en secondes) pour pacing côté client.
    pub audio_timestamp: f64,
    /// Compteur incrémenté lorsqu'un TopZeroSync est reçu.
    pub epoch: u64,
}

impl<T> fmt::Debug for TimedPacket<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("TimedPacket")
            .field("audio_timestamp", &self.audio_timestamp)
            .field("epoch", &self.epoch)
            .finish_non_exhaustive()
    }
}

/// Erreur remontée par `Receiver::try_recv`.
#[derive(Debug)]
pub enum TryRecvError {
    /// Aucun paquet n'est disponible pour le moment.
    Empty,
    /// Le receiver est en retard : le champ contient combien de paquets ont expiré
    /// ou ont déjà été consommés par les autres abonnés.
    ///
    /// Ce cas survient lorsque `purge_expired()` avance `head_seq` et que ce
    /// `Receiver` réclamait encore l'un des numéros supprimés. Le client doit
    /// donc ignorer les données perdues et se resynchroniser sur les paquets
    /// courants.
    Lagged(u64),
    /// Le channel est fermé et plus aucun paquet n'est disponible.
    Closed,
}

/// Erreur remontée par `Receiver::recv`.
#[derive(Debug)]
pub enum RecvError {
    Lagged(u64),
    Closed,
}

/// Erreur remontée par `Sender::send`.
#[derive(Debug)]
/// Erreur de diffusion détaillant la raison pour laquelle un paquet n'a pas été accepté.
pub enum SendError<T> {
    Closed(T),
    Expired(T),
}

struct Entry<T> {
    seq: u64,
    expires_at: Instant,
    payload: T,
    audio_timestamp: f64,
    epoch: u64,
}

struct State<T> {
    name: String,
    buffer: VecDeque<Entry<T>>,
    head_seq: u64,
    next_seq: u64,
    closed: bool,
    epoch: u64,
    epoch_start: Instant,
    last_segment_end: Option<Instant>,
    cursors: Vec<Weak<ReceiverCursor>>,
    initialized: bool,
    last_purge: Instant,
}

impl<T> State<T> {
    fn new(name: &str, capacity: usize, epoch_start: Instant) -> Self {
        Self {
            name: name.to_string(),
            buffer: VecDeque::with_capacity(capacity),
            head_seq: 0,
            next_seq: 0,
            closed: false,
            epoch: 0,
            epoch_start,
            last_segment_end: None,
            cursors: Vec::new(),
            initialized: false,
            last_purge: epoch_start,
        }
    }

    fn purge_expired(&mut self, now: Instant) -> bool {
        // Throttling : purger au maximum toutes les 20ms
        if now.duration_since(self.last_purge) < Duration::from_millis(20) {
            return false;
        }
        self.last_purge = now;

        let mut purged = 0u64;
        while let Some(entry) = self.buffer.front() {
            if entry.expires_at <= now {
                let delta = now - entry.expires_at;
                trace!(
                    "TimedBroadcast[{}]: purging expired packet (@{} epoch={},delta={})",
                    self.name,
                    entry.seq,
                    entry.epoch,
                    delta.as_millis()
                );
                self.buffer.pop_front();
                self.head_seq += 1;
                purged += 1;
            } else {
                break;
            }
        }
        if purged > 0 {
            trace!(
                "TimedBroadcast[{}]: purged {} expired packet(s) (head_seq={})",
                self.name,
                purged,
                self.head_seq
            );
            return true;
        }
        false
    }

    fn prune_consumed(&mut self) -> bool {
        let mut min_next = self.next_seq;
        let mut has_cursor = false;
        self.cursors.retain(|weak| {
            if let Some(cursor) = weak.upgrade() {
                let pos = cursor.next_seq.load(Ordering::SeqCst);
                if pos < min_next {
                    min_next = pos;
                }
                has_cursor = true;
                true
            } else {
                false
            }
        });

        if !has_cursor {
            return false;
        }

        let removable = min_next.saturating_sub(self.head_seq) as usize;
        if removable == 0 {
            return false;
        }

        for _ in 0..removable {
            let oentry = self.buffer.pop_front();
            if oentry.is_some() {
                let entry = oentry.unwrap();
                trace!(
                    "TimedBroadcast[{}]: pruning played packet (@{} epoch={})",
                    self.name,
                    entry.seq,
                    entry.epoch
                );

                self.head_seq += 1;
            }
        }
        true
    }
}

struct Inner<T> {
    state: Mutex<State<T>>,
    data_notify: Notify,
    space_notify: Notify,
    capacity: usize,
    sender_count: AtomicUsize,
    receiver_count: AtomicUsize,
    is_closed: AtomicBool,
}

impl<T> Inner<T> {
    fn new(name: &str, capacity: usize) -> Self {
        Self {
            state: Mutex::new(State::new(name, capacity, Instant::now())),
            data_notify: Notify::new(),
            space_notify: Notify::new(),
            capacity,
            sender_count: AtomicUsize::new(1),
            receiver_count: AtomicUsize::new(0),
            is_closed: AtomicBool::new(false),
        }
    }

    fn close(&self) {
        if !self.is_closed.swap(true, Ordering::SeqCst) {
            if let Ok(mut state) = self.state.lock() {
                state.closed = true;
            }
            self.data_notify.notify_waiters();
            self.space_notify.notify_waiters();
        }
    }
}

/// Créé un channel broadcast temporisé.
pub fn channel<T>(name: &str, capacity: usize) -> (Sender<T>, Receiver<T>) {
    assert!(capacity > 0, "capacity must be > 0");
    let inner = Arc::new(Inner::new(name, capacity));
    let next_seq = {
        let state = inner.state.lock().expect("timed broadcast mutex poisoned");
        state.next_seq
    };
    let sender = Sender {
        inner: inner.clone(),
    };
    let cursor = Arc::new(ReceiverCursor {
        next_seq: AtomicU64::new(next_seq),
    });
    {
        let mut state = inner.state.lock().expect("timed broadcast mutex poisoned");
        state.cursors.push(Arc::downgrade(&cursor));
    }
    inner.receiver_count.store(1, Ordering::SeqCst);
    let receiver = Receiver {
        inner,
        next_seq,
        cursor,
    };
    (sender, receiver)
}

/// Sender côté producteur.
pub struct Sender<T> {
    inner: Arc<Inner<T>>,
}

impl<T> Clone for Sender<T> {
    fn clone(&self) -> Self {
        self.inner.sender_count.fetch_add(1, Ordering::SeqCst);
        Self {
            inner: self.inner.clone(),
        }
    }
}

impl<T> Sender<T> {
    /// Diffuse un paquet. Bloque si la capacité est atteinte avec des paquets non périmés.
    ///
    /// Le TTL de chaque paquet est calculé à partir du `epoch_start` courant et du
    /// `audio_timestamp` fournis, ce qui signifie qu’un receiver en retard finira
    /// par recevoir un [`TryRecvError::Lagged`] lorsque `expires_at` est dépassé.
    pub async fn send(
        &self,
        payload: T,
        audio_timestamp: f64,
        segment_duration: f64,
    ) -> Result<usize, SendError<T>>
    where
        T: Clone,
    {
        let mut payload = Some(payload);
        loop {
            let mut wait_deadline = None;
            {
                let mut state = self
                    .inner
                    .state
                    .lock()
                    .expect("timed broadcast mutex poisoned");

                if state.closed {
                    return Err(SendError::Closed(
                        payload.expect("payload already consumed"),
                    ));
                }

                // Capturer le temps UNE SEULE FOIS pour cohérence temporelle
                let now = Instant::now();

                // 1. Purger d'abord les paquets expirés et consommés pour libérer l'espace
                // (skip pour le tout premier paquet)
                if state.buffer.len() > 0 {
                    let consumed = state.prune_consumed();
                    let expired = state.purge_expired(now);
                    if consumed || expired {
                        self.inner.space_notify.notify_waiters();
                    }
                }

                // 2. Vérifier si un slot est disponible et insérer
                let is_top_zero = audio_timestamp.abs() < TOP_ZERO_EPSILON
                    && segment_duration >= TOP_ZERO_EPSILON;
                let is_zero_header =
                    audio_timestamp.abs() < TOP_ZERO_EPSILON && segment_duration < TOP_ZERO_EPSILON;
                if state.buffer.len() < self.inner.capacity {
                    if !state.initialized {
                        if !is_top_zero && segment_duration >= TOP_ZERO_EPSILON {
                            warn!(
                                "TimedBroadcast[{}]: First packet has non-zero timestamp {:.1}ms - Duration={:.1}ms, treating as epoch start anyway",
                                state.name,
                                audio_timestamp*1000.0,
                            segment_duration*1000.0
                            );
                        }
                        state.epoch_start = now;
                        state.epoch = 0;
                        state.initialized = true;
                        info!(
                            "TimedBroadcast[{}]: initialized (epoch=0, ts={:.1}ms - Duration={:.1}ms)",
                            state.name, 
                            audio_timestamp*1000.0,
                            segment_duration*1000.0
                        );
                    } else if is_top_zero || is_zero_header {
                        // Restart epoch on TopZero relative to current wall-clock time to avoid
                        // expired packets when there's a long gap between tracks. Also trigger
                        // on zero-duration headers (OGG BOS/comment) so the epoch is reset
                        // before testing expiration.
                        state.epoch_start = state
                            .last_segment_end
                            .map(|end| end.max(now))
                            .unwrap_or(now);
                        // state.epoch_start = now;
                        state.epoch = state.epoch.wrapping_add(1);
                        info!(
                            "TimedBroadcast[{}]: new epoch={} (continuous={} - Duration={}ms)",
                            state.name,
                            state.epoch,
                            state.last_segment_end.is_some(),
                            segment_duration * 1000.0
                        );
                    }

                    let expires_at = state.epoch_start
                        + Duration::from_secs_f64(audio_timestamp + segment_duration);

                    let is_first_packet = state.next_seq == 0;
                    if !is_first_packet && !is_top_zero && !is_zero_header && expires_at <= now {
                        let grace_period = Duration::from_millis(50);
                        if now > expires_at + grace_period {
                            warn!(
                                "TimedBroadcast[{}]: rejecting already expired packet (ts={:.3}s, epoch={}, delta={}ms)",
                                state.name,
                                audio_timestamp,
                                state.epoch,
                                now.duration_since(expires_at).as_millis()
                            );
                            return Err(SendError::Expired(
                                payload.expect("payload already consumed"),
                            ));
                        }
                    }

                    let entry = Entry {
                        seq: state.next_seq,
                        expires_at,
                        payload: payload.take().expect("payload already consumed"),
                        audio_timestamp,
                        epoch: state.epoch,
                    };
                    state.next_seq += 1;
                    state.buffer.push_back(entry);

                    // 5. Only advance segment end for real audio (skip 0-duration metadata)
                    if segment_duration >= TOP_ZERO_EPSILON {
                        let new_end = expires_at;
                        state.last_segment_end = Some(match state.last_segment_end.take() {
                            Some(prev) => prev.max(new_end),
                            None => new_end,
                        });
                    }

                    let receivers = self.inner.receiver_count.load(Ordering::SeqCst);
                    drop(state);
                    self.inner.data_notify.notify_waiters();
                    return Ok(receivers);
                }

                wait_deadline = state.buffer.front().map(|entry| entry.expires_at);
            }

            if let Some(deadline) = wait_deadline {
                let deadline = tokio::time::Instant::from_std(deadline);
                tokio::select! {
                    _ = self.inner.space_notify.notified() => {},
                    _ = tokio::time::sleep_until(deadline) => {},
                }
            } else {
                self.inner.space_notify.notified().await;
            }
        }
    }

    /// Crée un nouveau receiver abonné au flux.
    pub fn subscribe(&self) -> Receiver<T> {
        let mut state = self
            .inner
            .state
            .lock()
            .expect("timed broadcast mutex poisoned");
        let next_seq = state.next_seq;
        let cursor = Arc::new(ReceiverCursor {
            next_seq: AtomicU64::new(next_seq),
        });
        state.cursors.push(Arc::downgrade(&cursor));
        state.prune_consumed();
        drop(state);

        self.inner.receiver_count.fetch_add(1, Ordering::SeqCst);

        Receiver {
            inner: self.inner.clone(),
            next_seq,
            cursor,
        }
    }

    /// Nombre actuel de receivers abonnés.
    pub fn receiver_count(&self) -> usize {
        self.inner.receiver_count.load(Ordering::SeqCst)
    }

    /// Ferme explicitement le channel.
    pub fn close(&self) {
        self.inner.close();
    }
}

impl<T> Drop for Sender<T> {
    fn drop(&mut self) {
        if self.inner.sender_count.fetch_sub(1, Ordering::SeqCst) == 1 {
            self.inner.close();
        }
    }
}

/// Receiver côté consommateur.
///
/// Chaque receiver garde son propre curseur `next_seq`. Si le producteur
/// recycle un paquet via `purge_expired()` avant que ce curseur ne l’ait lu,
/// la prochaine tentative de lecture retournera [`TryRecvError::Lagged`].
pub struct Receiver<T> {
    inner: Arc<Inner<T>>,
    next_seq: u64,
    cursor: Arc<ReceiverCursor>,
}

struct ReceiverCursor {
    next_seq: AtomicU64,
}

impl<T> Receiver<T>
where
    T: Clone,
{
    fn poll_entry(&mut self) -> Result<TimedPacket<T>, TryRecvError> {
        let mut state = self
            .inner
            .state
            .lock()
            .expect("timed broadcast mutex poisoned");

        if state.closed && state.buffer.is_empty() {
            return Err(TryRecvError::Closed);
        }

        let now = Instant::now();
        if state.purge_expired(now) {
            self.inner.space_notify.notify_waiters();
        }

        if self.next_seq < state.head_seq {
            let skipped = state.head_seq - self.next_seq;
            self.next_seq = state.head_seq;
            return Err(TryRecvError::Lagged(skipped));
        }

        let offset = (self.next_seq - state.head_seq) as usize;
        if offset < state.buffer.len() {
            let entry = state.buffer.get(offset).expect("invalid buffer offset");
            let packet = TimedPacket {
                payload: entry.payload.clone(),
                audio_timestamp: entry.audio_timestamp,
                epoch: entry.epoch,
            };
            self.next_seq += 1;
            self.cursor.next_seq.store(self.next_seq, Ordering::SeqCst);
            if state.prune_consumed() {
                self.inner.space_notify.notify_waiters();
            }
            return Ok(packet);
        }

        if state.closed {
            Err(TryRecvError::Closed)
        } else {
            Err(TryRecvError::Empty)
        }
    }

    /// Version synchrone utilisée dans `poll_read`.
    ///
    /// # Erreurs
    ///
    /// * [`TryRecvError::Lagged`] — des paquets ont expiré avant d'être consommés.
    /// * [`TryRecvError::Empty`] — la file est vide pour l'instant.
    /// * [`TryRecvError::Closed`] — plus aucun paquet n'arrivera.
    pub fn try_recv(&mut self) -> Result<TimedPacket<T>, TryRecvError> {
        self.poll_entry()
    }

    /// Attends qu'un paquet soit disponible.
    pub async fn recv(&mut self) -> Result<TimedPacket<T>, RecvError> {
        loop {
            match self.try_recv() {
                Ok(packet) => return Ok(packet),
                Err(TryRecvError::Empty) => {
                    self.inner.data_notify.notified().await;
                }
                Err(TryRecvError::Lagged(skipped)) => return Err(RecvError::Lagged(skipped)),
                Err(TryRecvError::Closed) => return Err(RecvError::Closed),
            }
        }
    }
}

impl<T> Clone for Receiver<T> {
    fn clone(&self) -> Self {
        self.inner.receiver_count.fetch_add(1, Ordering::SeqCst);
        let cursor = Arc::new(ReceiverCursor {
            next_seq: AtomicU64::new(self.next_seq),
        });
        {
            let mut state = self
                .inner
                .state
                .lock()
                .expect("timed broadcast mutex poisoned");
            state.cursors.push(Arc::downgrade(&cursor));
        }
        Self {
            inner: self.inner.clone(),
            next_seq: self.next_seq,
            cursor,
        }
    }
}

impl<T> Drop for Receiver<T> {
    fn drop(&mut self) {
        self.cursor.next_seq.store(self.next_seq, Ordering::SeqCst);
        if let Ok(mut state) = self.inner.state.lock() {
            if state.prune_consumed() {
                self.inner.space_notify.notify_waiters();
            }
        }
        if self.inner.receiver_count.fetch_sub(1, Ordering::SeqCst) == 1 {
            self.inner.space_notify.notify_waiters();
        }
    }
}

/// Calculate broadcast channel capacity based on max_lead_time.
///
/// Estimates the number of items needed to buffer max_lead_time seconds of audio.
/// Assumes ~20 items per second (50ms per chunk).
///
/// # Arguments
///
/// * `max_lead_time` - Maximum lead time in seconds
///
/// # Returns
///
/// Broadcast channel capacity (minimum 100 items)
pub(crate) fn calculate_broadcast_capacity(max_lead_time: f64) -> usize {
    // Estimation: ~20 items/second (chunks de 50ms en moyenne)
    // Pour 10s: 200 items
    let estimated_items_per_second = 20.0;
    let capacity = (max_lead_time * estimated_items_per_second) as usize;
    capacity.max(100) // Minimum 100 items
}
