use std::collections::VecDeque;
use tokio::time::Instant;

pub(super) const TIMING_WINDOW_CAPACITY: usize = 240;

#[derive(Clone, Copy, Debug)]
pub(super) struct UpstreamTimingSample {
    pub capture_pts_us: u64,
    pub send_pts_us: u64,
    pub queue_age_ms: u32,
    pub received_at: Instant,
}

#[derive(Clone, Copy, Debug)]
pub(super) struct UpstreamPresentationSample {
    pub due_at: Instant,
    pub handed_at: Instant,
}

#[derive(Debug, Default)]
pub(super) struct UpstreamScalarWindow {
    values: VecDeque<f64>,
}

impl UpstreamScalarWindow {
    pub fn push(&mut self, value: f64) {
        if self.values.len() >= TIMING_WINDOW_CAPACITY {
            self.values.pop_front();
        }
        self.values.push_back(value);
    }

    pub fn p95_abs(&self) -> Option<f64> {
        percentile(self.values.iter().map(|value| value.abs()), 0.95)
    }

    pub fn p95(&self) -> Option<f64> {
        percentile(self.values.iter().copied(), 0.95)
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }
}

fn percentile(values: impl Iterator<Item = f64>, quantile: f64) -> Option<f64> {
    let mut sorted = values.filter(|value| value.is_finite()).collect::<Vec<_>>();
    if sorted.is_empty() {
        return None;
    }
    sorted.sort_by(|left, right| left.total_cmp(right));
    let index = ((sorted.len() - 1) as f64 * quantile.clamp(0.0, 1.0)).ceil() as usize;
    sorted.get(index).copied()
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UpstreamSyncPhase {
    Acquiring,
    Syncing,
    Live,
    Healing,
    Failed,
}

impl UpstreamSyncPhase {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Acquiring => "acquiring",
            Self::Syncing => "syncing",
            Self::Live => "live",
            Self::Healing => "healing",
            Self::Failed => "failed",
        }
    }
}

#[derive(Debug, Default)]
pub(super) struct UpstreamClockState {
    pub session_id: u64,
    pub active_camera_generation: Option<u64>,
    pub active_microphone_generation: Option<u64>,
    pub session_started_at: Option<Instant>,
    pub phase: UpstreamSyncPhase,
    pub first_camera_remote_pts_us: Option<u64>,
    pub first_microphone_remote_pts_us: Option<u64>,
    pub latest_camera_remote_pts_us: Option<u64>,
    pub latest_microphone_remote_pts_us: Option<u64>,
    pub camera_startup_ready: bool,
    pub session_base_remote_pts_us: Option<u64>,
    pub last_video_local_pts_us: Option<u64>,
    pub last_audio_local_pts_us: Option<u64>,
    pub last_video_presented_pts_us: Option<u64>,
    pub last_audio_presented_pts_us: Option<u64>,
    pub camera_packet_count: u64,
    pub microphone_packet_count: u64,
    pub startup_anchor_logged: bool,
    pub playout_epoch: Option<Instant>,
    pub pairing_anchor_deadline: Option<Instant>,
    pub freshness_reanchors: u64,
    pub stale_audio_drops: u64,
    pub stale_video_drops: u64,
    pub skew_video_drops: u64,
    pub startup_timeouts: u64,
    pub video_freezes: u64,
    pub last_reason: String,
    pub latest_camera_timing: Option<UpstreamTimingSample>,
    pub latest_microphone_timing: Option<UpstreamTimingSample>,
    pub recent_camera_timing: VecDeque<UpstreamTimingSample>,
    pub recent_microphone_timing: VecDeque<UpstreamTimingSample>,
    pub latest_paired_client_capture_skew_ms: Option<f64>,
    pub latest_paired_client_send_skew_ms: Option<f64>,
    pub latest_paired_server_receive_skew_ms: Option<f64>,
    pub latest_camera_presentation: Option<UpstreamPresentationSample>,
    pub latest_microphone_presentation: Option<UpstreamPresentationSample>,
    pub client_capture_skew_window_ms: UpstreamScalarWindow,
    pub client_send_skew_window_ms: UpstreamScalarWindow,
    pub server_receive_skew_window_ms: UpstreamScalarWindow,
    pub camera_client_queue_age_window_ms: UpstreamScalarWindow,
    pub microphone_client_queue_age_window_ms: UpstreamScalarWindow,
    pub sink_handoff_skew_window_ms: UpstreamScalarWindow,
    pub camera_sink_late_window_ms: UpstreamScalarWindow,
    pub microphone_sink_late_window_ms: UpstreamScalarWindow,
}

impl Default for UpstreamSyncPhase {
    fn default() -> Self {
        Self::Acquiring
    }
}
