use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

use rho_core::{IdentityBundleManifest, LocalIdentityManifest, from_yaml, normalize_actor_id};

use rho_core::commands;

fn usage() -> ! {
    eprintln!(
        "usage: rho [--profile <github-handle|rho-id>] [--identity <rho-id>] [--home <path>] <dataset|publish|message|request|approve|result|id|crypto|repo|status|gh|commit|env|install-shell|version> ..."
    );
    std::process::exit(2);
}

fn gh_usage() -> ! {
    eprintln!("usage:\n  rho gh switch <login>\n  rho gh status\n  rho gh whoami");
    std::process::exit(2);
}

fn command_text(command: &mut Command, label: &str) -> String {
    let output = command.output().unwrap_or_else(|error| {
        eprintln!("failed to run {label}: {error}");
        std::process::exit(1);
    });
    if !output.status.success() {
        eprintln!("{label} failed");
        eprint!("{}", String::from_utf8_lossy(&output.stderr));
        std::process::exit(output.status.code().unwrap_or(1));
    }
    String::from_utf8_lossy(&output.stdout).trim().to_string()
}

fn run_subcommand(command: fn(&[String]) -> rho_core::RhoResult<()>, args: &[String]) -> ! {
    if let Err(error) = command(args) {
        eprintln!("{error}");
        std::process::exit(1);
    }
    std::process::exit(0);
}

fn try_gh_login() -> Result<String, String> {
    let output = Command::new("gh")
        .args(["api", "user", "--jq", ".login"])
        .output()
        .map_err(|error| format!("failed to run gh api user: {error}"))?;
    if !output.status.success() {
        return Err(String::from_utf8_lossy(&output.stderr).trim().to_string());
    }
    let login = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if login.is_empty() {
        return Err("gh api user returned an empty login".to_string());
    }
    Ok(login)
}

fn gh_user() -> (String, String) {
    let login = command_text(
        Command::new("gh").args(["api", "user", "--jq", ".login"]),
        "gh api user",
    );
    let id = command_text(
        Command::new("gh").args(["api", "user", "--jq", ".id"]),
        "gh api user",
    );
    (login, id)
}

fn identity_login_mismatch(identity: &str, active_login: &str) -> Result<Option<String>, String> {
    let canonical = normalize_actor_id(identity).map_err(|error| error.to_string())?;
    let handle = github_handle(&canonical)?;
    if handle == active_login {
        return Ok(None);
    }
    Ok(Some(format!(
        "rho warning: active gh account is {active_login}, but RHO_IDENTITY is {canonical}"
    )))
}

fn remove_bool_flag(args: &mut Vec<String>, flag: &str) -> bool {
    let before = args.len();
    args.retain(|arg| arg != flag);
    args.len() != before
}

fn take_value_flag(args: &mut Vec<String>, flags: &[&str]) -> Result<Option<String>, String> {
    let mut found = None;
    let mut index = 0;
    while index < args.len() {
        if flags.iter().any(|flag| args[index] == *flag) {
            if found.is_some() {
                return Err(format!("{} can only be provided once", flags.join("/")));
            }
            if index + 1 >= args.len() {
                return Err(format!("missing value for {}", args[index]));
            }
            let value = args.remove(index + 1);
            args.remove(index);
            found = Some(value);
        } else {
            index += 1;
        }
    }
    Ok(found)
}

fn active_rho_identity() -> Option<String> {
    env::var("RHO_IDENTITY")
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
        .and_then(|value| normalize_actor_id(&value).ok())
}

fn rho_home() -> PathBuf {
    env::var("RHO_HOME")
        .ok()
        .filter(|value| !value.is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(default_rho_home)
}

fn mapped_commit_identity() -> Option<(String, String, String)> {
    let identity = active_rho_identity()?;
    let handle = github_handle(&identity).ok()?;
    let path = rho_home()
        .join("identities")
        .join("github")
        .join(format!("{handle}.yaml"));
    let text = fs::read_to_string(path).ok()?;
    let manifest: LocalIdentityManifest = from_yaml(&text).ok()?;
    let git = manifest.local_identity.git?;
    Some((
        git.commit_name,
        git.commit_email,
        format!("rho identity {identity} / gh {}", git.github_login),
    ))
}

fn commit_identity() -> (String, String, String) {
    if let Some(mapped) = mapped_commit_identity() {
        return mapped;
    }
    let (login, id) = gh_user();
    (
        login.clone(),
        format!("{id}+{login}@users.noreply.github.com"),
        format!("active gh account {login}"),
    )
}

fn handle_gh(args: &[String]) {
    let Some(command) = args.first().map(String::as_str) else {
        gh_usage();
    };
    match command {
        "switch" => {
            let Some(login) = args.get(1) else {
                gh_usage();
            };
            let status = Command::new("gh")
                .args(["auth", "switch", "-u", login])
                .status()
                .unwrap_or_else(|error| {
                    eprintln!("failed to run gh auth switch: {error}");
                    std::process::exit(1);
                });
            if !status.success() {
                std::process::exit(status.code().unwrap_or(1));
            }
            let (active_login, id) = gh_user();
            println!("github active: {active_login}");
            println!("github noreply: {id}+{active_login}@users.noreply.github.com");
        }
        "status" => {
            let status = Command::new("gh")
                .args(["auth", "status"])
                .status()
                .unwrap_or_else(|error| {
                    eprintln!("failed to run gh auth status: {error}");
                    std::process::exit(1);
                });
            std::process::exit(status.code().unwrap_or(1));
        }
        "whoami" => {
            let (login, id) = gh_user();
            println!("{login}");
            println!("{id}+{login}@users.noreply.github.com");
        }
        _ => gh_usage(),
    }
}

fn handle_commit(args: &[String]) {
    let mut git_args = args.to_vec();
    let require_identity_match = remove_bool_flag(&mut git_args, "--require-identity-match");
    let root = take_value_flag(&mut git_args, &["-C", "--root"]).unwrap_or_else(|error| {
        eprintln!("{error}");
        std::process::exit(2);
    });
    match active_rho_identity() {
        Some(identity) => match try_gh_login() {
            Ok(active_login) => match identity_login_mismatch(&identity, &active_login) {
                Ok(Some(warning)) => {
                    eprintln!("{warning}");
                    if require_identity_match {
                        std::process::exit(1);
                    }
                }
                Ok(None) => {}
                Err(error) => {
                    if require_identity_match {
                        eprintln!("{error}");
                        std::process::exit(1);
                    }
                }
            },
            Err(error) => {
                if require_identity_match {
                    eprintln!("could not verify active gh account: {error}");
                    std::process::exit(1);
                }
            }
        },
        None => {
            if require_identity_match {
                eprintln!("--require-identity-match requires RHO_IDENTITY");
                std::process::exit(1);
            }
        }
    }
    let (name, email, source) = commit_identity();
    eprintln!("rho commit author: {name} <{email}> ({source})");
    let mut command = Command::new("git");
    if let Some(root) = root {
        command.arg("-C").arg(root);
    }
    command
        .arg("commit")
        .args(&git_args)
        .env("GIT_AUTHOR_NAME", &name)
        .env("GIT_AUTHOR_EMAIL", &email)
        .env("GIT_COMMITTER_NAME", &name)
        .env("GIT_COMMITTER_EMAIL", &email)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit());
    let status = command.status().unwrap_or_else(|error| {
        eprintln!("failed to run git commit: {error}");
        std::process::exit(1);
    });
    std::process::exit(status.code().unwrap_or(1));
}

fn github_handle(identity: &str) -> Result<String, String> {
    let canonical = normalize_actor_id(identity).map_err(|error| error.to_string())?;
    let Some(handle) = canonical.strip_prefix("rho://id/github/") else {
        return Err(format!("unsupported identity id: {canonical}"));
    };
    if handle.is_empty()
        || !handle
            .chars()
            .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-')
    {
        return Err(format!("invalid github identity handle: {handle}"));
    }
    Ok(handle.to_string())
}

fn normalize_github_identity(identity: &str) -> Result<(String, String), String> {
    let canonical = normalize_actor_id(identity).map_err(|error| error.to_string())?;
    let handle = github_handle(&canonical)?;
    Ok((canonical, handle))
}

fn resolve_profile_identity(profile: &str) -> Result<String, String> {
    if profile.starts_with("rho://id/") || profile.contains('/') {
        return normalize_actor_id(profile).map_err(|error| error.to_string());
    }
    let matches = matching_profile_identities(profile);
    match matches.as_slice() {
        [] => normalize_actor_id(profile).map_err(|error| error.to_string()),
        [identity] => Ok(identity.clone()),
        _ => Err(format!(
            "ambiguous profile {profile}: {}; use a provider-qualified identity like github/{profile}",
            matches.join(", ")
        )),
    }
}

fn matching_profile_identities(handle: &str) -> Vec<String> {
    let mut matches = Vec::new();
    collect_profile_identities(&rho_home().join("identities"), handle, true, &mut matches);
    collect_profile_identities(&rho_home().join("peers"), handle, false, &mut matches);
    matches.sort();
    matches.dedup();
    matches
}

fn collect_profile_identities(root: &Path, handle: &str, local: bool, matches: &mut Vec<String>) {
    let Ok(providers) = fs::read_dir(root) else {
        return;
    };
    for provider in providers.flatten() {
        let path = provider.path().join(format!("{handle}.yaml"));
        if !path.is_file() {
            continue;
        }
        let Ok(text) = fs::read_to_string(path) else {
            continue;
        };
        let identity = if local {
            from_yaml::<LocalIdentityManifest>(&text)
                .map(|manifest| manifest.local_identity.identity.id)
        } else {
            from_yaml::<IdentityBundleManifest>(&text).map(|manifest| manifest.identity.id)
        };
        if let Ok(identity) = identity {
            matches.push(identity);
        }
    }
}

#[derive(Debug, Default, PartialEq, Eq)]
struct GlobalOptions {
    profile: Option<String>,
    identity: Option<String>,
    home: Option<PathBuf>,
}

fn parse_global_options(args: &mut Vec<String>) -> Result<GlobalOptions, String> {
    let mut options = GlobalOptions::default();
    let mut rest_start = 0usize;
    while rest_start < args.len() {
        match args[rest_start].as_str() {
            "--profile" => {
                let Some(value) = args.get(rest_start + 1) else {
                    return Err("missing value after --profile".to_string());
                };
                options.profile = Some(value.clone());
                rest_start += 2;
            }
            "--identity" => {
                let Some(value) = args.get(rest_start + 1) else {
                    return Err("missing value after --identity".to_string());
                };
                options.identity = Some(value.clone());
                rest_start += 2;
            }
            "--home" => {
                let Some(value) = args.get(rest_start + 1) else {
                    return Err("missing value after --home".to_string());
                };
                options.home = Some(PathBuf::from(value));
                rest_start += 2;
            }
            "--" => {
                rest_start += 1;
                break;
            }
            _ => break,
        }
    }
    args.drain(0..rest_start);
    Ok(options)
}

fn set_process_env(key: &str, value: impl AsRef<OsStr>) {
    // Rho applies global CLI selectors before command dispatch and before
    // spawning worker threads, so process environment mutation is scoped to
    // this single command invocation.
    unsafe {
        env::set_var(key, value);
    }
}

fn apply_global_options(options: GlobalOptions) -> Result<(), String> {
    if options.profile.is_some() && options.identity.is_some() {
        return Err("--profile and --identity cannot be used together".to_string());
    }
    match (options.profile, options.identity) {
        (Some(profile), None) => {
            let identity = resolve_profile_identity(&profile)?;
            let handle = github_handle(&identity)?;
            set_process_env("RHO_IDENTITY", &identity);
            set_process_env("RHO_ENV_HANDLE", &handle);
            switch_gh_account(&handle);
            if let Some(home) = options.home {
                set_process_env("RHO_HOME", home);
            }
        }
        (None, Some(identity_arg)) => {
            let (identity, handle) = normalize_github_identity(&identity_arg)?;
            set_process_env("RHO_IDENTITY", &identity);
            set_process_env("RHO_ENV_HANDLE", &handle);
            switch_gh_account(&handle);
            if let Some(home) = options.home {
                set_process_env("RHO_HOME", home);
            }
        }
        (None, None) => {
            if let Some(home) = options.home {
                set_process_env("RHO_HOME", home);
            }
        }
        (Some(_), Some(_)) => unreachable!(),
    }
    Ok(())
}

fn switch_gh_account(handle: &str) {
    if let Ok(active) = try_gh_login()
        && active == handle
    {
        return;
    }
    let output = Command::new("gh")
        .args(["auth", "switch", "-u", handle])
        .output();
    match output {
        Ok(output) if output.status.success() => {}
        Ok(output) => {
            let stderr = String::from_utf8_lossy(&output.stderr);
            eprintln!(
                "rho warning: could not switch gh account to {handle}: {}",
                stderr.trim()
            );
        }
        Err(error) => {
            eprintln!("rho warning: could not run gh auth switch -u {handle}: {error}");
        }
    }
}

fn shell_quote(value: &str) -> String {
    format!("'{}'", value.replace('\'', "'\"'\"'"))
}

fn default_rho_home() -> PathBuf {
    env::var("RHO_ENV_HOME_ROOT")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            env::var("HOME")
                .map(PathBuf::from)
                .unwrap_or_else(|_| PathBuf::from("."))
                .join(".rho")
        })
}

fn env_usage() -> ! {
    eprintln!(
        "usage:\n  rho env switch <rho-id> [--home <path>] [--no-prompt]\n  rho env clear [--no-prompt]\n  rho env status\n  rho env shell <rho-id> [--home <path>]"
    );
    std::process::exit(2);
}

fn arg_value(args: &[String], flag: &str) -> Option<String> {
    args.windows(2)
        .find(|window| window[0] == flag)
        .map(|window| window[1].clone())
}

fn has_flag(args: &[String], flag: &str) -> bool {
    args.iter().any(|arg| arg == flag)
}

fn handle_env(args: &[String]) {
    let Some(command) = args.first().map(String::as_str) else {
        env_usage();
    };
    match command {
        "switch" => {
            let identity = args.get(1).unwrap_or_else(|| env_usage()).clone();
            let (identity, handle) = normalize_github_identity(&identity).unwrap_or_else(|error| {
                eprintln!("{error}");
                std::process::exit(1);
            });
            let home = arg_value(args, "--home")
                .map(PathBuf::from)
                .unwrap_or_else(default_rho_home);
            if let Ok(active_login) = try_gh_login()
                && let Ok(Some(warning)) = identity_login_mismatch(&identity, &active_login)
            {
                println!("echo {} >&2", shell_quote(&warning));
            }
            println!("export RHO_IDENTITY={}", shell_quote(&identity));
            println!(
                "export RHO_HOME={}",
                shell_quote(&home.display().to_string())
            );
            println!("export RHO_ENV_HANDLE={}", shell_quote(&handle));
            if !has_flag(args, "--no-prompt") {
                println!(
                    "if [ -z \"${{RHO_ENV_OLD_PS1+x}}\" ]; then export RHO_ENV_OLD_PS1=\"${{PS1:-}}\"; fi"
                );
                println!(
                    "export PS1={}\"${{RHO_ENV_OLD_PS1:-}}\"",
                    shell_quote(&format!("[rho:{handle}] "))
                );
            }
            println!(
                "echo {}",
                shell_quote(&format!("rho identity active: {identity}"))
            );
            println!(
                "echo {}",
                shell_quote(&format!("rho home: {}", home.display()))
            );
        }
        "clear" => {
            println!("unset RHO_IDENTITY");
            println!("unset RHO_ENV_HANDLE");
            println!("unset RHO_HOME");
            if !has_flag(args, "--no-prompt") {
                println!(
                    "if [ -n \"${{RHO_ENV_OLD_PS1+x}}\" ]; then export PS1=\"$RHO_ENV_OLD_PS1\"; unset RHO_ENV_OLD_PS1; fi"
                );
            }
            println!("echo 'rho identity cleared'");
        }
        "status" => {
            println!(
                "RHO_IDENTITY={}",
                env::var("RHO_IDENTITY").unwrap_or_default()
            );
            println!("RHO_HOME={}", env::var("RHO_HOME").unwrap_or_default());
            println!(
                "RHO_ENV_HANDLE={}",
                env::var("RHO_ENV_HANDLE").unwrap_or_default()
            );
        }
        "shell" => {
            let identity = args.get(1).unwrap_or_else(|| env_usage()).clone();
            let (identity, handle) = normalize_github_identity(&identity).unwrap_or_else(|error| {
                eprintln!("{error}");
                std::process::exit(1);
            });
            let home = arg_value(args, "--home")
                .map(PathBuf::from)
                .unwrap_or_else(default_rho_home);
            let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
            let status = Command::new(shell)
                .env("RHO_IDENTITY", identity)
                .env("RHO_HOME", home)
                .env("RHO_ENV_HANDLE", &handle)
                .env(
                    "PS1",
                    format!("[rho:{handle}] {}", env::var("PS1").unwrap_or_default()),
                )
                .status()
                .unwrap_or_else(|error| {
                    eprintln!("failed to start shell: {error}");
                    std::process::exit(1);
                });
            std::process::exit(status.code().unwrap_or(1));
        }
        _ => env_usage(),
    }
}

fn shell_profile_path(args: &[String]) -> PathBuf {
    if let Some(path) = arg_value(args, "--file") {
        return PathBuf::from(path);
    }
    let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
    let shell = env::var("SHELL").unwrap_or_default();
    if shell.contains("zsh") {
        PathBuf::from(home).join(".zshrc")
    } else {
        PathBuf::from(home).join(".bashrc")
    }
}

fn install_shell_block(bin_dir: &Path) -> String {
    format!(
        "# >>> rho shell integration >>>\n\
         case \":$PATH:\" in\n\
         *:{}:*) ;;\n\
         *) export PATH={}:$PATH ;;\n\
         esac\n\
         rho-use() {{ eval \"$(rho env switch \"$@\")\"; }}\n\
         rho-clear() {{ eval \"$(rho env clear)\"; }}\n\
         # <<< rho shell integration <<<\n",
        bin_dir.display(),
        shell_quote(&bin_dir.display().to_string())
    )
}

fn replace_marked_block(existing: &str, block: &str) -> String {
    let start = "# >>> rho shell integration >>>";
    let end = "# <<< rho shell integration <<<";
    if let Some(start_index) = existing.find(start)
        && let Some(end_relative) = existing[start_index..].find(end)
    {
        let end_index = start_index + end_relative + end.len();
        let mut updated = String::new();
        updated.push_str(existing[..start_index].trim_end());
        updated.push_str("\n\n");
        updated.push_str(block.trim_end());
        updated.push('\n');
        updated.push_str(existing[end_index..].trim_start_matches('\n'));
        return updated;
    }
    let mut updated = existing.trim_end().to_string();
    if !updated.is_empty() {
        updated.push_str("\n\n");
    }
    updated.push_str(block);
    updated
}

fn handle_install_shell(args: &[String]) {
    if has_flag(args, "--help") || has_flag(args, "-h") {
        eprintln!("usage: rho install-shell [--file <shell-rc>] [--print]");
        return;
    }
    let exe = env::current_exe().unwrap_or_else(|_| PathBuf::from("rho"));
    let bin_dir = exe.parent().unwrap_or_else(|| Path::new("."));
    let block = install_shell_block(bin_dir);
    if has_flag(args, "--print") {
        print!("{block}");
        return;
    }
    let profile = shell_profile_path(args);
    let existing = fs::read_to_string(&profile).unwrap_or_default();
    let updated = replace_marked_block(&existing, &block);
    if let Some(parent) = profile.parent()
        && let Err(error) = fs::create_dir_all(parent)
    {
        eprintln!("failed to create {}: {error}", parent.display());
        std::process::exit(1);
    }
    if let Err(error) = fs::write(&profile, updated) {
        eprintln!("failed to update {}: {error}", profile.display());
        std::process::exit(1);
    }
    println!("installed rho shell integration: {}", profile.display());
    println!("restart your shell or run: source {}", profile.display());
    println!("helpers: rho-use <rho-id>, rho-clear, rho env status");
}

fn main() {
    let mut args: Vec<String> = env::args().skip(1).collect();
    let global_options = parse_global_options(&mut args).unwrap_or_else(|error| {
        eprintln!("{error}");
        std::process::exit(2);
    });
    apply_global_options(global_options).unwrap_or_else(|error| {
        eprintln!("{error}");
        std::process::exit(2);
    });
    let Some(command) = args.first().cloned() else {
        usage();
    };
    match command.as_str() {
        "--version" | "-V" | "version" => {
            println!("rho {}", env!("CARGO_PKG_VERSION"));
        }
        "--help" | "-h" => usage(),
        "env" => {
            args.remove(0);
            handle_env(&args);
        }
        "install-shell" => {
            args.remove(0);
            handle_install_shell(&args);
        }
        "gh" => {
            args.remove(0);
            handle_gh(&args);
        }
        "commit" => {
            args.remove(0);
            handle_commit(&args);
        }
        "add" => {
            args.remove(0);
            match args.first().map(String::as_str) {
                Some("user") => {
                    args[0] = "add-user".to_string();
                    run_subcommand(commands::repo::run, &args);
                }
                _ => usage(),
            }
        }
        "dataset" => {
            args.remove(0);
            run_subcommand(commands::dataset::run, &args);
        }
        "publish" => {
            args.remove(0);
            run_subcommand(commands::publish::run, &args);
        }
        "message" => {
            args.remove(0);
            run_subcommand(commands::message::run, &args);
        }
        "request" => {
            args.remove(0);
            run_subcommand(commands::request::run, &args);
        }
        "tools" => {
            args.remove(0);
            run_subcommand(commands::tools::run, &args);
        }
        "run" => {
            args.remove(0);
            run_subcommand(commands::run::run, &args);
        }
        "approve" => {
            args[0] = "approve".to_string();
            run_subcommand(commands::request::run, &args);
        }
        "result" => {
            args.remove(0);
            run_subcommand(commands::result::run, &args);
        }
        "id" => {
            args.remove(0);
            run_subcommand(commands::id::run, &args);
        }
        "crypto" => {
            args.remove(0);
            run_subcommand(commands::crypto::run, &args);
        }
        "repo" => {
            args.remove(0);
            run_subcommand(commands::repo::run, &args);
        }
        "status" => {
            args[0] = "status".to_string();
            run_subcommand(commands::repo::run, &args);
        }
        _ => usage(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_leading_global_profile_flags() {
        let mut args = vec![
            "--profile".to_string(),
            "madhavajay".to_string(),
            "--home".to_string(),
            "/tmp/rho-home".to_string(),
            "repo".to_string(),
            "doctor".to_string(),
        ];
        let options = parse_global_options(&mut args).unwrap();
        assert_eq!(
            options,
            GlobalOptions {
                profile: Some("madhavajay".to_string()),
                identity: None,
                home: Some(PathBuf::from("/tmp/rho-home")),
            }
        );
        assert_eq!(args, vec!["repo".to_string(), "doctor".to_string()]);
    }

    #[test]
    fn rejects_profile_and_identity_together() {
        let error = apply_global_options(GlobalOptions {
            profile: Some("madhavajay".to_string()),
            identity: Some("github/madhavajay".to_string()),
            home: None,
        })
        .unwrap_err();
        assert_eq!(error, "--profile and --identity cannot be used together");
    }

    #[test]
    fn resolves_explicit_github_profile() {
        assert_eq!(
            resolve_profile_identity("github/madhavajay").unwrap(),
            "rho://id/github/madhavajay"
        );
        assert_eq!(
            resolve_profile_identity("rho://id/github/madhavajay").unwrap(),
            "rho://id/github/madhavajay"
        );
    }

    #[test]
    fn collects_ambiguous_plain_profile_matches() {
        let root = env::temp_dir().join(format!(
            "rho-profile-test-{}-{}",
            std::process::id(),
            rho_core::uuid_like()
        ));
        let github = root.join("github");
        let other = root.join("x");
        fs::create_dir_all(&github).unwrap();
        fs::create_dir_all(&other).unwrap();
        fs::write(
            github.join("madhavajay.yaml"),
            "version: 1\nidentity:\n  id: rho://id/github/madhavajay\n  kind: github\n  handle: madhavajay\n  display_name: Madhava\n  public_keys: []\n  proofs: []\n  created_at: \"2026-06-16T00:00:00Z\"\n",
        )
        .unwrap();
        fs::write(
            other.join("madhavajay.yaml"),
            "version: 1\nidentity:\n  id: rho://id/x/madhavajay\n  kind: x\n  handle: madhavajay\n  display_name: Madhava\n  public_keys: []\n  proofs: []\n  created_at: \"2026-06-16T00:00:00Z\"\n",
        )
        .unwrap();

        let mut matches = Vec::new();
        collect_profile_identities(&root, "madhavajay", false, &mut matches);
        matches.sort();
        assert_eq!(
            matches,
            vec![
                "rho://id/github/madhavajay".to_string(),
                "rho://id/x/madhavajay".to_string()
            ]
        );
        fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn detects_identity_login_mismatch() {
        assert!(matches!(
            identity_login_mismatch("rho://id/github/madhavajay", "madhavajay"),
            Ok(None)
        ));
        let warning = identity_login_mismatch("github/madhavajay", "madhavajay-test")
            .unwrap()
            .unwrap();
        assert!(warning.contains("active gh account is madhavajay-test"));
        assert!(warning.contains("RHO_IDENTITY is rho://id/github/madhavajay"));
    }

    #[test]
    fn removes_commit_identity_match_flag_before_forwarding_to_git() {
        let mut args = vec![
            "--require-identity-match".to_string(),
            "-m".to_string(),
            "message".to_string(),
        ];
        assert!(remove_bool_flag(&mut args, "--require-identity-match"));
        assert_eq!(args, vec!["-m".to_string(), "message".to_string()]);
        assert!(!remove_bool_flag(&mut args, "--require-identity-match"));
    }

    #[test]
    fn extracts_commit_root_flags_before_forwarding_to_git() {
        let mut args = vec![
            "-C".to_string(),
            "/tmp/project".to_string(),
            "-m".to_string(),
            "message".to_string(),
        ];
        assert_eq!(
            take_value_flag(&mut args, &["-C", "--root"]).unwrap(),
            Some("/tmp/project".to_string())
        );
        assert_eq!(args, vec!["-m".to_string(), "message".to_string()]);

        let mut args = vec![
            "--root".to_string(),
            "/tmp/project".to_string(),
            "--allow-empty".to_string(),
        ];
        assert_eq!(
            take_value_flag(&mut args, &["-C", "--root"]).unwrap(),
            Some("/tmp/project".to_string())
        );
        assert_eq!(args, vec!["--allow-empty".to_string()]);
    }

    #[test]
    fn rejects_invalid_commit_root_flags() {
        let mut missing = vec!["-C".to_string()];
        assert!(take_value_flag(&mut missing, &["-C", "--root"]).is_err());

        let mut duplicate = vec![
            "-C".to_string(),
            "/tmp/one".to_string(),
            "--root".to_string(),
            "/tmp/two".to_string(),
        ];
        assert!(take_value_flag(&mut duplicate, &["-C", "--root"]).is_err());
    }
}
