supgit 0.2.0

A simple Git CLI wrapper for common Git operations
use anyhow::{bail, Context, Result};
use std::env;
use std::process::Command;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

const INSTALL_URL: &str = "https://supgit.vercel.app/install.sh";
const GITHUB_API_URL: &str =
    "https://api.github.com/repos/ThomasNowProductions/SupGIT/releases/latest";
const UPDATE_CHECK_INTERVAL_SECS: u64 = 24 * 60 * 60;

fn get_current_version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}

fn get_last_check_file() -> Option<std::path::PathBuf> {
    dirs::cache_dir().map(|p| p.join("supgit").join("last_update_check"))
}

fn get_time_since_last_check() -> Option<Duration> {
    let path = get_last_check_file()?;
    let contents = std::fs::read_to_string(path).ok()?;
    let timestamp: u64 = contents.trim().parse().ok()?;
    let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
    Some(Duration::from_secs(now.saturating_sub(timestamp)))
}

fn record_update_check() {
    if let Some(path) = get_last_check_file() {
        let _ = std::fs::create_dir_all(path.parent().unwrap());
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs().to_string())
            .unwrap_or_default();
        let _ = std::fs::write(&path, now);
    }
}

fn fetch_latest_version() -> Result<String> {
    let output = Command::new("curl")
        .args([
            "-fsSL",
            "-H",
            "Accept: application/vnd.github.v3+json",
            GITHUB_API_URL,
        ])
        .output()
        .context("Failed to fetch release info from GitHub")?;

    if !output.status.success() {
        bail!(
            "Failed to fetch release info: {}",
            String::from_utf8_lossy(&output.stderr)
        );
    }

    let response = String::from_utf8_lossy(&output.stdout);
    for line in response.lines() {
        let line = line.trim();
        if line.starts_with("\"tag_name\":") {
            let version = line
                .split(':')
                .nth(1)
                .unwrap_or("")
                .trim()
                .trim_matches('"')
                .trim_start_matches('v')
                .trim_matches(',');
            return Ok(version.to_string());
        }
    }

    bail!("Could not parse version from GitHub response")
}

fn version_is_newer(latest: &str, current: &str) -> bool {
    let parse_version =
        |v: &str| -> Vec<u32> { v.split('.').filter_map(|s| s.parse().ok()).collect() };

    let latest_parts = parse_version(latest);
    let current_parts = parse_version(current);

    for i in 0..latest_parts.len().max(current_parts.len()) {
        let l = latest_parts.get(i).unwrap_or(&0);
        let c = current_parts.get(i).unwrap_or(&0);
        if l > c {
            return true;
        }
        if l < c {
            return false;
        }
    }
    false
}

pub fn check_and_auto_update() -> Result<()> {
    if env::var("SupGIT_SKIP_UPDATE_CHECK").is_ok() {
        return Ok(());
    }

    if let Some(elapsed) = get_time_since_last_check()
        && elapsed.as_secs() < UPDATE_CHECK_INTERVAL_SECS
    {
        return Ok(());
    }

    let latest = match fetch_latest_version() {
        Ok(v) => v,
        Err(_) => return Ok(()),
    };

    record_update_check();

    let current = get_current_version();
    if !version_is_newer(&latest, current) {
        return Ok(());
    }

    println!("Updating supgit from v{} to v{}...", current, latest);

    let mut sh = Command::new("sh");
    sh.arg("-c")
        .arg("curl -fsSL https://supgit.vercel.app/install.sh | sh");

    let status = sh.status().context("Failed to run installer")?;

    if status.success() {
        println!("✓ Updated to v{}", latest);
    }

    Ok(())
}

pub fn run_self_update(target_version: Option<&str>) -> Result<()> {
    println!("Current version: v{}", get_current_version());
    println!("Running installer...");

    let curl = Command::new("curl")
        .args(["-fsSL", INSTALL_URL])
        .output()
        .context("Failed to run curl. Is curl installed?")?;

    if !curl.status.success() {
        bail!(
            "Failed to download install script: {}",
            String::from_utf8_lossy(&curl.stderr)
        );
    }

    let mut sh = Command::new("sh");
    sh.arg("-c")
        .arg("curl -fsSL https://supgit.vercel.app/install.sh | sh");

    if let Some(v) = target_version {
        let version = if v.starts_with('v') {
            v.to_string()
        } else {
            format!("v{}", v)
        };
        sh.env("SupGIT_VERSION", version);
    }

    let status = sh.status().context("Failed to run installer")?;

    if !status.success() {
        bail!("Installer failed with exit code {:?}", status.code());
    }

    Ok(())
}