#!/usr/bin/env bash

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
helper="$repo_dir/config/dot/quickshell/scripts/panama-home-assistant-config"
service="$repo_dir/config/dot/quickshell/services/HomeAssistantConfig.qml"
page="$repo_dir/config/dot/quickshell/modules/settings/HomePhonePage.qml"
password_field="$repo_dir/config/dot/quickshell/modules/settings/PasswordField.qml"
harness_fixture="$repo_dir/tests/quickshell/HomeAssistantConfigHarness.qml"
work="$(mktemp -d /tmp/panama-ha-config.XXXXXX)"
env_file="$work/env"

fail() {
    printf 'Home Assistant config contract: %s\n' "$1" >&2
    exit 1
}

cleanup() {
    if declare -F qs_for_test >/dev/null; then
        qs_for_test kill >/dev/null 2>&1 || true
    fi
    rm -rf "$work"
}
trap cleanup EXIT

[[ -x "$helper" ]] || fail 'credential helper is missing or not executable'
[[ -f "$service" ]] || fail 'credential service is missing'
[[ -f "$harness_fixture" ]] || fail 'credential runtime harness is missing'

rg -Fq 'stdinEnabled: true' "$service" || fail 'credential writes do not use process stdin'
rg -Fq 'writeProc.write(root.pendingPayload + "\n")' "$service" || fail 'credential payload is not written over stdin'
rg -Fq 'root.pendingPayload = ""' "$service" || fail 'credential payload remains in service memory after write'
if rg -q 'command:.*(token|pendingPayload)' "$service"; then
    fail 'credential data can reach a process command line'
fi
rg -Fq 'PasswordField {' "$page" || fail 'Home Assistant token is not entered through the masked field'
rg -Fq 'activeFocusOnTab: true' "$password_field" || fail 'masked credential field is not keyboard reachable'
rg -Fq 'HomeAssistantConfig.save(' "$page" || fail 'Home Assistant configuration cannot be saved from Settings'
rg -Fq 'HomeAssistantConfig.clearToken()' "$page" || fail 'stored Home Assistant token cannot be cleared'
rg -Fq 'id: clearTokenButton' "$page" || fail 'clear-token action has no keyboard control identity'
rg -Fq 'id: saveHomeConfigButton' "$page" || fail 'save action has no keyboard control identity'
rg -Fq 'activeFocusOnTab: enabled' "$page" || fail 'credential actions are not in tab order'
rg -Fq 'Keys.onReturnPressed:' "$page" || fail 'credential actions have no keyboard activation'

cat >"$env_file" <<'EOF'
# Existing private shell settings must survive byte-for-byte.
export KEEP_ME='untouched value'
export JIRA_CREDENTIALS='unrelated-secret'
export PANAMA_HOME_ASSISTANT_URL='https://old.example.test'
export PANAMA_HOME_ASSISTANT_TOKEN='old-token'
export PANAMA_HOME_ASSISTANT_ENTITIES='light.old'
EOF
chmod 0644 "$env_file"

run_helper() {
    PANAMA_HOME_ASSISTANT_ENV_FILE="$env_file" "$helper" "$@"
}

status="$(run_helper status)" || fail 'status failed for a valid private env file'
jq -e '.configured == true and .tokenConfigured == true
    and .url == "https://old.example.test"
    and .entities == ["light.old"] and (has("token") | not)' \
    <<<"$status" >/dev/null || fail "status exposed or misread credentials: $status"
if rg -q 'old-token|unrelated-secret' <<<"$status"; then
    fail 'status output leaked a secret'
fi

secret='ha-secret-must-never-appear-in-ps-or-output'
payload="$work/payload.json"
jq -cn --arg token "$secret" '{
    url: "https://home.example.test/",
    token: $token,
    entities: ["light.kitchen", "light.desk", "light.kitchen"]
}' >"$payload"

# Keep stdin open long enough to prove the token is absent from the helper's
# process arguments. The secret lives only in the private payload file/stdin.
fifo="$work/input.fifo"
mkfifo "$fifo"
PANAMA_HOME_ASSISTANT_ENV_FILE="$env_file" "$helper" write <"$fifo" >"$work/write.out" 2>"$work/write.err" &
helper_pid=$!
for _ in $(seq 1 30); do
    kill -0 "$helper_pid" 2>/dev/null && break
    sleep 0.05
done
if ps -o args= -p "$helper_pid" | rg -Fq "$secret"; then
    fail 'token appeared in the credential helper process arguments'
fi
cp "$payload" "$fifo"
wait "$helper_pid" || fail 'stdin credential write failed'

write_result="$(cat "$work/write.out")"
jq -e '.ok == true and .configured == true and .tokenConfigured == true
    and .url == "https://home.example.test"
    and .entities == ["light.kitchen", "light.desk"] and (has("token") | not)' \
    <<<"$write_result" >/dev/null || fail "write returned unsafe or incorrect state: $write_result"
if rg -q "$secret|old-token|unrelated-secret" "$work/write.out" "$work/write.err"; then
    fail 'credential helper output leaked a secret'
fi

[[ "$(stat -c '%a' "$env_file")" == "600" ]] || fail 'private env file is not mode 0600'
rg -Fxq "export KEEP_ME='untouched value'" "$env_file" || fail 'unrelated env content changed'
rg -Fxq "export JIRA_CREDENTIALS='unrelated-secret'" "$env_file" || fail 'unrelated secret changed'
rg -Fq "$secret" "$env_file" || fail 'new token was not stored'

# Omitting token preserves it; an explicit empty token clears it.
printf '%s\n' '{"url":"https://new.example.test","entities":"light.office, light.hall"}' \
    | run_helper write >/dev/null || fail 'non-secret update failed'
rg -Fq "$secret" "$env_file" || fail 'blank token field unexpectedly erased the stored token'

printf '%s\n' '{"token":""}' | run_helper write >/dev/null || fail 'token clear failed'
cleared="$(run_helper status)"
jq -e '.configured == false and .tokenConfigured == false
    and .url == "https://new.example.test"
    and .entities == ["light.office", "light.hall"]' \
    <<<"$cleared" >/dev/null || fail "cleared state is wrong: $cleared"

before_hash="$(sha256sum "$env_file" | cut -d' ' -f1)"
printf '%s\n' '{"url":"file:///etc/passwd"}' | run_helper write >/dev/null 2>&1 \
    && fail 'invalid URL was accepted'
after_hash="$(sha256sum "$env_file" | cut -d' ' -f1)"
[[ "$before_hash" == "$after_hash" ]] || fail 'rejected input still modified the private env file'

# Exercise the actual QML Process.write() boundary with a pre-existing token.
# The IPC carries only non-secret fields; the helper must preserve the token.
config_path="$work/quickshell"
harness="$config_path/home-assistant-config-harness.qml"
cp -a "$repo_dir/config/dot/quickshell" "$config_path"
cp "$harness_fixture" "$harness"
printf '%s\n' \
    "export PANAMA_HOME_ASSISTANT_URL='https://qml-old.example.test'" \
    "export PANAMA_HOME_ASSISTANT_TOKEN=''" \
    "export PANAMA_HOME_ASSISTANT_ENTITIES='light.old'" >"$env_file"
chmod 0600 "$env_file"
qml_token_file="$work/qml-token"
printf '%s' 'qml-private-token' >"$qml_token_file"
chmod 0600 "$qml_token_file"

qs_for_test() {
    PANAMA_HOME_ASSISTANT_ENV_FILE="$env_file" \
        PANAMA_TEST_TOKEN_FILE="$qml_token_file" \
        XDG_CONFIG_HOME="$work/config" XDG_STATE_HOME="$work/state" \
        qs -p "$harness" "$@"
}

stop_harness() {
    qs_for_test kill >/dev/null 2>&1 || true
}

qs_for_test --daemonize >/dev/null
for _ in $(seq 1 60); do
    qs_for_test ipc show 2>/dev/null | rg -q '^target home-assistant-config-test$' && break
    sleep 0.1
done
qs_for_test ipc show 2>/dev/null | rg -q '^target home-assistant-config-test$' \
    || fail 'credential QML harness did not start'

for _ in $(seq 1 60); do
    qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
    jq -e '.busy == false and .url == "https://qml-old.example.test"' <<<"$qml_status" >/dev/null && break
    sleep 0.1
done
jq -e '.configured == false and .tokenConfigured == false and .pendingPayloadEmpty == true' \
    <<<"$qml_status" >/dev/null || fail "QML service did not load redacted state: $qml_status"

qs_for_test ipc call home-assistant-config-test saveWithToken \
    https://qml-new.example.test 'light.office,light.hall' >/dev/null \
    || fail 'QML service refused a private token-file update'
for _ in $(seq 1 60); do
    qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
    jq -e '.busy == false and .configured == true and .tokenConfigured == true
        and .refreshCalls > 0' <<<"$qml_status" >/dev/null && break
    sleep 0.1
done
jq -e '.configured == true and .tokenConfigured == true
    and .url == "https://qml-new.example.test"
    and .entities == ["light.office", "light.hall"]
    and .pendingPayloadEmpty == true and .lastError == "" and .refreshCalls > 0' \
    <<<"$qml_status" >/dev/null || fail "QML secret stdin save did not settle safely: $qml_status"
rg -Fq 'qml-private-token' "$env_file" || fail 'QML secret stdin save did not store the token'
if ps -o args= -p "$(qs_for_test list | awk '/Process ID:/ {print $3; exit}')" | rg -Fq 'qml-private-token'; then
    fail 'QML token appeared in the shell process arguments'
fi

qs_for_test ipc call home-assistant-config-test save \
    https://qml-final.example.test 'light.bedroom,light.hall' >/dev/null \
    || fail 'QML service refused a non-secret update'
for _ in $(seq 1 60); do
    qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
    jq -e '.busy == false and .url == "https://qml-final.example.test"
        and .entities == ["light.bedroom", "light.hall"]' <<<"$qml_status" >/dev/null && break
    sleep 0.1
done
jq -e '.configured == true and .tokenConfigured == true
    and .pendingPayloadEmpty == true and .lastError == ""' \
    <<<"$qml_status" >/dev/null || fail "QML stdin save did not settle safely: $qml_status"
rg -Fq 'qml-private-token' "$env_file" || fail 'QML non-secret save erased the stored token'

qs_for_test ipc call home-assistant-config-test clearToken >/dev/null \
    || fail 'QML service refused token clear'
for _ in $(seq 1 60); do
    qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
    jq -e '.busy == false and .tokenConfigured == false' <<<"$qml_status" >/dev/null && break
    sleep 0.1
done
jq -e '.configured == false and .tokenConfigured == false
    and .pendingPayloadEmpty == true and .lastError == ""' \
    <<<"$qml_status" >/dev/null || fail "QML token clear did not settle safely: $qml_status"
if rg -Fq 'qml-private-token' "$env_file"; then
    fail 'QML token clear left the old token in the private env file'
fi
stop_harness

trap - EXIT
cleanup
printf 'Home Assistant config contract: PASS\n'
