// server/src/video.rs

use anyhow::Context;
use futures_util::Stream;
use gst::MessageView::*;
use gst::prelude::*;
use gstreamer as gst;
use gstreamer_app as gst_app;
use lesavka_common::eye_source::{default_eye_source_mode, eye_source_mode_for_request};
use lesavka_common::lesavka::VideoPacket;
use lesavka_common::process_metrics::ProcessCpuSampler;
use std::os::unix::fs::FileTypeExt;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::{Duration, Instant, sleep};
use tokio_stream::wrappers::ReceiverStream;
use tonic::Status;
use tracing::{Level, debug, enabled, error, info, trace, warn};

pub use crate::video_sinks::{CameraRelay, HdmiSink, WebcamSink};
use crate::video_support::{
    adjust_effective_fps, contains_idr, default_eye_fps, env_u32, env_usize, should_send_frame,
};

const EYE_ID: [&str; 2] = ["l", "r"];
static START: OnceLock<gst::ClockTime> = OnceLock::new();
static SERVER_PROCESS_CPU_TENTHS: OnceLock<Arc<AtomicU32>> = OnceLock::new();

fn server_process_cpu_metric() -> Arc<AtomicU32> {
    Arc::clone(SERVER_PROCESS_CPU_TENTHS.get_or_init(|| {
        let metric = Arc::new(AtomicU32::new(0));
        let metric_for_thread = Arc::clone(&metric);
        std::thread::spawn(move || {
            let mut sampler = ProcessCpuSampler::new();
            loop {
                if let Some(value) = sampler.sample_tenths_percent() {
                    metric_for_thread.store(value, Ordering::Relaxed);
                }
                std::thread::sleep(std::time::Duration::from_secs(1));
            }
        });
        metric
    }))
}

pub struct VideoStream {
    _pipeline: gst::Pipeline,
    #[cfg(not(coverage))]
    _bus_watch: Option<BusWatchHandle>,
    #[cfg(not(coverage))]
    _stall_watchdog_alive: Option<Arc<AtomicBool>>,
    inner: ReceiverStream<Result<VideoPacket, Status>>,
}

impl Stream for VideoStream {
    type Item = Result<VideoPacket, Status>;

    fn poll_next(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Option<Self::Item>> {
        Stream::poll_next(std::pin::Pin::new(&mut self.inner), cx)
    }
}

impl Drop for VideoStream {
    fn drop(&mut self) {
        let _ = self._pipeline.set_state(gst::State::Null);
        #[cfg(not(coverage))]
        {
            if let Some(alive) = self._stall_watchdog_alive.take() {
                alive.store(false, Ordering::Relaxed);
            }
            let _ = self._bus_watch.take();
        }
    }
}

#[cfg(not(coverage))]
struct BusWatchHandle {
    alive: Arc<AtomicBool>,
    join: Option<std::thread::JoinHandle<()>>,
}

#[cfg(not(coverage))]
impl BusWatchHandle {
    fn spawn(
        bus: gst::Bus,
        eye: String,
        stream_errors: tokio::sync::mpsc::Sender<Result<VideoPacket, Status>>,
    ) -> Self {
        let alive = Arc::new(AtomicBool::new(true));
        let alive_flag = Arc::clone(&alive);
        let join = std::thread::spawn(move || {
            while alive_flag.load(Ordering::Relaxed) {
                let Some(msg) = bus.timed_pop(gst::ClockTime::from_mseconds(250)) else {
                    continue;
                };
                match msg.view() {
                    Error(err) => {
                        error!(
                            target:"lesavka_server::video",
                            eye = %eye,
                            "💥 pipeline error: {} ({})",
                            err.error(),
                            err.debug().unwrap_or_default()
                        );
                        let detail = format!(
                            "eye-{eye} capture pipeline error: {} ({})",
                            err.error(),
                            err.debug().unwrap_or_default()
                        );
                        let _ = stream_errors.blocking_send(Err(Status::internal(detail)));
                        break;
                    }
                    Warning(warning) => {
                        warn!(
                            target:"lesavka_server::video",
                            eye = %eye,
                            "⚠️  pipeline warning: {} ({})",
                            warning.error(),
                            warning.debug().unwrap_or_default()
                        );
                    }
                    Info(info_msg) => {
                        info!(
                            target:"lesavka_server::video",
                            eye = %eye,
                            "📌  pipeline info: {} ({})",
                            info_msg.error(),
                            info_msg.debug().unwrap_or_default()
                        );
                    }
                    StateChanged(state) if state.current() == gst::State::Playing => {
                        debug!(target:"lesavka_server::video", eye = %eye, "🎬 pipeline PLAYING");
                    }
                    StateChanged(state) if state.current() == gst::State::Null => {
                        debug!(target:"lesavka_server::video", eye = %eye, "🛑 pipeline stopped");
                        break;
                    }
                    Eos(..) => {
                        debug!(target:"lesavka_server::video", eye = %eye, "🏁 pipeline EOS");
                        break;
                    }
                    _ => {}
                }
            }
        });
        Self {
            alive,
            join: Some(join),
        }
    }
}

#[cfg(not(coverage))]
impl Drop for BusWatchHandle {
    /// Stops the background bus watcher before joining it so stream teardown does not leak helper threads.
    fn drop(&mut self) {
        self.alive.store(false, Ordering::Relaxed);
        if let Some(join) = self.join.take() {
            let _ = join.join();
        }
    }
}

#[cfg(not(coverage))]
fn start_eye_pipeline(pipeline: &gst::Pipeline, bus: &gst::Bus, eye: &str) -> anyhow::Result<()> {
    pipeline
        .set_state(gst::State::Playing)
        .context(format!("🎥 starting video pipeline eye-{eye}"))?;
    for _ in 0..20 {
        match bus.timed_pop(gst::ClockTime::from_mseconds(200)) {
            Some(msg) => match msg.view() {
                Error(err) => {
                    let _ = pipeline.set_state(gst::State::Null);
                    return Err(anyhow::anyhow!(
                        "🎥 eye-{eye} pipeline error: {} ({})",
                        err.error(),
                        err.debug().unwrap_or_default()
                    ));
                }
                StateChanged(state) if state.current() == gst::State::Playing => return Ok(()),
                _ => continue,
            },
            None => continue,
        }
    }
    Ok(())
}

fn eye_device_wait_timeout() -> Duration {
    Duration::from_millis(
        std::env::var("LESAVKA_EYE_DEVICE_WAIT_MS")
            .ok()
            .and_then(|value| value.parse::<u64>().ok())
            .unwrap_or(5_000),
    )
}

fn eye_device_wait_poll() -> Duration {
    Duration::from_millis(
        std::env::var("LESAVKA_EYE_DEVICE_POLL_MS")
            .ok()
            .and_then(|value| value.parse::<u64>().ok())
            .map(|value| value.max(25))
            .unwrap_or(100),
    )
}

/// Returns the warning threshold for midstream eye-capture stalls.
///
/// The input is `LESAVKA_EYE_STALL_WARN_MS`: `0` disables the watchdog, invalid
/// values fall back to five seconds, and very small positive values are clamped
/// to 500ms so the diagnostic cannot accidentally flood logs. The output is
/// `None` when disabled or a bounded duration used by the downstream watchdog.
fn eye_stall_warn_timeout() -> Option<Duration> {
    let millis = std::env::var("LESAVKA_EYE_STALL_WARN_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(5_000);
    (millis > 0).then_some(Duration::from_millis(millis.max(500)))
}

fn wall_clock_ms() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_millis().min(u64::MAX as u128) as u64)
        .unwrap_or_default()
}

pub fn eye_source_profile() -> (u32, u32, u32) {
    let mode = default_eye_source_mode();
    (mode.width, mode.height, mode.fps)
}

fn round_down_even_u32(value: u32) -> u32 {
    let rounded = value.max(2);
    rounded - (rounded % 2)
}

fn reset_stream_telemetry_window(
    last_window_sec: &AtomicU64,
    current_sec: u64,
    source_gap_peak_ms: &AtomicU32,
    send_gap_peak_ms: &AtomicU32,
    queue_peak_depth: &AtomicU32,
) {
    let prev = last_window_sec.load(Ordering::Relaxed);
    if current_sec <= prev {
        return;
    }
    if last_window_sec
        .compare_exchange(prev, current_sec, Ordering::SeqCst, Ordering::SeqCst)
        .is_ok()
    {
        source_gap_peak_ms.store(0, Ordering::Relaxed);
        send_gap_peak_ms.store(0, Ordering::Relaxed);
        queue_peak_depth.store(0, Ordering::Relaxed);
    }
}

#[derive(Clone, Copy, Debug)]
struct EyeCaptureRequest {
    width: u32,
    height: u32,
    fps: u32,
    max_bitrate_kbit: u32,
}

fn normalize_eye_capture_request(
    requested_width: u32,
    requested_height: u32,
    _requested_fps: u32,
    max_bitrate_kbit: u32,
) -> EyeCaptureRequest {
    let source_mode = eye_source_mode_for_request(requested_width, requested_height);
    EyeCaptureRequest {
        width: round_down_even_u32(source_mode.width.max(320)),
        height: round_down_even_u32(source_mode.height.max(180)),
        fps: source_mode.fps.max(1),
        max_bitrate_kbit,
    }
}
