//! tests pos.rs
//! Positive-path integration checks for every sub-command
//! that already has real logic behind it.

mod util;
use util::marlin;

use predicates::{prelude::*, str}; // brings `PredicateBooleanExt::and`
use std::fs;
use tempfile::tempdir;

/* ─────────────────────────── TAG ─────────────────────────────── */

#[test]
fn tag_should_add_hierarchical_tag_and_search_finds_it() {
    let tmp = tempdir().unwrap();
    let file = tmp.path().join("foo.md");
    fs::write(&file, "# test\n").unwrap();

    marlin(&tmp)
        .current_dir(tmp.path())
        .arg("init")
        .assert()
        .success();

    marlin(&tmp)
        .args(["tag", file.to_str().unwrap(), "project/md"])
        .assert()
        .success();

    marlin(&tmp)
        .args(["search", "tag:project/md"])
        .assert()
        .success()
        .stdout(str::contains("foo.md"));
}

/* ─────────────────────────── ATTR ────────────────────────────── */

#[test]
fn attr_set_then_ls_roundtrip() {
    let tmp = tempdir().unwrap();
    let file = tmp.path().join("report.pdf");
    fs::write(&file, "%PDF-1.4\n").unwrap();

    marlin(&tmp)
        .current_dir(tmp.path())
        .arg("init")
        .assert()
        .success();

    marlin(&tmp)
        .args(["attr", "set", file.to_str().unwrap(), "reviewed", "yes"])
        .assert()
        .success();

    marlin(&tmp)
        .args(["attr", "ls", file.to_str().unwrap()])
        .assert()
        .success()
        .stdout(str::contains("reviewed = yes"));
}

/* ─────────────────────── COLLECTIONS ────────────────────────── */

#[test]
fn coll_create_add_and_list() {
    let tmp = tempdir().unwrap();

    let a = tmp.path().join("a.txt");
    let b = tmp.path().join("b.txt");
    fs::write(&a, "").unwrap();
    fs::write(&b, "").unwrap();

    marlin(&tmp)
        .current_dir(tmp.path())
        .arg("init")
        .assert()
        .success();

    marlin(&tmp)
        .args(["coll", "create", "Set"])
        .assert()
        .success();
    for f in [&a, &b] {
        marlin(&tmp)
            .args(["coll", "add", "Set", f.to_str().unwrap()])
            .assert()
            .success();
    }

    marlin(&tmp)
        .args(["coll", "list", "Set"])
        .assert()
        .success()
        .stdout(str::contains("a.txt").and(str::contains("b.txt")));
}

/* ─────────────────────────── VIEWS ───────────────────────────── */

#[test]
fn view_save_list_and_exec() {
    let tmp = tempdir().unwrap();

    let todo = tmp.path().join("TODO.txt");
    fs::write(&todo, "remember the milk\n").unwrap();

    marlin(&tmp)
        .current_dir(tmp.path())
        .arg("init")
        .assert()
        .success();

    // save & list
    marlin(&tmp)
        .args(["view", "save", "tasks", "milk"])
        .assert()
        .success();
    marlin(&tmp)
        .args(["view", "list"])
        .assert()
        .success()
        .stdout(str::contains("tasks: milk"));

    // exec
    marlin(&tmp)
        .args(["view", "exec", "tasks"])
        .assert()
        .success()
        .stdout(str::contains("TODO.txt"));
}

/* ─────────────────────────── LINKS ───────────────────────────── */

#[test]
fn link_add_rm_and_list() {
    let tmp = tempdir().unwrap();

    let foo = tmp.path().join("foo.txt");
    let bar = tmp.path().join("bar.txt");
    fs::write(&foo, "").unwrap();
    fs::write(&bar, "").unwrap();

    // handy closure
    let mc = || marlin(&tmp);

    mc().current_dir(tmp.path()).arg("init").assert().success();
    mc().args(["scan", tmp.path().to_str().unwrap()])
        .assert()
        .success();

    // add
    mc().args(["link", "add", foo.to_str().unwrap(), bar.to_str().unwrap()])
        .assert()
        .success();

    // list (outgoing default)
    mc().args(["link", "list", foo.to_str().unwrap()])
        .assert()
        .success()
        .stdout(str::contains("foo.txt").and(str::contains("bar.txt")));

    // remove
    mc().args(["link", "rm", foo.to_str().unwrap(), bar.to_str().unwrap()])
        .assert()
        .success();

    // list now empty
    mc().args(["link", "list", foo.to_str().unwrap()])
        .assert()
        .success()
        .stdout(str::is_empty());
}

/* ─────────────────────── SCAN (multi-path) ───────────────────── */

#[test]
fn scan_with_multiple_paths_indexes_all() {
    let tmp = tempdir().unwrap();

    let dir_a = tmp.path().join("A");
    let dir_b = tmp.path().join("B");
    std::fs::create_dir_all(&dir_a).unwrap();
    std::fs::create_dir_all(&dir_b).unwrap();
    let f1 = dir_a.join("one.txt");
    let f2 = dir_b.join("two.txt");
    fs::write(&f1, "").unwrap();
    fs::write(&f2, "").unwrap();

    marlin(&tmp)
        .current_dir(tmp.path())
        .arg("init")
        .assert()
        .success();

    // multi-path scan
    marlin(&tmp)
        .args(["scan", dir_a.to_str().unwrap(), dir_b.to_str().unwrap()])
        .assert()
        .success();

    // both files findable
    for term in ["one.txt", "two.txt"] {
        marlin(&tmp)
            .args(["search", term])
            .assert()
            .success()
            .stdout(str::contains(term));
    }
}
