use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::SystemTime;
use std::fs;

const GITEA_API: &str =
    "https://gitea.weircon.dk/api/v1/repos/agw/gtk-ahfail/releases/latest";
const UPDATE_NOTIFY_MSG: &str =
    "Update available — visit https://gitea.weircon.dk/agw/gtk-ahfail/releases";
const CHECK_INTERVAL_SECS: u64 = 86_400;

// Guards against multiple windows spawning duplicate checks on the same failure.
static CHECK_IN_FLIGHT: AtomicBool = AtomicBool::new(false);

pub fn is_newer(latest: &str, current: &str) -> bool {
    let parse = |s: &str| -> Option<(u32, u32, u32)> {
        let s = s.trim_start_matches('v');
        let p: Vec<&str> = s.splitn(3, '.').collect();
        if p.len() != 3 { return None; }
        Some((p[0].parse().ok()?, p[1].parse().ok()?, p[2].parse().ok()?))
    };
    match (parse(latest), parse(current)) {
        (Some(l), Some(c)) => l > c,
        _ => false,
    }
}

fn cache_file() -> PathBuf {
    let dir = std::env::var("HOME")
        .map(|h| PathBuf::from(h).join(".cache/ahfail"))
        .unwrap_or_else(|_| {
            let uid = unsafe { libc::getuid() };
            PathBuf::from(format!("/tmp/ahfail-cache-{uid}"))
        });
    let _ = fs::create_dir_all(&dir);
    dir.join("last_update_check")
}

fn rate_limited() -> bool {
    let path = cache_file();
    if let Ok(meta) = fs::metadata(&path) {
        if let Ok(modified) = meta.modified() {
            let age = SystemTime::now()
                .duration_since(modified)
                .unwrap_or_default();
            return age.as_secs() < CHECK_INTERVAL_SECS;
        }
    }
    false
}

fn touch_cache() {
    let _ = fs::write(cache_file(), b"");
}

fn send_notification() {
    #[cfg(target_os = "linux")]
    let _ = Command::new("notify-send")
        .args(["ahfail", UPDATE_NOTIFY_MSG])
        .status();

    #[cfg(target_os = "macos")]
    let _ = Command::new("osascript")
        .args(["-e", &format!(
            "display notification \"{}\" with title \"ahfail\"",
            UPDATE_NOTIFY_MSG
        )])
        .status();
}

/// Run in a background thread. Checks Gitea for a newer release and sends a
/// desktop notification if one exists. Rate-limited to once per 24 hours.
/// Fails silently on any error.
pub fn check_for_update(current_version: &str) {
    if rate_limited() { return; }
    // Prevent concurrent threads (e.g. multi-monitor) from all firing at once.
    if CHECK_IN_FLIGHT.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
        return;
    }

    let agent = ureq::AgentBuilder::new()
        .timeout_connect(std::time::Duration::from_secs(5))
        .timeout_read(std::time::Duration::from_secs(10))
        .build();

    let Ok(resp) = agent.get(GITEA_API).call() else {
        CHECK_IN_FLIGHT.store(false, Ordering::SeqCst);
        return;
    };
    touch_cache();

    let Ok(body) = resp.into_string() else {
        CHECK_IN_FLIGHT.store(false, Ordering::SeqCst);
        return;
    };

    CHECK_IN_FLIGHT.store(false, Ordering::SeqCst);

    if let Some(tag) = extract_tag_name(&body) {
        if is_newer(&tag, current_version) {
            send_notification();
        }
    }
}

fn extract_tag_name(json: &str) -> Option<String> {
    // Handle both compact ("tag_name":"v1.0") and spaced ("tag_name": "v1.0") JSON.
    let key = "\"tag_name\"";
    let after_key = json.find(key)? + key.len();
    let rest = json[after_key..].trim_start_matches(|c: char| c == ':' || c.is_ascii_whitespace());
    let rest = rest.strip_prefix('"')?;
    let end = rest.find('"')?;
    Some(rest[..end].to_string())
}
