gitcore 1.2.0

A secure, zero-friction Git identity manager for developers who juggle multiple accounts.
use crate::git::run_command;
use crate::models::{Account, Platform};
use colored::Colorize;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process::{Command, Output};

pub fn get_ssh_dir() -> PathBuf {
    dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("~"))
        .join(".ssh")
}

pub fn update_ssh_config(accounts: &[Account]) -> io::Result<()> {
    let ssh_dir = get_ssh_dir();
    fs::create_dir_all(&ssh_dir)?;
    let config_path = ssh_dir.join("config");
    const START_MARKER: &str = "# >>> gitcore managed block >>>";
    const END_MARKER: &str = "# <<< gitcore managed block <<<";

    let mut managed_block = String::new();
    managed_block.push_str("# Generated by gitcore - DO NOT EDIT MANUALLY\n");
    managed_block.push_str(START_MARKER);
    managed_block.push('\n');

    for acc in accounts {
        let key_full_path = ssh_dir.join(&acc.key_path);
        let key_path_str = key_full_path.to_string_lossy().replace('\\', "/");
        managed_block.push_str(&format!("Host {}\n", acc.host_alias));
        managed_block.push_str(&format!("  HostName {}\n", acc.platform.host()));
        managed_block.push_str("  User git\n");
        managed_block.push_str(&format!("  IdentityFile {}\n", key_path_str));
        managed_block.push_str("  AddKeysToAgent yes\n");
        managed_block.push_str("  IdentitiesOnly yes\n\n");
    }

    managed_block.push_str(END_MARKER);
    managed_block.push('\n');

    let existing = fs::read_to_string(&config_path).unwrap_or_default();
    let new_content = if let Some(start) = existing.find(START_MARKER) {
        if let Some(end_rel) = existing[start..].find(END_MARKER) {
            let end = start + end_rel + END_MARKER.len();
            let before = existing[..start].trim_end();
            let after = existing[end..].trim_start();

            let mut merged = String::new();
            if !before.is_empty() {
                merged.push_str(before);
                merged.push_str("\n\n");
            }
            merged.push_str(&managed_block);
            if !after.is_empty() {
                merged.push('\n');
                merged.push_str(after);
                if !merged.ends_with('\n') {
                    merged.push('\n');
                }
            }
            merged
        } else {
            format!("{}\n{}", existing.trim_end(), managed_block)
        }
    } else if existing.trim().is_empty() {
        managed_block.clone()
    } else {
        format!("{}\n\n{}", existing.trim_end(), managed_block)
    };

    fs::write(&config_path, new_content)?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let _ = fs::set_permissions(&config_path, fs::Permissions::from_mode(0o600));
    }

    println!("{}", "Success: SSH config updated".green());
    Ok(())
}

pub fn generate_ssh_key(key_path: &str, email: &str, passphrase: &str) -> io::Result<String> {
    let ssh_dir = get_ssh_dir();
    let key_full = ssh_dir.join(key_path);

    if key_full.exists() {
        println!("{}", "Key already exists, using existing key".yellow());
    } else {
        println!("{}", "[*] Generating SSH key...".cyan());
        run_command(
            "ssh-keygen",
            &[
                "-t",
                "ed25519",
                "-f",
                key_full.to_str().unwrap(),
                "-N",
                passphrase,
                "-C",
                email,
            ],
        )?;

        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let _ = fs::set_permissions(&key_full, fs::Permissions::from_mode(0o600));
            let pub_key_path = ssh_dir.join(format!("{}.pub", key_path));
            let _ = fs::set_permissions(&pub_key_path, fs::Permissions::from_mode(0o644));
        }

        println!("{}", "Success: SSH key generated".green());
        if passphrase.is_empty() {
            println!(
                "{}",
                "Warning: No passphrase - key is not protected".yellow()
            );
        } else {
            println!("{}", "Success: Key is protected with passphrase".green());
        }
    }

    let pub_key_path = ssh_dir.join(format!("{}.pub", key_path));
    let pub_key = fs::read_to_string(pub_key_path)?;
    Ok(pub_key)
}

pub fn delete_account_keys(name: &str) -> io::Result<Vec<PathBuf>> {
    let ssh_dir = get_ssh_dir();
    let key_name = format!("id_ed25519_{}", name);
    let paths = [
        ssh_dir.join(&key_name),
        ssh_dir.join(format!("{}.pub", key_name)),
    ];

    let mut deleted = Vec::new();
    for path in paths {
        if path.exists() {
            fs::remove_file(&path)?;
            deleted.push(path);
        }
    }

    Ok(deleted)
}

pub fn test_ssh_connection(host_alias: &str) -> io::Result<Output> {
    Command::new("ssh")
        .args([
            "-o",
            "BatchMode=yes",
            "-o",
            "ConnectTimeout=10",
            "-o",
            "StrictHostKeyChecking=accept-new",
            "-T",
            host_alias,
        ])
        .output()
}

pub fn check_host_key(platform_host: &str) -> HostKeyStatus {
    let known_hosts_path = get_ssh_dir().join("known_hosts");

    if !known_hosts_path.exists() {
        return HostKeyStatus::Unknown;
    }

    match fs::read_to_string(&known_hosts_path) {
        Ok(content) => {
            if content.contains(platform_host) {
                HostKeyStatus::Known
            } else {
                HostKeyStatus::New
            }
        }
        Err(_) => HostKeyStatus::Unknown,
    }
}

#[derive(Debug, Clone, Copy)]
pub enum HostKeyStatus {
    Known,
    New,
    Unknown,
}

pub fn provider_key_url(platform: &Platform) -> &'static str {
    match platform {
        Platform::Github => "https://github.com/settings/keys",
        Platform::Gitlab => "https://gitlab.com/-/profile/keys",
        Platform::Codeberg => "https://codeberg.org/user/keys",
        Platform::Bitbucket => "https://bitbucket.org/account/settings/ssh-keys/",
    }
}