use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;

// Base runner kept as the documented counterpart to relicario_with_git_identity
// below (every test in this file needs the git identity, so only the _with_
// variant is currently called).
#[allow(dead_code)]
fn relicario(config_home: &Path, args: &[&str]) -> std::process::Output {
    Command::new(env!("CARGO_BIN_EXE_relicario"))
        .env("XDG_CONFIG_HOME", config_home)
        .env("HOME", config_home) // belt-and-suspenders for dirs on all platforms
        .args(args)
        .output()
        .expect("run relicario")
}

/// Like relicario() but also injects the git committer identity so that
/// `git commit` inside `org init` doesn't fail with "Please tell me who you are."
fn relicario_with_git_identity(config_home: &Path, args: &[&str]) -> std::process::Output {
    Command::new(env!("CARGO_BIN_EXE_relicario"))
        .env("XDG_CONFIG_HOME", config_home)
        .env("HOME", config_home)
        .env("GIT_AUTHOR_NAME", "Test Device")
        .env("GIT_AUTHOR_EMAIL", "test@relicario.test")
        .env("GIT_COMMITTER_NAME", "Test Device")
        .env("GIT_COMMITTER_EMAIL", "test@relicario.test")
        .args(args)
        .output()
        .expect("run relicario")
}

fn git(repo: &Path, args: &[&str]) -> std::process::Output {
    Command::new("git")
        .current_dir(repo)
        .args(args)
        .output()
        .expect("run git")
}

/// Lay out device keys directly under `<config_home>/relicario/devices/<name>/`
/// and set `devices/current` — mirrors the B2 seed_helper_tests approach.
/// Returns the OpenSSH public key string so the caller can build an allowed_signers
/// file for `git verify-commit`.
fn seed_device(config_home: &Path, name: &str) -> String {
    let (priv_openssh, pub_openssh) =
        relicario_core::device::generate_keypair().expect("generate_keypair");

    let dev_dir = config_home
        .join("relicario")
        .join("devices")
        .join(name);
    fs::create_dir_all(&dev_dir).expect("create device dir");
    let signing_key_path = dev_dir.join("signing.key");
    fs::write(&signing_key_path, priv_openssh.as_str())
        .expect("write signing.key");
    // ssh requires 0600 on private key files or it refuses to use them.
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(&signing_key_path, fs::Permissions::from_mode(0o600))
            .expect("chmod signing.key");
    }
    fs::write(dev_dir.join("signing.pub"), &pub_openssh)
        .expect("write signing.pub");
    // Also write stub deploy key files so configure_git_signing doesn't trip on
    // a missing deploy.key path (the git config value just points to the file;
    // the file itself is never read during org init).
    fs::write(dev_dir.join("deploy.key"), "").expect("write stub deploy.key");
    fs::write(dev_dir.join("deploy.pub"), "").expect("write stub deploy.pub");

    // Set this device as current.
    let devices_dir = config_home.join("relicario").join("devices");
    fs::write(devices_dir.join("current"), format!("{name}\n"))
        .expect("write current");

    pub_openssh
}

#[test]
fn org_init_produces_a_signed_initial_commit() {
    let cfg = TempDir::new().unwrap();
    let org = TempDir::new().unwrap();

    // Lay out the device key directly (no `device add` needed — it requires Gitea).
    let pub_openssh = seed_device(cfg.path(), "test-dev");

    // Initialize the org vault. `--dir` comes AFTER `org init` (B14 global).
    // Inject git identity so the commit doesn't fail "Please tell me who you are."
    let init = relicario_with_git_identity(
        cfg.path(),
        &["org", "init", "--dir", org.path().to_str().unwrap(), "--name", "Acme"],
    );
    assert!(
        init.status.success(),
        "org init failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&init.stdout),
        String::from_utf8_lossy(&init.stderr)
    );

    // The org repo must be configured to sign.
    let cfg_out = git(org.path(), &["config", "commit.gpgsign"]);
    assert_eq!(
        String::from_utf8_lossy(&cfg_out.stdout).trim(),
        "true",
        "org repo must have commit.gpgsign=true"
    );

    // The HEAD commit object must carry a signature header.
    let head = git(org.path(), &["cat-file", "commit", "HEAD"]);
    let body = String::from_utf8_lossy(&head.stdout);
    assert!(
        body.contains("gpgsig "),
        "HEAD commit must be signed (no gpgsig header found):\n{body}"
    );

    // Configure an allowed_signers file so `git verify-commit` can validate the
    // SSH signature. The principal must match the committer email injected above.
    let allowed_signers_path = cfg.path().join("allowed_signers");
    let allowed_line = format!("test@relicario.test {}", pub_openssh.trim());
    fs::write(&allowed_signers_path, format!("{allowed_line}\n"))
        .expect("write allowed_signers");
    git(
        org.path(),
        &[
            "config",
            "gpg.ssh.allowedSignersFile",
            allowed_signers_path.to_str().unwrap(),
        ],
    );

    // Now verify-commit should succeed.
    let verify = git(org.path(), &["verify-commit", "HEAD"]);
    assert!(
        verify.status.success(),
        "git verify-commit HEAD failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&verify.stdout),
        String::from_utf8_lossy(&verify.stderr)
    );

    // The commit body must carry the org-init action trailer.
    let log_out = git(org.path(), &["log", "-1", "--format=%B"]);
    let commit_body = String::from_utf8_lossy(&log_out.stdout);
    assert!(
        commit_body.contains("Relicario-Action: org-init"),
        "HEAD commit body must contain 'Relicario-Action: org-init' trailer:\n{commit_body}"
    );
}
