use anyhow::{Context, Result, anyhow};
use gst::prelude::*;
use gstreamer as gst;
use gstreamer_app as gst_app;
use gtk::{gdk, glib};
use shell_escape::escape;
use std::borrow::Cow;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use super::devices::CameraMode;

const CAMERA_PREVIEW_DEFAULT_WIDTH: i32 = 1280;
const CAMERA_PREVIEW_DEFAULT_HEIGHT: i32 = 720;
const CAMERA_PREVIEW_DEFAULT_FPS: u32 = 30;
const CAMERA_PREVIEW_IDLE: &str = "Select a webcam and click Start Preview.";
const MIC_MONITOR_RATE: i32 = 16_000;
const MIC_MONITOR_CHANNELS: i32 = 1;
const MIC_MONITOR_SAMPLE_BYTES: usize = 2;
const MIC_REPLAY_SECONDS: usize = 3;
const MIC_REPLAY_PATH: &str = "/tmp/lesavka-mic-replay.wav";
const MIC_REPLAY_MAX_BYTES: usize = MIC_MONITOR_RATE as usize
    * MIC_MONITOR_CHANNELS as usize
    * MIC_MONITOR_SAMPLE_BYTES
    * MIC_REPLAY_SECONDS;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceTestKind {
    Camera,
    Microphone,
    MicrophoneReplay,
    Speaker,
}

pub struct DeviceTestController {
    camera: Option<LocalCameraPreview>,
    selected_camera: Option<String>,
    selected_camera_mode: Option<CameraMode>,
    microphone: Option<LocalMicrophoneMonitor>,
    microphone_probe: Option<LocalMicrophoneLevelProbe>,
    speaker: Option<Child>,
    microphone_replay: Option<Child>,
    microphone_buffer: Arc<Mutex<Vec<u8>>>,
    microphone_level: Arc<Mutex<f64>>,
}

impl Default for DeviceTestController {
    fn default() -> Self {
        Self {
            camera: None,
            selected_camera: None,
            selected_camera_mode: None,
            microphone: None,
            microphone_probe: None,
            speaker: None,
            microphone_replay: None,
            microphone_buffer: Arc::new(Mutex::new(Vec::new())),
            microphone_level: Arc::new(Mutex::new(0.0)),
        }
    }
}

impl DeviceTestController {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn bind_camera_preview(
        &mut self,
        camera_picture: &gtk::Picture,
        camera_status: &gtk::Label,
    ) -> Result<()> {
        let mirrored = self
            .camera
            .as_ref()
            .is_some_and(LocalCameraPreview::mirrored);
        if let Some(camera) = self.camera.as_mut() {
            camera.stop();
        }
        let mut preview = LocalCameraPreview::new(camera_picture, camera_status);
        preview.set_mirrored(mirrored);
        preview.set_selected(self.selected_camera.as_deref())?;
        preview.set_selected_mode(self.selected_camera_mode)?;
        self.camera = Some(preview);
        Ok(())
    }

    pub fn is_running(&mut self, kind: DeviceTestKind) -> bool {
        self.cleanup_finished();
        match kind {
            DeviceTestKind::Camera => self
                .camera
                .as_ref()
                .is_some_and(LocalCameraPreview::is_running),
            DeviceTestKind::Microphone => {
                self.microphone
                    .as_ref()
                    .is_some_and(LocalMicrophoneMonitor::is_running)
                    || self
                        .microphone_probe
                        .as_ref()
                        .is_some_and(LocalMicrophoneLevelProbe::is_running)
            }
            DeviceTestKind::MicrophoneReplay => self.microphone_replay.is_some(),
            DeviceTestKind::Speaker => self.speaker.is_some(),
        }
    }

    pub fn set_camera_selection(&mut self, camera: Option<&str>) -> Result<()> {
        self.selected_camera = normalize_camera_selection(camera);
        if let Some(preview) = self.camera.as_mut() {
            preview.set_selected(self.selected_camera.as_deref())?;
        }
        Ok(())
    }

    pub fn set_camera_quality(&mut self, mode: Option<CameraMode>) -> Result<()> {
        self.selected_camera_mode = mode;
        if let Some(preview) = self.camera.as_mut() {
            preview.set_selected_mode(mode)?;
        }
        Ok(())
    }

    pub fn toggle_camera(&mut self) -> Result<bool> {
        let preview = self
            .camera
            .as_mut()
            .ok_or_else(|| anyhow!("camera preview panel is not ready yet"))?;
        preview.toggle()
    }

    pub fn stop_camera_preview(&mut self) {
        if let Some(camera) = self.camera.as_mut() {
            camera.stop();
        }
    }

    pub fn set_camera_preview_mirrored(&mut self, mirrored: bool) {
        if let Some(camera) = self.camera.as_mut() {
            camera.set_mirrored(mirrored);
        }
    }

    pub fn camera_preview_mirrored(&self) -> bool {
        self.camera
            .as_ref()
            .is_some_and(LocalCameraPreview::mirrored)
    }

    pub fn toggle_microphone(&mut self, source: Option<&str>, sink: Option<&str>) -> Result<bool> {
        self.cleanup_finished();
        if self.microphone.is_some() {
            self.stop(DeviceTestKind::Microphone);
            return Ok(false);
        }

        let monitor = LocalMicrophoneMonitor::start(
            source,
            sink,
            Arc::clone(&self.microphone_buffer),
            Arc::clone(&self.microphone_level),
        )?;
        self.microphone = Some(monitor);
        Ok(true)
    }

    pub fn stop_microphone_monitor(&mut self) {
        self.stop(DeviceTestKind::Microphone);
    }

    pub fn stop_local_capture_for_relay(&mut self) {
        if self
            .camera
            .as_ref()
            .is_some_and(LocalCameraPreview::is_device_preview_running)
            && let Some(camera) = self.camera.as_mut()
        {
            camera.stop();
        }
        if let Some(mut monitor) = self.microphone.take() {
            monitor.stop();
        }
    }

    pub fn sync_relay_uplink_probe(
        &mut self,
        relay_live: bool,
        camera_active: bool,
        camera_label: Option<&str>,
        camera_preview_path: &Path,
        microphone_active: bool,
        microphone_level_path: &Path,
    ) -> Result<()> {
        self.cleanup_finished();
        let camera_should_probe = relay_live && camera_active && camera_label.is_some();
        if camera_should_probe {
            let preview = self
                .camera
                .as_mut()
                .ok_or_else(|| anyhow!("camera preview panel is not ready yet"))?;
            preview.start_relay_file(
                camera_preview_path.to_path_buf(),
                camera_label.unwrap_or("selected webcam").to_string(),
            )?;
        } else if self
            .camera
            .as_ref()
            .is_some_and(LocalCameraPreview::is_relay_file_running)
            && let Some(preview) = self.camera.as_mut()
        {
            preview.stop();
        }

        let microphone_should_probe = relay_live && microphone_active;
        if microphone_should_probe {
            if self.microphone.is_some() {
                self.stop(DeviceTestKind::Microphone);
            }
            let needs_probe = self
                .microphone_probe
                .as_ref()
                .is_none_or(|probe| !probe.is_running_for(microphone_level_path));
            if needs_probe {
                self.stop_microphone_probe();
                self.microphone_probe = Some(LocalMicrophoneLevelProbe::start(
                    microphone_level_path.to_path_buf(),
                    Arc::clone(&self.microphone_level),
                ));
            }
        } else {
            self.stop_microphone_probe();
        }
        Ok(())
    }

    pub fn toggle_speaker(&mut self, sink: Option<&str>) -> Result<bool> {
        self.toggle_child(DeviceTestKind::Speaker, build_speaker_test(sink))
    }

    pub fn stop_speaker_test(&mut self) {
        if self.speaker.is_some() {
            self.stop(DeviceTestKind::Speaker);
        }
    }

    pub fn toggle_microphone_replay(&mut self, sink: Option<&str>) -> Result<bool> {
        self.cleanup_finished();
        if self.microphone_replay.is_some() {
            self.stop(DeviceTestKind::MicrophoneReplay);
            return Ok(false);
        }

        let wav_bytes = self.replay_wav_bytes()?;
        fs::write(MIC_REPLAY_PATH, wav_bytes).context("writing microphone replay clip")?;
        let child = build_microphone_replay_test(MIC_REPLAY_PATH, sink)?
            .spawn()
            .context("starting microphone replay")?;
        self.microphone_replay = Some(child);
        Ok(true)
    }

    pub fn stop_microphone_replay(&mut self) {
        if self.microphone_replay.is_some() {
            self.stop(DeviceTestKind::MicrophoneReplay);
        }
    }

    pub fn microphone_level_fraction(&mut self) -> f64 {
        self.cleanup_finished();
        self.microphone_level
            .lock()
            .map(|value| (*value).clamp(0.0, 1.0))
            .unwrap_or(0.0)
    }

    pub fn microphone_replay_ready(&mut self) -> bool {
        self.cleanup_finished();
        self.microphone_buffer
            .lock()
            .map(|buffer| !buffer.is_empty())
            .unwrap_or(false)
    }

    pub fn stop_all(&mut self) {
        if let Some(camera) = self.camera.as_mut() {
            camera.stop();
        }
        for kind in [
            DeviceTestKind::Microphone,
            DeviceTestKind::MicrophoneReplay,
            DeviceTestKind::Speaker,
        ] {
            self.stop(kind);
        }
        self.stop_microphone_probe();
    }

    fn toggle_child(&mut self, kind: DeviceTestKind, command: Result<Command>) -> Result<bool> {
        self.cleanup_finished();
        if self.slot(kind).is_some() {
            self.stop(kind);
            return Ok(false);
        }
        let child = command?
            .spawn()
            .with_context(|| format!("starting {kind:?} test"))?;
        *self.slot_mut(kind) = Some(child);
        Ok(true)
    }

    fn stop(&mut self, kind: DeviceTestKind) {
        match kind {
            DeviceTestKind::Camera => panic!("camera preview is not stopped through this path"),
            DeviceTestKind::Microphone => {
                if let Some(mut monitor) = self.microphone.take() {
                    monitor.stop();
                }
                self.stop_microphone_probe();
                if let Ok(mut level) = self.microphone_level.lock() {
                    *level = 0.0;
                }
            }
            DeviceTestKind::MicrophoneReplay | DeviceTestKind::Speaker => {
                if let Some(mut child) = self.slot_mut(kind).take() {
                    let _ = child.kill();
                    let _ = child.wait();
                }
            }
        }
    }

    fn cleanup_finished(&mut self) {
        if self
            .microphone
            .as_mut()
            .is_some_and(|monitor| !monitor.is_running())
        {
            self.microphone = None;
        }
        if self
            .microphone_probe
            .as_mut()
            .is_some_and(|probe| !probe.is_running())
        {
            self.microphone_probe = None;
        }
        for kind in [DeviceTestKind::MicrophoneReplay, DeviceTestKind::Speaker] {
            let finished = self
                .slot_mut(kind)
                .as_mut()
                .and_then(|child| child.try_wait().ok())
                .flatten()
                .is_some();
            if finished {
                let _ = self.slot_mut(kind).take();
            }
        }
    }

    fn slot(&self, kind: DeviceTestKind) -> &Option<Child> {
        match kind {
            DeviceTestKind::Camera | DeviceTestKind::Microphone => {
                panic!("this device test is not an external child process")
            }
            DeviceTestKind::MicrophoneReplay => &self.microphone_replay,
            DeviceTestKind::Speaker => &self.speaker,
        }
    }

    fn slot_mut(&mut self, kind: DeviceTestKind) -> &mut Option<Child> {
        match kind {
            DeviceTestKind::Camera | DeviceTestKind::Microphone => {
                panic!("this device test is not an external child process")
            }
            DeviceTestKind::MicrophoneReplay => &mut self.microphone_replay,
            DeviceTestKind::Speaker => &mut self.speaker,
        }
    }

    fn stop_microphone_probe(&mut self) {
        if let Some(mut probe) = self.microphone_probe.take() {
            probe.stop();
        }
    }

    fn replay_wav_bytes(&self) -> Result<Vec<u8>> {
        let audio = self
            .microphone_buffer
            .lock()
            .map_err(|_| anyhow!("microphone replay buffer is unavailable right now"))?
            .clone();
        if audio.is_empty() {
            return Err(anyhow!(
                "Monitor Mic long enough to capture audio before replaying the last 3 seconds."
            ));
        }
        Ok(build_wav_bytes(
            &audio,
            MIC_MONITOR_RATE as u32,
            MIC_MONITOR_CHANNELS as u16,
            16,
        ))
    }
}

struct LocalCameraPreview {
    picture: gtk::Picture,
    latest: Arc<Mutex<Option<PreviewFrame>>>,
    status_text: Arc<Mutex<String>>,
    generation: Arc<AtomicU64>,
    running: Arc<AtomicBool>,
    mirrored: Arc<AtomicBool>,
    selected_device: Option<String>,
    selected_mode: Option<CameraMode>,
    relay_preview_path: Option<PathBuf>,
}

struct LocalMicrophoneMonitor {
    running: Arc<AtomicBool>,
    generation: Arc<AtomicU64>,
}

struct LocalMicrophoneLevelProbe {
    path: PathBuf,
    running: Arc<AtomicBool>,
    generation: Arc<AtomicU64>,
}

struct PreviewFrame {
    width: i32,
    height: i32,
    stride: usize,
    rgba: Vec<u8>,
}
