#!/usr/bin/env bash

# `mise x` gets secrets only when asked, and never as files.

mkdir -p "$HOME/bin"
printf '#!/usr/bin/env bash\nexec python3 "%s" "$@"\n' "$TEST_ROOT/helpers/scripts/fnox_stub.py" >"$HOME/bin/fnox"
chmod +x "$HOME/bin/fnox"
export PATH="$HOME/bin:$PATH"
export FNOX_STUB_LOG="$PWD/fnox-calls.jsonl"

cat >mise.toml <<'TOML'
[secrets.fnox]
TOML

assert_succeed "mise x --secrets DEPLOY_KEY -- sh -c 'test \"\$DEPLOY_KEY\" = deploy-s3cr3t-0001'"
assert "jq -r 'select(.argv|index(\"--keys\"))|.argv[-1]' fnox-calls.jsonl" "DEPLOY_KEY"

# by default nothing, and fnox never runs
rm -f fnox-calls.jsonl
assert "mise x -- sh -c 'echo \${DEPLOY_KEY:-unset}'" "unset"
[[ ! -e fnox-calls.jsonl ]] || fail "mise x without a flag must not run fnox"
# no environment variable or setting turns it on
assert "MISE_SECRETS=DEPLOY_KEY mise x -- sh -c 'echo \${DEPLOY_KEY:-unset}'" "unset"
[[ ! -e fnox-calls.jsonl ]] || fail "an environment variable must not grant secrets"

# a file secret cannot be given
assert_fail_contains "mise x --secrets GCP_SA_JSON -- true" "GCP_SA_JSON is a file secret (as_file = true)"
assert_fail_contains "mise x --secrets NOPE -- true" "mise x: unknown secret NOPE"
assert_fail_contains "mise x --secrets NOPE -- true" "Granted by: --secrets"
assert_fail_contains "mise x --secrets SIGNING_KEY -- true" "mise x: SIGNING_KEY cannot be injected"

# --secrets-all names every injectable key except files
rm -f fnox-calls.jsonl
assert_contains "mise x --secrets-all -- true </dev/null 2>&1" "skipping file secrets GCP_SA_JSON"
keys="$(jq -r 'select(.argv|index("--keys"))|.argv[-1]' fnox-calls.jsonl)"
assert_not_contains "echo $keys" "GCP_SA_JSON"
assert_not_contains "echo $keys" "SIGNING_KEY"
assert_contains "echo $keys" "STRIPE_KEY"
assert "mise x --secrets-all -- sh -c 'echo \${STRIPE_KEY:-unset}' </dev/null 2>/dev/null" "stripe-s3cr3t-0003"
assert "mise x --secrets-all -- sh -c 'echo \${GCP_SA_JSON:-unset}' </dev/null 2>/dev/null" "unset"
assert_fail_contains "mise x --secrets-all --secrets DEPLOY_KEY -- true </dev/null" "cannot be used with"

# safe mode refuses
assert_fail_contains "MISE_SAFE=1 mise x --secrets DEPLOY_KEY -- true" "safe"
assert_fail_contains "MISE_EXPERIMENTAL=0 mise x --secrets DEPLOY_KEY -- true" "experimental"

# a source is required
printf '' >mise.toml
assert_fail_contains "mise x --secrets DEPLOY_KEY -- true" "mise x --secrets needs a secrets source"
printf '[secrets.fnox]\n' >mise.toml

# the marker names what the command was given, and what it already held
assert "mise x --secrets DEPLOY_KEY -- sh -c 'printf %s \"\$__MISE_SECRET_KEYS\"'" "DEPLOY_KEY"
assert "__MISE_SECRET_KEYS=FOO FOO=x mise x --secrets DEPLOY_KEY -- sh -c 'printf %s \"\$__MISE_SECRET_KEYS\"'" "DEPLOY_KEY,FOO"
# without a grant an inherited marker passes through untouched
assert "__MISE_SECRET_KEYS=FOO FOO=x mise x -- sh -c 'printf %s \"\$__MISE_SECRET_KEYS\"'" "FOO"

# mise secrets ls shows where each key can go
assert "mise secrets ls -J | jq -c '.keys[] | select(.key==\"DEPLOY_KEY\") | .scopes'" '["run","exec"]'
assert "mise secrets ls -J | jq -c '.keys[] | select(.key==\"GCP_SA_JSON\") | .scopes'" '["run"]'
assert "mise secrets ls -J | jq -c '.keys[] | select(.key==\"SIGNING_KEY\") | .scopes'" '[]'
assert_contains "mise secrets ls" "run, exec"
assert_contains "mise secrets ls --complete" "DEPLOY_KEY"
assert_not_contains "mise secrets ls --complete" "SIGNING_KEY"

# a failed exec does not leave values for anything mise runs afterwards, and prints none
mise x --secrets DEPLOY_KEY -- /nonexistent-binary >failed.out 2>&1 </dev/null && fail "exec of a missing binary succeeded"
assert_not_contains "cat failed.out" "s3cr3t"
assert_contains "cat failed.out" "nonexistent-binary"

# fish gets the values from its environment, never from argv: a stand-in named fish takes the
# fish branch and reports its own command line
mkdir -p "$PWD/fakefish"
cat >"$PWD/fakefish/fish" <<'SH'
#!/usr/bin/env bash
if [ -r "/proc/$$/cmdline" ]; then tr '\0' ' ' <"/proc/$$/cmdline" >"$ARGV_OUT"; else echo __MISE_FISH_ENV_ >"$ARGV_OUT"; fi
SH
chmod +x "$PWD/fakefish/fish"
ARGV_OUT="$PWD/argv.out" mise x --secrets DEPLOY_KEY -- "$PWD/fakefish/fish" </dev/null >/dev/null 2>&1
assert_contains "cat argv.out" "__MISE_FISH_ENV_"
assert_not_contains "cat argv.out" "s3cr3t"
if command -v fish >/dev/null; then
  assert "mise x --secrets DEPLOY_KEY -- fish -c 'echo \$DEPLOY_KEY'" "deploy-s3cr3t-0001"
fi

# a pitchfork-style probe passes no flag, so it gets nothing
assert "mise x -- sh -c 'echo \${DEPLOY_KEY:-unset}'" "unset"

# grants go in after the sandbox filter: a key fnox removes stays removed even when allow_env
# would copy the inherited value back
assert "FNOX_AGE_KEY=stale mise x --deny-env --allow-env DEPLOY_KEY --allow-env 'FNOX_*' --secrets DEPLOY_KEY -- sh -c 'echo \${FNOX_AGE_KEY:-unset}'" "unset"
assert "FNOX_AGE_KEY=stale mise x --deny-env --allow-env DEPLOY_KEY --allow-env FNOX_AGE_KEY --secrets DEPLOY_KEY -- sh -c 'echo \${FNOX_AGE_KEY:-unset}'" "unset"
assert "mise x --deny-env --allow-env DEPLOY_KEY --secrets DEPLOY_KEY -- sh -c 'echo \$DEPLOY_KEY'" "deploy-s3cr3t-0001"

# a value with a NUL byte cannot be an environment variable: refused without echoing it
FNOX_STUB_MODE=nul mise x --secrets DEPLOY_KEY -- true >nul.out 2>&1 </dev/null && fail "exec of a NUL value succeeded"
assert_contains "cat nul.out" "DEPLOY_KEY contains a NUL byte"
assert_not_contains "cat nul.out" "s3cr3t"
assert_not_contains "cat nul.out" "panicked"

# a key mise sets for the command is a collision even when its value equals the shell's
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
DATABASE_URL = "default"
TOML
export DATABASE_URL=default
assert_fail_contains "mise x --secrets DATABASE_URL -- true" "mise x: DATABASE_URL is both a secret and a mise env var"
assert_contains "mise x --secrets-all -- env </dev/null 2>&1" "not granting DATABASE_URL to mise x: mise sets it for this command"
assert "mise x --secrets-all -- sh -c 'printf %s \"\$DATABASE_URL\"' </dev/null 2>/dev/null" "default"

# a key only the shell sets is not a mise env var: the secret wins under both flags, with and
# without the env cache
unset DATABASE_URL
printf '[secrets.fnox]\n' >mise.toml
for cache in 0 1; do
  export MISE_ENV_CACHE=$cache __MISE_ENV_CACHE_KEY="dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q="
  assert "DEPLOY_KEY=from-shell mise x --secrets DEPLOY_KEY -- sh -c 'printf %s \"\$DEPLOY_KEY\"' </dev/null 2>/dev/null" "deploy-s3cr3t-0001"
  assert "DEPLOY_KEY=from-shell mise x --secrets-all -- sh -c 'printf %s \"\$DEPLOY_KEY\"' </dev/null 2>/dev/null" "deploy-s3cr3t-0001"
  assert_not_contains "DEPLOY_KEY=from-shell mise x --secrets-all -- true </dev/null 2>&1" "not granting DEPLOY_KEY"
done

# a mise that config.fish starts sees no copy of a granted value: fish reads the temporary
# __MISE_FISH_SECRET_<n> variables only after config.fish, so they must be treated as secrets too
if command -v fish >/dev/null; then
  mkdir -p "$PWD/fishcfg/fish"
  printf 'mise run fishtask >fishout.txt 2>&1\n' >"$PWD/fishcfg/fish/config.fish"
  cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
FLAG = "1"

[tasks.fishtask]
run = 'env >fishtask.env; echo "flag=$FLAG"'
TOML
  rm -f fishtask.env
  XDG_CONFIG_HOME="$PWD/fishcfg" mise x --secrets DEPLOY_KEY -- fish -c 'true' </dev/null >/dev/null 2>&1
  [[ -f fishtask.env ]] || fail "config.fish did not run the nested task"
  assert_not_contains "cat fishtask.env" "s3cr3t"
  assert_not_contains "cat fishtask.env" "__MISE_FISH_SECRET_"
  # a plain [env] value is not a secret: it reaches the nested task and is not redacted
  assert_contains "cat fishout.txt" "flag=1"
  # a secret a parent granted, restored by --allow-env, is copied under the secret name too
  rm -f fishtask.env
  __MISE_SECRET_KEYS=DEPLOY_KEY DEPLOY_KEY=inherited-s3cr3t-9999 XDG_CONFIG_HOME="$PWD/fishcfg" mise x --deny-env --allow-env DEPLOY_KEY --allow-env XDG_CONFIG_HOME --allow-env __MISE_SECRET_KEYS -- fish -c 'true' </dev/null >/dev/null 2>&1
  [[ -f fishtask.env ]] || fail "config.fish did not run the nested task"
  assert_not_contains "cat fishtask.env" "inherited-s3cr3t-9999"
fi
