mod common;

use common::TestVault;

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

    let payload_path = v.path().join("payload.txt");
    std::fs::write(&payload_path, b"attached-bytes").unwrap();

    let attach = v.run(&["attach", "thing", payload_path.to_str().unwrap()]);
    assert!(attach.status.success(), "attach failed: {:?}", attach);

    let list = v.run(&["attachments", "thing"]);
    let stdout = String::from_utf8(list.stdout).unwrap();
    assert!(stdout.contains("payload.txt"), "missing payload: {stdout}");

    let aid = stdout.lines()
        .find(|l| l.contains("payload.txt"))
        .and_then(|l| l.split_whitespace().next())
        .expect("aid token");

    let out_path = v.path().join("extracted.txt");
    let ex = v.run(&["extract", "thing", aid, "--out", out_path.to_str().unwrap()]);
    assert!(ex.status.success(), "extract failed: {:?}", ex);
    assert_eq!(std::fs::read(out_path).unwrap(), b"attached-bytes");
}

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

    let payload_path = v.path().join("payload.txt");
    std::fs::write(&payload_path, b"attached-bytes").unwrap();
    let attach = v.run(&["attach", "thing", payload_path.to_str().unwrap()]);
    assert!(attach.status.success());

    let list = v.run(&["attachments", "thing"]);
    let stdout = String::from_utf8(list.stdout).unwrap();
    let aid = stdout.lines()
        .find(|l| l.contains("payload.txt"))
        .and_then(|l| l.split_whitespace().next())
        .expect("aid token")
        .to_string();

    // Detach removes the attachment from the item AND deletes the blob.
    let out = v.run(&["detach", "thing", &aid]);
    assert!(
        out.status.success(),
        "detach failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );

    // Item no longer lists the attachment.
    let list2 = v.run(&["attachments", "thing"]);
    let stdout2 = String::from_utf8(list2.stdout).unwrap();
    assert!(
        !stdout2.contains("payload.txt"),
        "attachment still listed after detach: {stdout2}"
    );

    // Encrypted blob file is gone.
    let blob_path = v.path()
        .join("attachments")
        .join("");
    let item_attach_dir = std::fs::read_dir(v.path().join("attachments"))
        .unwrap().next().unwrap().unwrap().path();
    let blob = item_attach_dir.join(format!("{aid}.enc"));
    assert!(!blob.exists(), "blob still on disk: {}", blob.display());
    let _ = blob_path; // keep the variable to avoid an unused warning
}

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

    let out = v.run(&["detach", "thing", "deadbeef"]);
    assert!(!out.status.success(), "expected failure: {:?}", out);
    assert!(
        String::from_utf8_lossy(&out.stderr).to_lowercase().contains("no attachment"),
        "expected 'no attachment' error in stderr"
    );
}

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

    v.run(&["settings", "attachment-cap", "--per-attachment-max-bytes", "10"]);

    let big = v.path().join("big.bin");
    std::fs::write(&big, vec![0u8; 100]).unwrap();
    let out = v.run(&["attach", "thing", big.to_str().unwrap()]);
    assert!(!out.status.success(), "expected failure; got {:?}", out);
    assert!(String::from_utf8(out.stderr).unwrap().to_lowercase().contains("attachment"));
}
