use gstreamer as gst;
use gstreamer::prelude::*;
use std::sync::{
    Arc, Mutex,
    atomic::{AtomicBool, AtomicU64, Ordering},
};
use std::time::{Duration, Instant};
use tonic::Status;
use tracing::warn;

use lesavka_common::lesavka::AudioPacket;

#[cfg(not(coverage))]
pub(super) struct AudioSourceHealth {
    started_at: Instant,
    last_sample_at: Mutex<Instant>,
    packets: AtomicU64,
    closed: AtomicBool,
}

#[cfg(not(coverage))]
impl AudioSourceHealth {
    pub(super) fn new() -> Self {
        let now = Instant::now();
        Self {
            started_at: now,
            last_sample_at: Mutex::new(now),
            packets: AtomicU64::new(0),
            closed: AtomicBool::new(false),
        }
    }

    pub(super) fn mark_sample(&self) {
        self.packets.fetch_add(1, Ordering::Relaxed);
        if let Ok(mut last) = self.last_sample_at.lock() {
            *last = Instant::now();
        }
    }

    pub(super) fn is_closed(&self) -> bool {
        self.closed.load(Ordering::Relaxed)
    }

    fn signal_failure(&self) -> bool {
        !self.closed.swap(true, Ordering::Relaxed)
    }

    fn elapsed(&self) -> Duration {
        self.started_at.elapsed()
    }

    fn idle_for(&self) -> Duration {
        self.last_sample_at
            .lock()
            .map(|last| last.elapsed())
            .unwrap_or_else(|_| Duration::from_secs(0))
    }

    fn packets(&self) -> u64 {
        self.packets.load(Ordering::Relaxed)
    }
}

#[cfg(not(coverage))]
fn env_duration_ms(name: &str, default_ms: u64) -> Duration {
    Duration::from_millis(env_u64(name, default_ms))
}

#[cfg(not(coverage))]
fn env_u64(name: &str, default: u64) -> u64 {
    std::env::var(name)
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(default)
}

#[cfg(not(coverage))]
fn env_bool(name: &str) -> bool {
    matches!(
        std::env::var(name).as_deref(),
        Ok("1" | "true" | "yes" | "on")
    )
}

/// Watch the remote speaker capture source without turning normal UAC silence
/// into a reconnect loop. Hard-fail idle/cadence checks only when an operator
/// explicitly enables those lab guardrails through the documented env knobs.
#[cfg(not(coverage))]
pub(super) fn spawn_audio_source_watchdog(
    pipeline: gst::Pipeline,
    health: Arc<AudioSourceHealth>,
    tx: tokio::sync::mpsc::Sender<Result<AudioPacket, Status>>,
    alsa_dev: String,
) {
    let startup_grace = env_duration_ms("LESAVKA_AUDIO_SOURCE_GRACE_MS", 3_000);
    let idle_timeout = env_duration_ms("LESAVKA_AUDIO_SOURCE_IDLE_MS", 1_500);
    let fail_on_idle = env_bool("LESAVKA_AUDIO_FAIL_ON_IDLE");
    let min_packets_per_second = env_u64("LESAVKA_AUDIO_MIN_PACKETS_PER_SEC", 0);
    std::thread::spawn(move || {
        loop {
            std::thread::sleep(Duration::from_millis(250));
            if health.is_closed() {
                break;
            }

            let elapsed = health.elapsed();
            if elapsed < startup_grace {
                continue;
            }

            let packets = health.packets();
            let idle_for = health.idle_for();
            let rate = packets as f64 / elapsed.as_secs_f64().max(0.001);

            let failure = if fail_on_idle && packets == 0 {
                Some(format!(
                    "remote speaker capture produced no audio samples after {} ms on {alsa_dev}",
                    elapsed.as_millis()
                ))
            } else if fail_on_idle && idle_for >= idle_timeout {
                Some(format!(
                    "remote speaker capture stalled for {} ms on {alsa_dev}",
                    idle_for.as_millis()
                ))
            } else if min_packets_per_second > 0
                && packets / elapsed.as_secs().max(1) < min_packets_per_second
            {
                Some(format!(
                    "remote speaker capture cadence is too low on {alsa_dev}: {rate:.1} packets/s, expected at least {} packets/s",
                    min_packets_per_second
                ))
            } else {
                None
            };

            if let Some(message) = failure {
                if health.signal_failure() {
                    warn!("🔊🛟 {message}; restarting audio capture on next client reconnect");
                    let _ = pipeline.set_state(gst::State::Null);
                    let _ = tx.blocking_send(Err(Status::unavailable(message)));
                }
                break;
            }
        }
    });
}
