mod common;

use common::TestVault;

#[test]
fn edit_password_captures_history() {
    let v = TestVault::init();
    v.run(&["add", "login", "--title", "bank",
           "--username", "u", "--password", "first-pw"]);

    // edit: accept defaults on title/group/tags/username/url, then change pw.
    let out = run_edit_with_pw_change(&v, "bank", "second-pw");
    assert!(out.status.success(), "edit failed:\nstdout: {}\nstderr: {}",
            String::from_utf8_lossy(&out.stdout),
            String::from_utf8_lossy(&out.stderr));

    // Verify the edit commit exists in git log.
    let log = std::process::Command::new("git")
        .current_dir(v.path()).args(["log", "--oneline"])
        .output().unwrap();
    let log_str = String::from_utf8(log.stdout).unwrap();
    assert!(log_str.contains("edit: bank"), "missing edit commit: {log_str}");

    // And the item file has been re-written (there's a single items/<id>.enc).
    let items_dir = v.path().join("items");
    let entries: Vec<_> = std::fs::read_dir(&items_dir).unwrap()
        .map(|e| e.unwrap().path()).collect();
    assert_eq!(entries.len(), 1);
}

/// Drives the interactive `edit` flow end-to-end:
/// 1. passphrase via env var.
/// 2. blank lines for title, group, tags, username, url.
/// 3. "y" for "Change password?"
/// 4. new password via RELICARIO_TEST_ITEM_SECRET env var.
fn run_edit_with_pw_change(v: &TestVault, query: &str, new_pw: &str) -> std::process::Output {
    use assert_cmd::cargo::CommandCargoExt;
    use std::io::Write;
    use std::process::{Command, Stdio};

    let mut cmd = Command::cargo_bin("relicario").unwrap();
    cmd.current_dir(v.path())
        .env("RELICARIO_IMAGE", &v.reference_image)
        .env("RELICARIO_TEST_PASSPHRASE", &v.passphrase)
        .env("RELICARIO_TEST_ITEM_SECRET", new_pw)
        .args(["edit", query])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    let mut child = cmd.spawn().unwrap();
    {
        let stdin = child.stdin.as_mut().unwrap();
        // title, group, tags, username, url (keep defaults), then yes-to-change-pw.
        for line in ["", "", "", "", "", "y"] {
            writeln!(stdin, "{line}").unwrap();
        }
    }
    child.wait_with_output().unwrap()
}

#[test]
fn history_command_lists_per_field_entries() {
    let v = TestVault::init();
    v.run(&["add", "login", "--title", "bank",
           "--username", "u", "--password", "first-pw"]);
    let out = run_edit_with_pw_change(&v, "bank", "second-pw");
    assert!(out.status.success(), "edit failed: {:?}", out);

    // `history <query>` should list the captured field and a count.
    let out = v.run(&["history", "bank"]);
    assert!(
        out.status.success(),
        "history failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout.contains("login_password"),
        "expected login_password key, got: {stdout}"
    );
    // Default (no --show) hides values.
    assert!(
        !stdout.contains("first-pw"),
        "values should be masked without --show: {stdout}"
    );
    assert!(
        stdout.contains("****"),
        "expected masked value indicator: {stdout}"
    );
}

#[test]
fn history_command_show_reveals_prior_values() {
    let v = TestVault::init();
    v.run(&["add", "login", "--title", "bank",
           "--username", "u", "--password", "first-pw"]);
    let out = run_edit_with_pw_change(&v, "bank", "second-pw");
    assert!(out.status.success());

    let out = v.run(&["history", "bank", "--show"]);
    assert!(out.status.success(), "history --show failed: {:?}", out);
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout.contains("first-pw"),
        "expected old value 'first-pw' in --show output: {stdout}"
    );
}

#[test]
fn history_command_reports_empty_when_nothing_changed() {
    let v = TestVault::init();
    v.run(&["add", "login", "--title", "untouched",
           "--username", "u", "--password", "pw"]);

    let out = v.run(&["history", "untouched"]);
    assert!(out.status.success(), "history failed: {:?}", out);
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout.to_lowercase().contains("no history"),
        "expected 'no history' message, got: {stdout}"
    );
}

#[test]
fn edit_totp_rotates_secret_and_captures_history() {
    let v = TestVault::init();
    v.run(&[
        "add", "totp",
        "--title", "github",
        "--issuer", "github.com",
        "--label", "alice",
        "--secret", "JBSWY3DPEHPK3PXP",
    ]);

    // Edit: change issuer, label, then rotate the secret to a new base32 value.
    let out = run_edit_totp(&v, "github", "github-new.com", "alice@new", "NB2W45DFOIZA");
    assert!(
        out.status.success(),
        "edit failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    // Verify the issuer and label changes persisted by reading the item back.
    let out = v.run(&["get", "github"]);
    assert!(out.status.success(), "get failed: {:?}", out);
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout.contains("github-new.com"),
        "expected new issuer in get output, got: {stdout}"
    );
    assert!(
        stdout.contains("alice@new"),
        "expected new label in get output, got: {stdout}"
    );
}

/// Drives the interactive `edit` flow for a TOTP item with secret rotation.
/// Stdin order: Title, Group, Tags (all blank to keep), Issuer, Label,
/// then "y" to "Change TOTP secret?" The new secret comes from
/// RELICARIO_TEST_ITEM_SECRET.
fn run_edit_totp(
    v: &TestVault,
    query: &str,
    new_issuer: &str,
    new_label: &str,
    new_secret_b32: &str,
) -> std::process::Output {
    use assert_cmd::cargo::CommandCargoExt;
    use std::io::Write;
    use std::process::{Command, Stdio};

    let mut cmd = Command::cargo_bin("relicario").unwrap();
    cmd.current_dir(v.path())
        .env("RELICARIO_IMAGE", &v.reference_image)
        .env("RELICARIO_TEST_PASSPHRASE", &v.passphrase)
        .env("RELICARIO_TEST_ITEM_SECRET", new_secret_b32)
        .args(["edit", query])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    let mut child = cmd.spawn().unwrap();
    {
        let stdin = child.stdin.as_mut().unwrap();
        for line in ["", "", "", new_issuer, new_label, "y"] {
            writeln!(stdin, "{line}").unwrap();
        }
    }
    child.wait_with_output().unwrap()
}
