#!/usr/bin/env bash

# Bad grants fail before the scheduler starts anything, with every problem in one error.

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"

write() {
  cat >mise.toml <<TOML
[secrets.fnox]
${2-}

[tasks.build]
run = "touch build.out"

[tasks.deploy]
depends = ["build"]
secrets = $1
run = 'true'
TOML
}
expect_fail() {
  assert_fail_contains "$1" "$2"
  [[ ! -e build.out ]] || fail "build ran first"
  rm -f build.out
}

write '["DEPLOY_KYE"]'
expect_fail "mise run deploy </dev/null" "Did you mean DEPLOY_KEY"
write '["SIGNING_KEY"]'
expect_fail "mise run deploy </dev/null" "SIGNING_KEY cannot be injected"
write 'true'
expect_fail "mise run deploy </dev/null" "secrets = true is not supported"
write '["deploy key"]'
expect_fail "mise run deploy </dev/null" "is not a valid environment variable name"
write '["AWS_*"]'
expect_fail "mise run deploy </dev/null" "wildcards are not supported"
write '["PATH"]'
expect_fail "mise run deploy </dev/null" "reserved by mise"
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
DATABASE_URL = "x"

[tasks.build]
run = "touch build.out"

[tasks.deploy]
depends = ["build"]
secrets = ["DATABASE_URL"]
run = 'true'
TOML
expect_fail "mise run deploy </dev/null" "DATABASE_URL is both a secret and a mise env var"

write '["DEPLOY_KEY", "DATABASE_URL", "PEM_KEY"]'
expect_fail "mise run --deny-env deploy </dev/null" "its sandbox denies env vars"
assert_succeed "mise run --allow-env DEPLOY_KEY --allow-env DATABASE_URL --allow-env PEM_KEY deploy </dev/null"
rm -f build.out

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

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
run = 'echo {{ env.DEPLOY_KEY }}'
TOML
assert_fail_contains "mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"

# a source declared only in global config is ignored
cat >mise.toml <<'TOML'
[tasks.deploy]
secrets = ["DEPLOY_KEY"]
run = 'true'
TOML
mkdir -p "$MISE_CONFIG_DIR"
printf '[secrets.fnox]\n' >"$MISE_CONFIG_DIR/config.toml"
assert_fail_contains "mise run deploy </dev/null" "is ignored"
rm "$MISE_CONFIG_DIR/config.toml"

# one failing key: the first task reports it, the second does not retry
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.e1]
secrets = ["DEPLOY_KEY"]
run = 'true'

[tasks.e2]
secrets = ["DEPLOY_KEY"]
run = 'true'

[tasks.lint]
run = "exit 1"
TOML
rm -f fnox-calls.jsonl
output=$(FNOX_STUB_FAIL=DEPLOY_KEY mise run -c --jobs 1 e1 ::: e2 </dev/null 2>&1 || true)
grep -q "fnox could not resolve DEPLOY_KEY" <<<"$output" || fail "expected G14, got: $output"
grep -q "not retrying DEPLOY_KEY" <<<"$output" || fail "expected G15, got: $output"
assert "jq -s '[.[]|select(.argv|index(\"--keys\"))]|length' fnox-calls.jsonl" "1"
g14=$(grep -c "fnox could not resolve DEPLOY_KEY" <<<"$output" || true)
[[ $g14 -eq 1 ]] || fail "expected G14 once, got $g14: $output"

# G14 is printed even when an earlier task already failed, so G15 never points at nothing
output=$(FNOX_STUB_FAIL=DEPLOY_KEY mise run -c --jobs 1 lint ::: e1 ::: e2 </dev/null 2>&1 || true)
grep -q "fnox could not resolve DEPLOY_KEY" <<<"$output" || fail "expected G14, got: $output"
grep -q "not retrying DEPLOY_KEY" <<<"$output" || fail "expected G15, got: $output"
g14=$(grep -c "fnox could not resolve DEPLOY_KEY" <<<"$output" || true)
[[ $g14 -eq 1 ]] || fail "expected G14 once, got $g14: $output"

assert_fail_contains "MISE_EXPERIMENTAL=0 mise run e1 </dev/null" "experimental"

# an unusable list on a subtask injected by run = [{ task }] still fails, and the subtask does not run
for list in '["AWS_*"]:wildcards are not supported' '["PATH"]:reserved by mise' '["deploy key"]:is not a valid environment variable name'; do
  cat >mise.toml <<TOML
[secrets.fnox]

[tasks.deploy]
secrets = ${list%%:*}
run = 'touch deploy.out'

[tasks.parent]
run = [{ task = "deploy" }]
TOML
  assert_fail_contains "mise run parent </dev/null" "${list#*:}"
  [[ ! -e deploy.out ]] || fail "deploy ran"
done
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DEPLOY_KEY", "AWS_*"]
run = 'touch deploy.out'

[tasks.parent]
run = [{ task = "deploy" }]
TOML
assert_fail_contains "mise run --dry-run parent </dev/null" "wildcards are not supported"

# no source plus another problem: one aggregate naming both
cat >mise.toml <<'TOML'
[tasks.a]
secrets = ["DEPLOY_KEY"]
run = 'true'

[tasks.b]
secrets = ["deploy key"]
run = 'true'
TOML
output=$(mise run a ::: b </dev/null 2>&1 || true)
grep -q "cannot grant secrets (2 problems)" <<<"$output" || fail "expected one aggregate, got: $output"
grep -q "no secrets source is configured" <<<"$output" || fail "expected S1, got: $output"
grep -q "not a valid environment variable name" <<<"$output" || fail "expected G3, got: $output"

# a bootstrap-denied marker is parsed
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
run = 'true'
TOML
assert_fail_contains "__MISE_SECRETS_DENIED=bootstrap mise run deploy </dev/null" "mise bootstrap"

# validate keeps the ignored-file note, and describes a shared source once
cat >mise.toml <<'TOML'
[tasks.deploy]
secrets = ["DEPLOY_KEY"]
run = 'true'
TOML
printf '[secrets.fnox]\n' >"$MISE_CONFIG_DIR/config.toml" 2>/dev/null || {
  mkdir -p "$MISE_CONFIG_DIR"
  printf '[secrets.fnox]\n' >"$MISE_CONFIG_DIR/config.toml"
}
output=$(mise tasks validate 2>&1 || true)
grep -q "is ignored" <<<"$output" || fail "validate lost the ignored note: $output"
rm "$MISE_CONFIG_DIR/config.toml"
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.a]
secrets = ["DEPLOY_KEY"]
run = 'true'

[tasks.b]
secrets = ["DATABASE_URL"]
run = 'true'

[tasks.c]
secrets = ["PEM_KEY"]
run = 'true'
TOML
rm -f fnox-calls.jsonl
mise tasks validate </dev/null >/dev/null 2>&1 || true
assert "jq -s '[.[]|select(.argv|index(\"--describe\"))]|length' fnox-calls.jsonl" "1"

# mise sets the key to the very value the shell already has: still a collision
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
DATABASE_URL = "default"

[tasks.deploy]
secrets = ["DATABASE_URL"]
run = 'true'
TOML
assert_fail_contains "DATABASE_URL=default mise run deploy </dev/null" "DATABASE_URL is both a secret and a mise env var"
# the shell sets it and mise does not: the secret wins
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DATABASE_URL"]
run = 'printf %s "$DATABASE_URL" >db.out'
TOML
assert_succeed "DATABASE_URL=default mise run deploy </dev/null"
assert "cat db.out" "postgres://app:db-s3cr3t-0002@db/app"

# a task that only orchestrates other tasks must not hold secrets while they run
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.child]
run = 'printf %s "${GCP_SA_JSON-unset}" >child.out'

[tasks.parent]
secrets = ["GCP_SA_JSON"]
run = [{ task = "child" }]
TOML
rm -f fnox-calls.jsonl child.out
assert_fail_contains "mise run parent </dev/null" "starts no process of its own"
[[ ! -e child.out ]] || fail "child ran"
assert "jq -s '[.[]|select(.argv|index(\"--keys\"))]|length' fnox-calls.jsonl" "0"

# a default that the shell already satisfies assigns nothing, so it is not a collision
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
DATABASE_URL = { default = "local" }

[tasks.deploy]
secrets = ["DATABASE_URL"]
run = 'printf %s "$DATABASE_URL" >db.out'
TOML
assert_succeed "DATABASE_URL=from-shell mise run deploy </dev/null"
assert "cat db.out" "postgres://app:db-s3cr3t-0002@db/app"

# a default that reads a granted key would copy the stale shell value
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
run = 'true'
TOML
assert_fail_contains "env -u TOKEN mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"
# the caller supplies TOKEN, so the default is never rendered
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
run = 'printf %s "$TOKEN" >token.out'
TOML
assert_succeed "TOKEN=ready mise run deploy </dev/null"
assert "cat token.out" "ready"
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.OTHER_VAR }}" } }
run = 'true'
TOML
assert_succeed "OTHER_VAR=x mise run deploy </dev/null"

# a default is judged against the env the resolver sees: config [env] unsetting TOKEN lets it
# render even though the shell has a value; config [env] assigning TOKEN keeps it from rendering
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
TOKEN = false

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
run = 'true'
TOML
assert_fail_contains "TOKEN=ready mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
TOKEN = "from-config"

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
run = 'printf %s "$TOKEN" >token2.out'
TOML
assert_succeed "env -u TOKEN mise run deploy </dev/null"
assert "cat token2.out" "from-config"

# config [env] assigning an empty TOKEN does not satisfy the default, so it renders
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
TOKEN = ""

[tasks.deploy]
secrets = ["DEPLOY_KEY"]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
run = 'true'
TOML
assert_fail_contains "TOKEN=ready mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"

# an earlier templated value may render empty, so the default after it is still checked: a file
# task sets TOKEN, and a [tasks.deploy] block overlays a default for the same key
mkdir -p mise-tasks
cat >mise-tasks/deploy <<'SH'
#!/usr/bin/env bash
#MISE secrets=["DEPLOY_KEY"]
#MISE env={TOKEN="{{ '' }}"}
true
SH
chmod +x mise-tasks/deploy
cat >mise.toml <<'TOML'
[secrets.fnox]

[tasks.deploy]
env = { TOKEN = { default = "{{ env.DEPLOY_KEY }}" } }
TOML
assert_fail_contains "DEPLOY_KEY=old mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"
# a plain non-empty earlier value satisfies the default, so the task runs
cat >mise-tasks/deploy <<'SH'
#!/usr/bin/env bash
#MISE secrets=["DEPLOY_KEY"]
#MISE env={TOKEN="plain"}
true
SH
assert_succeed "DEPLOY_KEY=old mise run deploy </dev/null"

# the resolver reports which defaults rendered: a default that yielded to a script-exported
# value is not scanned
cat >export-token.sh <<'SH'
export TOKEN=ready
SH
mkdir -p mise-tasks
cat >mise-tasks/deploy <<'SH'
#!/usr/bin/env bash
#MISE secrets=["DEPLOY_KEY"]
#MISE env={TOKEN={default="{{ env.DEPLOY_KEY }}"}}
printf %s "$TOKEN" >tok3.out
SH
chmod +x mise-tasks/deploy
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
_.source = "export-token.sh"
TOML
assert_succeed "env -u TOKEN DEPLOY_KEY=old mise run deploy </dev/null"
assert "cat tok3.out" "ready"
# an overlay assigning the same key later does not make the yielded default render
cat >mise.toml <<'TOML'
[secrets.fnox]

[env]
_.source = "export-token.sh"

[tasks.deploy]
env = { TOKEN = "override" }
TOML
assert_succeed "env -u TOKEN DEPLOY_KEY=old mise run deploy </dev/null"
assert "cat tok3.out" "override"
# and where nothing supplies TOKEN the default really renders, so it is refused
cat >mise.toml <<'TOML'
[secrets.fnox]
TOML
assert_fail_contains "env -u TOKEN DEPLOY_KEY=old mise run deploy </dev/null" "cannot see the secret DEPLOY_KEY"
