mod common;

use common::TestVault;

#[test]
fn settings_roundtrip_trash_retention() {
    let v = TestVault::init();
    let out = v.run(&["settings", "show"]);
    assert!(String::from_utf8(out.stdout).unwrap().contains("trash_retention"));

    let out = v.run(&["settings", "trash-retention", "--days", "60"]);
    assert!(out.status.success(), "set failed: {:?}", out);
    let out = v.run(&["settings", "show"]);
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(stdout.contains("60"), "expected 60: {stdout}");
}

#[test]
fn settings_rejects_conflicting_retention_flags() {
    let v = TestVault::init();
    let out = v.run(&["settings", "trash-retention", "--days", "30", "--forever"]);
    assert!(!out.status.success());
}

#[test]
fn generate_uses_vault_default_length() {
    let v = TestVault::init();

    // Default vault settings: GeneratorRequest::Random { length: 20, ... }.
    let out = v.run(&["generate"]);
    assert!(out.status.success(), "{:?}", String::from_utf8_lossy(&out.stderr));
    let pw = String::from_utf8(out.stdout).unwrap();
    assert_eq!(
        pw.trim().chars().count(),
        20,
        "expected 20 chars at default, got {pw:?}"
    );

    // Update the vault default length to 32.
    let out = v.run(&["settings", "generator-defaults", "--length", "32"]);
    assert!(
        out.status.success(),
        "set generator-defaults failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    // `generate` (no flags) should now produce 32 chars.
    let out = v.run(&["generate"]);
    assert!(out.status.success(), "{:?}", String::from_utf8_lossy(&out.stderr));
    let pw = String::from_utf8(out.stdout).unwrap();
    assert_eq!(
        pw.trim().chars().count(),
        32,
        "expected 32 chars after update, got {pw:?}"
    );

    // Explicit flag overrides the vault default.
    let out = v.run(&["generate", "--length", "8"]);
    assert!(out.status.success());
    let pw = String::from_utf8(out.stdout).unwrap();
    assert_eq!(
        pw.trim().chars().count(),
        8,
        "explicit flag should override vault default, got {pw:?}"
    );
}

#[test]
fn status_reports_item_and_attachment_counts() {
    let v = TestVault::init();
    v.run(&["add", "login", "--title", "active",
           "--username", "u", "--password", "p"]);
    v.run(&["add", "login", "--title", "to-trash",
           "--username", "u", "--password", "p"]);
    v.run(&["rm", "to-trash"]);

    let payload = v.path().join("payload.txt");
    std::fs::write(&payload, b"hello-world").unwrap();
    v.run(&["attach", "active", payload.to_str().unwrap()]);

    let out = v.run(&["status"]);
    assert!(
        out.status.success(),
        "status failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );
    let stdout = String::from_utf8(out.stdout).unwrap();
    let lower = stdout.to_lowercase();

    // 1 active + 1 trashed = 2 items total.
    assert!(lower.contains("items"), "missing items section: {stdout}");
    assert!(stdout.contains('2') || stdout.contains("2 ")
            || lower.contains("active: 1") || lower.contains("1 active"),
            "expected item counts in output: {stdout}");
    assert!(lower.contains("trash"), "missing trash count: {stdout}");

    // 1 attachment, 11 bytes.
    assert!(lower.contains("attachment"), "missing attachment section: {stdout}");
    assert!(stdout.contains("11"), "expected 11-byte size in output: {stdout}");

    // device count line removed — device key system was security theater (audit B1).

    // Last-commit line.
    assert!(
        lower.contains("last commit") || lower.contains("commit"),
        "missing last-commit info: {stdout}",
    );
}

#[test]
fn status_shows_last_backup_line() {
    let v = TestVault::init();
    let out = v.run(&["status"]);
    assert!(out.status.success());
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(stdout.contains("Last export:"), "missing last export line: {stdout}");
    assert!(stdout.contains("never"), "fresh vault should report 'never': {stdout}");
}

#[test]
fn status_shows_recent_backup_after_export() {
    let v = TestVault::init();
    let backup_path = v.path().join("v.relbak");
    v.run_with_backup_pass(
        &["backup", "export", backup_path.to_str().unwrap()],
        "test-backup-pass-2026",
    );
    let out = v.run(&["status"]);
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(stdout.contains("Last export:"), "{stdout}");
    assert!(!stdout.contains("never"), "should NOT say 'never' after export: {stdout}");
}

#[test]
fn generate_works_outside_vault() {
    use assert_cmd::cargo::CommandCargoExt;
    use std::process::{Command, Stdio};
    use tempfile::TempDir;

    let tmp = TempDir::new().unwrap();
    let out = Command::cargo_bin("relicario")
        .unwrap()
        .current_dir(tmp.path())
        .args(["generate", "--length", "12"])
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();
    assert!(
        out.status.success(),
        "no-vault generate failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let pw = String::from_utf8(out.stdout).unwrap();
    assert_eq!(pw.trim().chars().count(), 12);
}
