sticks 0.3.3

A tool for managing C and C++ projects
Documentation
use anyhow::{Context, Result};
use std::env;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::Command;

use crate::constants::{github, install_paths};

fn get_install_path() -> Result<PathBuf> {
	if let Ok(output) = Command::new("which").arg("sticks").output() {
		if output.status.success() {
			let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
			return Ok(PathBuf::from(path));
		}
	}

	let paths = vec![
		PathBuf::from(install_paths::USR_LOCAL_BIN),
		PathBuf::from(install_paths::USR_BIN),
		dirs::home_dir()
			.map(|h| h.join(install_paths::CARGO_BIN_SUFFIX))
			.unwrap_or_default(),
	];

	for path in paths {
		if path.exists() {
			return Ok(path);
		}
	}

	anyhow::bail!("Could not determine sticks installation path")
}

fn get_architecture() -> &'static str {
	#[cfg(target_arch = "x86_64")]
	return "x86_64";
	#[cfg(not(target_arch = "x86_64"))]
	return "unsupported";
}

fn get_current_version() -> String {
	env!("CARGO_PKG_VERSION").to_string()
}

fn get_latest_version() -> Result<String> {
	let output = Command::new("curl")
		.args(["-s", github::RELEASE_API_URL])
		.output()
		.context("Failed to fetch latest release information")?;

	if !output.status.success() {
		anyhow::bail!("Failed to check for latest version");
	}

	let json = String::from_utf8_lossy(&output.stdout);

	// Extract tag_name from JSON response using a more robust approach
	if let Some(tag_start) = json.find("\"tag_name\":\"") {
		let version_str = &json[tag_start + 12..];
		if let Some(tag_end) = version_str.find('"') {
			let version = version_str[..tag_end].trim_start_matches('v').to_string();
			return Ok(version);
		}
	}

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

pub fn update_project() -> Result<()> {
	println!("🔄 Checking for updates...");

	let current_version = get_current_version();
	let arch = get_architecture();
	if arch == "unsupported" {
		anyhow::bail!(
			"Unsupported architecture: only x86_64 is supported. Please update manually."
		);
	}

	let install_path = get_install_path()?;
	let is_system_install =
		install_path.starts_with("/usr/bin") || install_path.starts_with("/usr/local/bin");

	let latest_version = get_latest_version().context("Failed to check for updates")?;

	if current_version == latest_version {
		println!(
			"✓ You're already on the latest version (v{})!",
			current_version
		);
		return Ok(());
	}

	println!(
		"đŸ“Ļ Update available: v{} → v{}",
		current_version, latest_version
	);

	if is_system_install {
		println!();
		println!("â„šī¸  System installation detected.");
		println!("đŸ“Ļ Please use your package manager to update:");
		println!();
		println!("  Arch Linux:     sudo pacman -Syu sticks");
		println!("  Debian/Ubuntu:  sudo apt update && sudo apt upgrade sticks");
		println!();
		println!("💡 Or download the latest package from:");
		println!("   https://github.com/mAmineChniti/sticks/releases/latest");
		return Ok(());
	}

	println!("đŸ“Ĩ Downloading v{} from GitHub releases...", latest_version);

	let temp_dir = env::temp_dir().join(format!("sticks-update-{}", std::process::id()));
	fs::create_dir_all(&temp_dir).context("Failed to create temp directory")?;

	let binary_name = format!("sticks-linux-{}", arch);
	let download_url = format!("{}/{}", github::RELEASE_DOWNLOAD_URL, binary_name);
	let temp_binary = temp_dir.join("sticks");

	let status = Command::new("curl")
		.args([
			"-L",
			"-f",
			"-o",
			temp_binary.to_str().unwrap(),
			&download_url,
		])
		.status()
		.context("Failed to download update. Is curl installed?")?;

	if !status.success() {
		fs::remove_dir_all(&temp_dir).ok();
		anyhow::bail!(
			"Failed to download update from {}. \
			Please check your internet connection or download manually.",
			download_url
		);
	}

	let mut perms = fs::metadata(&temp_binary)
		.context("Failed to get file permissions")?
		.permissions();
	perms.set_mode(0o755);
	fs::set_permissions(&temp_binary, perms).context("Failed to set executable permissions")?;

	fs::copy(&temp_binary, &install_path)
		.with_context(|| format!("Failed to replace binary at {:?}", install_path))?;

	fs::remove_dir_all(&temp_dir).ok();

	println!();
	println!(
		"✓ Successfully upgraded from v{} to v{}!",
		current_version, latest_version
	);
	println!("💡 Run 'sticks --version' to verify the installation.");

	Ok(())
}