use std::path::PathBuf;
use std::process::Command;
use std::fs;
use std::io::Write;

pub struct VolumeState {
    pub volume: u32,
    pub muted: bool,
    pub lock_path: PathBuf,
}

/// Atomically creates lock file. Returns true if this process is first (primary).
pub fn try_acquire_lock(path: &std::path::Path) -> bool {
    fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .map(|mut f| { let _ = f.write_all(b"1"); true })
        .unwrap_or(false)
}

/// Returns lock file path: $XDG_RUNTIME_DIR/ahfail.lock or /tmp/ahfail-{uid}.lock
pub fn lock_path() -> PathBuf {
    if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
        PathBuf::from(dir).join("ahfail.lock")
    } else {
        PathBuf::from(format!("/tmp/ahfail-{}.lock", get_uid()))
    }
}

fn get_uid() -> u32 {
    unsafe { libc::getuid() }
}

/// If primary: save current system volume/mute state, set volume to 100% unmuted.
/// Returns Some(VolumeState) if this process is the primary (should restore on exit).
pub fn save_and_set_max() -> Option<VolumeState> {
    let path = lock_path();
    if !try_acquire_lock(&path) { return None; }
    let (volume, muted) = get_current_volume();
    set_volume_max();
    Some(VolumeState { volume, muted, lock_path: path })
}

/// Restores volume from saved state and removes lock file.
pub fn restore(state: VolumeState) {
    restore_volume(state.volume, state.muted);
    let _ = fs::remove_file(&state.lock_path);
}

#[cfg(target_os = "linux")]
fn get_current_volume() -> (u32, bool) {
    let vol = Command::new("pactl")
        .args(["get-sink-volume", "@DEFAULT_SINK@"])
        .output()
        .ok()
        .and_then(|o| parse_pactl_volume(&String::from_utf8_lossy(&o.stdout)))
        .unwrap_or(100);

    let muted = Command::new("pactl")
        .args(["get-sink-mute", "@DEFAULT_SINK@"])
        .output()
        .ok()
        .map(|o| String::from_utf8_lossy(&o.stdout).contains("yes"))
        .unwrap_or(false);

    (vol, muted)
}

#[cfg(target_os = "linux")]
fn parse_pactl_volume(output: &str) -> Option<u32> {
    for token in output.split_whitespace() {
        if let Some(pct) = token.strip_suffix('%') {
            if let Ok(v) = pct.parse::<u32>() {
                if v <= 100 {
                    return Some(v);
                }
            }
        }
    }
    None
}

#[cfg(target_os = "linux")]
fn set_volume_max() {
    let _ = Command::new("pactl").args(["set-sink-mute", "@DEFAULT_SINK@", "0"]).status();
    let _ = Command::new("pactl").args(["set-sink-volume", "@DEFAULT_SINK@", "65536"]).status();
}

#[cfg(target_os = "linux")]
fn restore_volume(volume: u32, muted: bool) {
    let v = ((volume as u64 * 65536) / 100) as u32;
    let _ = Command::new("pactl")
        .args(["set-sink-volume", "@DEFAULT_SINK@", &v.to_string()])
        .status();
    let mute_arg = if muted { "1" } else { "0" };
    let _ = Command::new("pactl").args(["set-sink-mute", "@DEFAULT_SINK@", mute_arg]).status();
}

#[cfg(target_os = "macos")]
fn get_current_volume() -> (u32, bool) {
    let output = Command::new("osascript")
        .args(["-e", "output volume of (get volume settings)"])
        .output()
        .ok();
    let vol = output.as_ref()
        .and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse().ok())
        .unwrap_or(100);

    let muted = Command::new("osascript")
        .args(["-e", "output muted of (get volume settings)"])
        .output()
        .ok()
        .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "true")
        .unwrap_or(false);

    (vol, muted)
}

#[cfg(target_os = "macos")]
fn set_volume_max() {
    let _ = Command::new("osascript")
        .args(["-e", "set volume output volume 100 without output muted"])
        .status();
}

#[cfg(target_os = "macos")]
fn restore_volume(volume: u32, muted: bool) {
    let script = if muted {
        format!("set volume output volume {} with output muted", volume)
    } else {
        format!("set volume output volume {} without output muted", volume)
    };
    let _ = Command::new("osascript").args(["-e", &script]).status();
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn get_current_volume() -> (u32, bool) {
    (100, false)
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn set_volume_max() {}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn restore_volume(_volume: u32, _muted: bool) {}
