use std::time::Duration;
use tokio::time::Instant;

use super::UpstreamMediaKind;
use crate::calibration::{FACTORY_MJPEG_AUDIO_OFFSET_US, FACTORY_MJPEG_VIDEO_OFFSET_US};

pub(super) fn upstream_timing_trace_enabled() -> bool {
    std::env::var("LESAVKA_UPSTREAM_TIMING_TRACE")
        .ok()
        .map(|value| {
            let trimmed = value.trim();
            !(trimmed.eq_ignore_ascii_case("0")
                || trimmed.eq_ignore_ascii_case("false")
                || trimmed.eq_ignore_ascii_case("no")
                || trimmed.eq_ignore_ascii_case("off"))
        })
        .unwrap_or(false)
}

pub(super) fn upstream_playout_delay() -> Duration {
    let delay_ms = std::env::var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(350);
    Duration::from_millis(delay_ms)
}

pub(super) fn upstream_bundled_playout_delay() -> Duration {
    let delay_ms = std::env::var("LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS")
        .or_else(|_| std::env::var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS"))
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(350);
    Duration::from_millis(delay_ms)
}

pub(super) fn upstream_max_live_lag() -> Duration {
    let lag_ms = std::env::var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(1_000);
    Duration::from_millis(lag_ms.clamp(1, 1_000))
}

pub(super) fn upstream_startup_timeout() -> Duration {
    let timeout_ms = std::env::var("LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(60_000);
    Duration::from_millis(timeout_ms.max(1))
}

pub(super) fn upstream_require_paired_startup() -> bool {
    std::env::var("LESAVKA_UPSTREAM_REQUIRE_PAIRED_STARTUP")
        .ok()
        .map(|value| {
            let trimmed = value.trim();
            !(trimmed.eq_ignore_ascii_case("0")
                || trimmed.eq_ignore_ascii_case("false")
                || trimmed.eq_ignore_ascii_case("no")
                || trimmed.eq_ignore_ascii_case("off"))
        })
        .unwrap_or(true)
}

pub(super) fn upstream_playout_offset_us(kind: UpstreamMediaKind) -> i64 {
    let name = match kind {
        UpstreamMediaKind::Camera => "LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US",
        UpstreamMediaKind::Microphone => "LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US",
    };
    let default_offset_us = match kind {
        UpstreamMediaKind::Camera => FACTORY_MJPEG_VIDEO_OFFSET_US,
        // 0.17 keeps shipped offsets small: the planner owns freshness and
        // pairing, while calibration only handles sub-frame trim.
        UpstreamMediaKind::Microphone => FACTORY_MJPEG_AUDIO_OFFSET_US,
    };
    std::env::var(name)
        .ok()
        .and_then(|value| value.trim().parse::<i64>().ok())
        .unwrap_or(default_offset_us)
}

pub(super) fn upstream_bundled_playout_offset_override_us(kind: UpstreamMediaKind) -> Option<i64> {
    let name = match kind {
        UpstreamMediaKind::Camera => "LESAVKA_UPSTREAM_BUNDLED_VIDEO_PLAYOUT_OFFSET_US",
        UpstreamMediaKind::Microphone => "LESAVKA_UPSTREAM_BUNDLED_AUDIO_PLAYOUT_OFFSET_US",
    };
    std::env::var(name)
        .ok()
        .and_then(|value| value.trim().parse::<i64>().ok())
}

pub(super) fn upstream_pairing_master_slack() -> Duration {
    let slack_us = std::env::var("LESAVKA_UPSTREAM_PAIR_SLACK_US")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(80_000);
    Duration::from_micros(slack_us)
}

pub(super) fn upstream_audio_master_wait_grace() -> Duration {
    let grace_ms = std::env::var("LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(350);
    Duration::from_millis(grace_ms)
}

pub(super) fn upstream_reanchor_late_threshold(playout_delay: Duration) -> Duration {
    if let Some(override_ms) = std::env::var("LESAVKA_UPSTREAM_REANCHOR_LATE_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
    {
        return Duration::from_millis(override_ms);
    }

    let default_ms = (playout_delay.as_millis().min(u64::MAX as u128) as u64)
        .saturating_div(2)
        .max(100);
    Duration::from_millis(default_ms)
}

pub(super) fn upstream_camera_startup_grace_us() -> u64 {
    std::env::var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .unwrap_or(if cfg!(test) { 0 } else { 250 })
        .saturating_mul(1_000)
}

pub(super) fn apply_playout_offset(base: Instant, offset_us: i64) -> Instant {
    if offset_us >= 0 {
        base + Duration::from_micros(offset_us as u64)
    } else {
        base.checked_sub(Duration::from_micros(offset_us.unsigned_abs()))
            .unwrap_or(base)
    }
}
