//! `pic admins` smoke tests. Verifies the CRUD round trip and the
//! capability gate (a Member account can't list/create admins).

use predicates::prelude::*;

use crate::common;
use crate::common::cleanup::UserGuard;

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_show_list_patch_remove_round_trip() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let username = common::unique_username("rtadm");

    // Create — password piped via stdin.
    let out = common::pic_as(&env)
        .args([
            "admins",
            "create",
            &username,
            "--password",
            "-",
            "--instance-role",
            "admin",
            "--email",
            "admin@example.test",
        ])
        .write_stdin("pw-from-stdin\n")
        .output()
        .expect("admins create");
    assert!(out.status.success(), "admins create failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let id = stdout
        .lines()
        .find_map(|l| l.strip_prefix("id"))
        .map(|s| s.trim().to_string())
        .expect("id field in admins create output");
    let _guard = UserGuard::new(&env.url, &env.token, &id);

    // List — our new admin appears with the right role.
    let out = common::pic_as(&env)
        .args(["admins", "ls"])
        .output()
        .expect("admins ls");
    assert!(out.status.success(), "admins ls failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let header = stdout.lines().next().expect("header");
    assert_eq!(
        common::cells(header),
        vec![
            "id",
            "username",
            "role",
            "active",
            "email",
            "created_at",
            "last_login_at"
        ]
    );
    let row = stdout
        .lines()
        .skip(1)
        .map(common::cells)
        .find(|c| c.get(1).copied() == Some(username.as_str()))
        .unwrap_or_else(|| panic!("{username} missing from admins ls: {stdout}"));
    assert_eq!(row[2], "admin");
    assert_eq!(row[3], "true");

    // Show — KvBlock includes the username row.
    common::pic_as(&env)
        .args(["admins", "show", &id])
        .assert()
        .success()
        .stdout(predicate::str::contains(&username));

    // Patch — deactivate.
    common::pic_as(&env)
        .args(["admins", "set", &id, "--active", "false"])
        .assert()
        .success();

    // Patch result echoes is_active=false.
    let out = common::pic_as(&env)
        .args(["admins", "show", &id])
        .output()
        .expect("admins show after patch");
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout
            .lines()
            .any(|l| l.starts_with("is_active") && l.trim_end().ends_with("false")),
        "patch should have deactivated: {stdout}"
    );

    // Remove.
    common::pic_as(&env)
        .args(["admins", "rm", &id])
        .assert()
        .success()
        .stdout(predicate::str::contains(format!("Deleted admin {id}")));
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_without_password_errors() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let username = common::unique_username("nopw");

    common::pic_as(&env)
        .args(["admins", "create", &username])
        .assert()
        .failure()
        .stderr(predicate::str::contains("missing --password"));
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_with_inline_password_is_rejected() {
    // Audit 2026-06-11 (H2) — an inline password on argv leaks into
    // shell history / ps / proc; only `--password -` (stdin) is allowed.
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let username = common::unique_username("inlinepw");

    common::pic_as(&env)
        .args(["admins", "create", &username, "--password", "hunter2"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("inline is not allowed"));
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn member_cannot_list_admins() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let admin_env = common::admin_env(fx);
    let m = common::member::member_user(fx, &common::unique_username("nomgmt"));
    let member_env = common::custom_env(&fx.url, &m.token);
    common::seed_credentials(&member_env, &m.username);

    common::pic_as(&member_env)
        .args(["admins", "ls"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("HTTP 403"));
    // Belt and suspenders so the unused `admin_env` doesn't warn.
    let _ = admin_env;
}
