#!/usr/bin/env bash

# The stated reason on the authentication prompt.
#
# panama-sudo lets a caller say WHY it is about to trigger a password prompt.
# The reason is untrusted text from an unprivileged process, so the properties
# worth pinning are the ones that keep it honest:
#
#   1. The prompt renders the reason BESIDE polkitd's real action message,
#      labeled as unverified -- never in place of it. Any process can claim
#      "Updating your system" while requesting something else; the action text
#      is the trust anchor and must survive.
#   2. Single-shot and short-lived: a reason attaches to the next request
#      only, is consumed whether or not it was fresh, and expires rather than
#      dressing up an unrelated prompt minutes later.
#   3. panama-sudo degrades to plain pkexec: no --reason, no qs, or a dead
#      shell must all still run the command.
#
# The wrapper is exercised for real against stub qs and pkexec; the QML side
# is pinned statically, the way the polkit-agent contract pins its rules.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
wrapper="$repo_dir/bin/panama-sudo"
service="$repo_dir/config/dot/quickshell/services/Polkit.qml"
prompt="$repo_dir/config/dot/quickshell/modules/polkit/PolkitPrompt.qml"
shell_qml="$repo_dir/config/dot/quickshell/shell.qml"

findings=()
note() { findings+=("$1"); }

[[ -x "$wrapper" ]] || { printf 'polkit reason contract: %s is not executable\n' "$wrapper" >&2; exit 1; }

# ── The wrapper, for real ────────────────────────────────────────────────────

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
calls="$work/calls"
stub_dir="$work/bin"
mkdir -p "$stub_dir"

for command in qs pkexec; do
    cat >"$stub_dir/$command" <<STUB
#!/usr/bin/env bash
printf '%s %s\n' "$command" "\$*" >>"$calls"
STUB
    chmod +x "$stub_dir/$command"
done

run_wrapper() { PATH="$stub_dir:$PATH" "$wrapper" "$@" >/dev/null 2>&1; }

# A reason reaches the shell first, then the command runs unchanged.
: >"$calls"
run_wrapper --reason "Test reason" -- some-command --with args \
    || note 'the wrapper failed with a reason and a command'
grep -q 'qs ipc call polkit reason Test reason' "$calls" \
    || note 'the reason never reaches the shell over IPC'
grep -q 'pkexec some-command --with args' "$calls" \
    || note 'the command does not reach pkexec unchanged'
[[ "$(head -1 "$calls")" == qs* ]] \
    || note 'the reason is sent after pkexec instead of before the prompt can appear'

# No reason means no IPC chatter, and still pkexec.
: >"$calls"
run_wrapper -- some-command || note 'the wrapper failed without a reason'
grep -q 'qs' "$calls" && note 'the wrapper calls qs even when no reason was given'
grep -q 'pkexec some-command' "$calls" || note 'a reasonless call does not reach pkexec'

# A dead shell must not cost the command: qs failing is stepped over.
cat >"$stub_dir/qs" <<'STUB'
#!/usr/bin/env bash
exit 1
STUB
chmod +x "$stub_dir/qs"
: >"$calls"
run_wrapper --reason "Doomed" -- some-command \
    || note 'a failing qs stops the command instead of degrading to plain pkexec'
grep -q 'pkexec some-command' "$calls" \
    || note 'the command is lost when the shell is not answering'

# No command is a usage error, not a bare pkexec prompt for nothing.
run_wrapper --reason "Aimless" -- && note 'the wrapper accepts a reason with no command'

# ── The QML side, statically ─────────────────────────────────────────────────

# The IPC door exists and feeds the service.
rg -Fq 'target: "polkit"' "$shell_qml" \
    || note 'shell.qml has no polkit IPC target'
rg -Fq 'Polkit.stateReason(text)' "$shell_qml" \
    || note 'the polkit IPC target does not feed Polkit.stateReason'

# Single-shot, bounded, and cleared: consumed on adopt even when stale, aged
# against a ten-second window, and wiped with the rest of the request state.
rg -Fq 'root.pendingReason = null' "$service" \
    || note 'a stated reason is not consumed when a request arrives'
rg -Fq 'pending.at <= 10000' "$service" \
    || note 'a stated reason never expires, so it can dress up a later prompt'
rg -Fq 'root.statedReason = ""' "$service" \
    || note 'the stated reason survives dismissal'

# The prompt shows the real message AND the labeled reason -- both, in that
# trust order.
rg -Fq 'text: Polkit.message' "$prompt" \
    || note "polkitd's own action message is no longer rendered"
rg -Fq 'text: Polkit.statedReason' "$prompt" \
    || note 'the stated reason is never rendered'
rg -Fq 'Stated reason (unverified)' "$prompt" \
    || note 'the stated reason is not labeled as an unverified claim'

if (( ${#findings[@]} > 0 )); then
    printf 'polkit reason contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'polkit reason contract: PASS\n'
