#[cfg(not(coverage))]
use crate::video_support::pick_h264_decoder;
#[cfg(not(coverage))]
use anyhow::{Context, Result};
#[cfg(not(coverage))]
use gstreamer as gst;
#[cfg(not(coverage))]
use gstreamer::prelude::{Cast, ElementExt, GstBinExt, GstObjectExt, PadExt};
#[cfg(not(coverage))]
use gstreamer_app as gst_app;
#[cfg(not(coverage))]
use gtk::prelude::WidgetExt;
#[cfg(not(coverage))]
use gtk::{gdk, glib};
use lesavka_common::{
    eye_source::eye_source_mode_for_request,
    lesavka::{MonitorRequest, VideoPacket, relay_client::RelayClient},
};
#[cfg(not(coverage))]
use std::collections::VecDeque;
#[cfg(not(coverage))]
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(not(coverage))]
use std::sync::{Arc, Mutex};
#[cfg(not(coverage))]
use std::time::{Duration, Instant};
#[cfg(not(coverage))]
use tonic::{Request, transport::Channel};
#[cfg(not(coverage))]
use tracing::{debug, warn};

#[cfg(not(coverage))]
const PREVIEW_WIDTH: i32 = 960;
#[cfg(not(coverage))]
const PREVIEW_HEIGHT: i32 = 540;
#[cfg(not(coverage))]
const INLINE_PREVIEW_REQUEST_WIDTH: i32 = DEFAULT_EYE_SOURCE_WIDTH;
#[cfg(not(coverage))]
const INLINE_PREVIEW_REQUEST_HEIGHT: i32 = DEFAULT_EYE_SOURCE_HEIGHT;
#[cfg(not(coverage))]
const INLINE_PREVIEW_REQUEST_FPS: u32 = DEFAULT_EYE_SOURCE_FPS;
#[cfg(not(coverage))]
const INLINE_PREVIEW_MAX_KBIT: u32 = DEFAULT_EYE_SOURCE_MAX_KBIT;
#[cfg(not(coverage))]
const DEFAULT_EYE_SOURCE_WIDTH: i32 = 1920;
#[cfg(not(coverage))]
const DEFAULT_EYE_SOURCE_HEIGHT: i32 = 1080;
#[cfg(not(coverage))]
const DEFAULT_EYE_SOURCE_FPS: u32 = 60;
#[cfg(not(coverage))]
const DEFAULT_EYE_SOURCE_MAX_KBIT: u32 = 18_000;
#[cfg(not(coverage))]
const PREVIEW_IDLE_STATUS: &str = "Connect relay to preview.";
#[cfg(not(coverage))]
const TELEMETRY_WINDOW: Duration = Duration::from_secs(5);

#[cfg(not(coverage))]
pub struct LauncherPreview {
    server_addr: Arc<Mutex<String>>,
    log_sink: Arc<Mutex<Option<std::sync::mpsc::Sender<String>>>>,
    inline_feeds: Arc<Mutex<[PreviewFeed; 2]>>,
    window_feeds: Arc<Mutex<[PreviewFeed; 2]>>,
}

#[cfg(not(coverage))]
#[derive(Clone)]
pub struct PreviewBinding {
    enabled: Arc<AtomicBool>,
    alive: Arc<AtomicBool>,
    active_bindings: Arc<AtomicUsize>,
}

#[cfg(not(coverage))]
#[derive(Clone, Copy, Debug)]
pub enum PreviewSurface {
    Inline,
    Window,
}

#[cfg(not(coverage))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PreviewMetricsSnapshot {
    pub receive_fps: f32,
    pub present_fps: f32,
    pub server_fps: f32,
    pub server_process_cpu_pct: f32,
    pub stream_spread_ms: f32,
    pub packet_loss_pct: f32,
    pub dropped_frames: u64,
    pub queue_depth: u32,
    pub queue_depth_peak: u32,
    pub packet_gap_peak_ms: f32,
    pub present_gap_peak_ms: f32,
    pub server_source_gap_peak_ms: f32,
    pub server_send_gap_peak_ms: f32,
    pub server_queue_peak: u32,
    pub server_encoder_label: String,
    pub decoder_label: String,
    pub stream_caps_label: String,
    pub decoded_caps_label: String,
    pub rendered_caps_label: String,
}

#[cfg(not(coverage))]
#[derive(Clone, Copy, Debug)]
struct PreviewProfile {
    source_monitor_id: u32,
    display_width: i32,
    display_height: i32,
    requested_width: i32,
    requested_height: i32,
    requested_fps: u32,
    max_bitrate_kbit: u32,
}

#[cfg(not(coverage))]
impl PreviewSurface {
    fn profile(self) -> PreviewProfile {
        match self {
            Self::Inline => PreviewProfile {
                source_monitor_id: 0,
                display_width: preview_dimension("LESAVKA_PREVIEW_WIDTH", PREVIEW_WIDTH),
                display_height: preview_dimension("LESAVKA_PREVIEW_HEIGHT", PREVIEW_HEIGHT),
                requested_width: preview_dimension(
                    "LESAVKA_PREVIEW_REQUEST_WIDTH",
                    INLINE_PREVIEW_REQUEST_WIDTH,
                ),
                requested_height: preview_dimension(
                    "LESAVKA_PREVIEW_REQUEST_HEIGHT",
                    INLINE_PREVIEW_REQUEST_HEIGHT,
                ),
                requested_fps: preview_bitrate(
                    "LESAVKA_PREVIEW_REQUEST_FPS",
                    INLINE_PREVIEW_REQUEST_FPS,
                ),
                max_bitrate_kbit: preview_bitrate(
                    "LESAVKA_PREVIEW_MAX_KBIT",
                    INLINE_PREVIEW_MAX_KBIT,
                ),
            },
            Self::Window => PreviewProfile {
                source_monitor_id: 0,
                display_width: preview_dimension("LESAVKA_BREAKOUT_PREVIEW_WIDTH", 1280),
                display_height: preview_dimension("LESAVKA_BREAKOUT_PREVIEW_HEIGHT", 720),
                requested_width: preview_dimension(
                    "LESAVKA_BREAKOUT_REQUEST_WIDTH",
                    DEFAULT_EYE_SOURCE_WIDTH,
                ),
                requested_height: preview_dimension(
                    "LESAVKA_BREAKOUT_REQUEST_HEIGHT",
                    DEFAULT_EYE_SOURCE_HEIGHT,
                ),
                requested_fps: preview_bitrate(
                    "LESAVKA_BREAKOUT_REQUEST_FPS",
                    DEFAULT_EYE_SOURCE_FPS,
                ),
                max_bitrate_kbit: preview_bitrate(
                    "LESAVKA_BREAKOUT_PREVIEW_MAX_KBIT",
                    DEFAULT_EYE_SOURCE_MAX_KBIT,
                ),
            },
        }
    }
}

#[cfg(not(coverage))]
impl LauncherPreview {
    pub fn new(server_addr: String) -> Result<Self> {
        gst::init().context("initialising preview gstreamer")?;
        let server_addr = Arc::new(Mutex::new(server_addr));
        let log_sink = Arc::new(Mutex::new(None));
        let inline_feeds = Arc::new(Mutex::new([
            PreviewFeed::spawn(
                Arc::clone(&server_addr),
                0,
                PreviewSurface::Inline.profile(),
                Arc::clone(&log_sink),
            )?,
            PreviewFeed::spawn(
                Arc::clone(&server_addr),
                1,
                PreviewSurface::Inline.profile(),
                Arc::clone(&log_sink),
            )?,
        ]));
        let window_feeds = Arc::new(Mutex::new([
            PreviewFeed::spawn(
                Arc::clone(&server_addr),
                0,
                PreviewSurface::Window.profile(),
                Arc::clone(&log_sink),
            )?,
            PreviewFeed::spawn(
                Arc::clone(&server_addr),
                1,
                PreviewSurface::Window.profile(),
                Arc::clone(&log_sink),
            )?,
        ]));
        Ok(Self {
            server_addr: Arc::clone(&server_addr),
            log_sink: Arc::clone(&log_sink),
            inline_feeds,
            window_feeds,
        })
    }

    pub fn set_log_sink(&self, tx: std::sync::mpsc::Sender<String>) {
        if let Ok(mut slot) = self.log_sink.lock() {
            *slot = Some(tx);
        }
    }

    pub fn set_server_addr(&self, server_addr: String) {
        if let Ok(mut slot) = self.server_addr.lock() {
            *slot = server_addr;
        }
    }

    pub fn set_session_active(&self, active: bool) {
        if let Ok(feeds) = self.inline_feeds.lock() {
            for feed in feeds.iter() {
                feed.set_active(active);
            }
        }
        if let Ok(feeds) = self.window_feeds.lock() {
            for feed in feeds.iter() {
                feed.set_active(active);
            }
        }
    }

    pub fn shutdown_all(&self) {
        if let Ok(feeds) = self.inline_feeds.lock() {
            for feed in feeds.iter() {
                feed.shutdown();
            }
        }
        if let Ok(feeds) = self.window_feeds.lock() {
            for feed in feeds.iter() {
                feed.shutdown();
            }
        }
    }

    pub fn install_on_picture(
        &self,
        monitor_id: usize,
        surface: PreviewSurface,
        picture: &gtk::Picture,
        status_label: &gtk::Label,
    ) -> Option<PreviewBinding> {
        match surface {
            PreviewSurface::Inline => self
                .inline_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned())
                .map(|feed| feed.install_on_picture(picture, status_label)),
            PreviewSurface::Window => self
                .window_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned())
                .map(|feed| feed.install_on_picture(picture, status_label)),
        }
    }

    pub fn snapshot_metrics(
        &self,
        monitor_id: usize,
        surface: PreviewSurface,
    ) -> Option<PreviewMetricsSnapshot> {
        match surface {
            PreviewSurface::Inline => self
                .inline_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned())
                .map(|feed| feed.snapshot_metrics()),
            PreviewSurface::Window => self
                .window_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned())
                .map(|feed| feed.snapshot_metrics()),
        }
    }

    pub fn set_capture_profile(
        &self,
        monitor_id: usize,
        source_monitor_id: usize,
        requested_width: i32,
        requested_height: i32,
        requested_fps: u32,
        max_bitrate_kbit: u32,
    ) {
        let (
            inline_requested_width,
            inline_requested_height,
            inline_requested_fps,
            inline_max_bitrate_kbit,
        ) = sanitize_preview_request(
            requested_width,
            requested_height,
            requested_fps,
            max_bitrate_kbit,
        );
        self.rebuild_feed(
            &self.inline_feeds,
            monitor_id,
            Some((
                source_monitor_id,
                inline_requested_width,
                inline_requested_height,
                inline_requested_fps,
                inline_max_bitrate_kbit,
            )),
            None,
        );
        self.rebuild_feed(
            &self.window_feeds,
            monitor_id,
            Some((
                source_monitor_id,
                requested_width,
                requested_height,
                requested_fps,
                max_bitrate_kbit,
            )),
            None,
        );
    }

    pub fn set_breakout_profile(&self, monitor_id: usize, width: i32, height: i32) {
        self.rebuild_feed(&self.window_feeds, monitor_id, None, Some((width, height)));
    }

    #[cfg(test)]
    pub(crate) fn profile_for_test(
        &self,
        monitor_id: usize,
        surface: PreviewSurface,
    ) -> Option<(u32, i32, i32, i32, i32, u32, u32)> {
        let feed = match surface {
            PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
            PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
        }?;
        let profile = feed.profile();
        Some((
            profile.source_monitor_id,
            profile.display_width,
            profile.display_height,
            profile.requested_width,
            profile.requested_height,
            profile.requested_fps,
            profile.max_bitrate_kbit,
        ))
    }

    #[cfg(test)]
    pub(crate) fn feed_disabled_for_test(
        &self,
        monitor_id: usize,
        surface: PreviewSurface,
    ) -> Option<bool> {
        let feed = match surface {
            PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
            PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
        }?;
        Some(feed.is_disabled())
    }

    #[cfg(test)]
    pub(crate) fn activate_surface_for_test(&self, monitor_id: usize, surface: PreviewSurface) {
        let feed = match surface {
            PreviewSurface::Inline => self
                .inline_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned()),
            PreviewSurface::Window => self
                .window_feeds
                .lock()
                .ok()
                .and_then(|feeds| feeds.get(monitor_id).cloned()),
        };
        if let Some(feed) = feed {
            feed.session_active.store(true, Ordering::Relaxed);
            feed.active_bindings.fetch_add(1, Ordering::AcqRel);
        }
    }

    fn rebuild_feed(
        &self,
        feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
        monitor_id: usize,
        requested: Option<(usize, i32, i32, u32, u32)>,
        display: Option<(i32, i32)>,
    ) {
        let Ok(mut feeds) = feeds.lock() else {
            return;
        };
        let Some(existing) = feeds.get(monitor_id).cloned() else {
            return;
        };
        let was_active = existing.is_active();
        let keep_disabled = existing.is_disabled();
        let mut profile = existing.profile();
        if let Some((
            source_monitor_id,
            requested_width,
            requested_height,
            requested_fps,
            max_bitrate_kbit,
        )) = requested
        {
            profile.source_monitor_id = source_monitor_id as u32;
            profile.requested_width = requested_width.max(2);
            profile.requested_height = requested_height.max(2);
            profile.requested_fps = requested_fps.max(1);
            profile.max_bitrate_kbit = max_bitrate_kbit.max(800);
        }
        if let Some((display_width, display_height)) = display {
            profile.display_width = display_width.max(2);
            profile.display_height = display_height.max(2);
        }
        let next_feed = if keep_disabled {
            Some(PreviewFeed::spawn_disabled(profile))
        } else {
            match PreviewFeed::spawn(
                Arc::clone(&self.server_addr),
                monitor_id as u32,
                profile,
                Arc::clone(&self.log_sink),
            ) {
                Ok(feed) => Some(feed),
                Err(err) => {
                    warn!(monitor_id, ?err, "could not rebuild preview feed");
                    None
                }
            }
        };
        if let Some(feed) = next_feed {
            if was_active {
                feed.set_active(true);
            }
            existing.shutdown();
            feeds[monitor_id] = feed;
        }
    }

    pub fn set_monitor_enabled(&self, monitor_id: usize, enabled: bool) {
        self.set_feed_enabled(&self.inline_feeds, monitor_id, enabled);
        self.set_feed_enabled(&self.window_feeds, monitor_id, enabled);
    }

    fn set_feed_enabled(
        &self,
        feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
        monitor_id: usize,
        enabled: bool,
    ) {
        let Ok(mut feeds) = feeds.lock() else {
            return;
        };
        let Some(existing) = feeds.get(monitor_id).cloned() else {
            return;
        };
        if existing.is_disabled() != enabled {
            return;
        }
        let was_active = existing.is_active();
        let profile = existing.profile();
        let replacement = if enabled {
            match PreviewFeed::spawn(
                Arc::clone(&self.server_addr),
                monitor_id as u32,
                profile,
                Arc::clone(&self.log_sink),
            ) {
                Ok(feed) => feed,
                Err(err) => {
                    warn!(monitor_id, ?err, "could not enable preview feed");
                    return;
                }
            }
        } else {
            PreviewFeed::spawn_disabled(profile)
        };
        if was_active {
            replacement.set_active(true);
        }
        existing.shutdown();
        feeds[monitor_id] = replacement;
    }
}
