//! `pic api-keys` — mint / ls / rm journeys.
//!
//! Server semantics asserted here:
//!   * `mint` emits the `raw_token` *exactly once* and never on `ls`.
//!   * A minted key is a valid bearer for `/auth/me`.
//!   * After `rm`, the same token is rejected (401).

use predicates::prelude::*;
use serde_json::Value;

use crate::common;

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn mint_prints_raw_token_once_and_ls_omits_it() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let name = format!("pic-cli-mint-{}", common::unique_slug("k"));

    let out = common::pic_as(&env)
        .args([
            "--output",
            "json",
            "api-keys",
            "mint",
            &name,
            "--scope",
            "script:read",
        ])
        .output()
        .expect("api-keys mint");
    assert!(out.status.success(), "mint failed: {out:?}");
    let body: Value = serde_json::from_slice(&out.stdout).expect("JSON");
    let token = body["token"]
        .as_str()
        .expect("mint should expose `token`")
        .to_string();
    let key_id = body["id"]
        .as_str()
        .expect("mint should expose `id`")
        .to_string();
    assert!(
        token.starts_with("pic_"),
        "tokens are pic_-prefixed: {token}"
    );

    // `ls` must NEVER carry the raw token. The key row should appear,
    // identified by name, but `token` is mint-only.
    let ls = common::pic_as(&env)
        .args(["--output", "json", "api-keys", "ls"])
        .output()
        .expect("api-keys ls");
    assert!(ls.status.success(), "ls failed: {ls:?}");
    let ls_body: Value = serde_json::from_slice(&ls.stdout).expect("JSON");
    let arr = ls_body.as_array().expect("array");
    let row = arr
        .iter()
        .find(|r| r.get("id").and_then(Value::as_str) == Some(key_id.as_str()))
        .expect("our key in ls");
    assert!(
        row.get("token").is_none(),
        "ls must not expose raw_token: {row}"
    );

    // Cleanup so we don't leak keys across runs.
    common::pic_as(&env)
        .args(["api-keys", "rm", &key_id])
        .assert()
        .success();
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn minted_key_works_as_bearer() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let name = format!("pic-cli-bearer-{}", common::unique_slug("k"));

    let mint = common::pic_as(&env)
        .args([
            "--output",
            "json",
            "api-keys",
            "mint",
            &name,
            "--scope",
            "script:read",
        ])
        .output()
        .expect("mint");
    assert!(mint.status.success());
    let body: Value = serde_json::from_slice(&mint.stdout).unwrap();
    let token = body["token"].as_str().unwrap().to_string();
    let id = body["id"].as_str().unwrap().to_string();

    // Drive whoami with the minted token — proves the bearer string we
    // captured really is what the server stamped.
    let key_env = common::custom_env(&fx.url, &token);
    common::seed_credentials(&key_env, &fx.admin_username);
    common::pic_as(&key_env)
        .args(["whoami"])
        .assert()
        .success()
        .stdout(predicate::str::contains(fx.admin_username.as_str()));

    common::pic_as(&env)
        .args(["api-keys", "rm", &id])
        .assert()
        .success();
}

/// After `rm`, the bearer token is dead server-side: a follow-up
/// `whoami` driven by it must 401, not 500.
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn rm_revokes_the_token() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let name = format!("pic-cli-rm-{}", common::unique_slug("k"));

    let mint = common::pic_as(&env)
        .args([
            "--output",
            "json",
            "api-keys",
            "mint",
            &name,
            "--scope",
            "script:read",
        ])
        .output()
        .expect("mint");
    let body: Value = serde_json::from_slice(&mint.stdout).unwrap();
    let token = body["token"].as_str().unwrap().to_string();
    let id = body["id"].as_str().unwrap().to_string();

    common::pic_as(&env)
        .args(["api-keys", "rm", &id])
        .assert()
        .success()
        .stdout(predicate::str::contains(format!("Revoked api-key {id}")));

    let dead = common::custom_env(&fx.url, &token);
    common::pic_as(&dead)
        .args(["whoami"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("HTTP 401"));
}

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

    common::pic_as(&env)
        .args(["api-keys", "mint", "doomed", "--scope", "script:nope"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("unknown scope"));
}
