#!/usr/bin/env bash

# Behavioral coverage for the QML handoff after the helper commits a restore.
# The harness has a unique shell identity, isolated XDG roots, and fake external
# consumers. It records the real SettingsBackup call order without touching the
# daily-driver shell, compositor, keymap, or wallpaper.

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
service="$repo_dir/config/dot/quickshell/services/SettingsBackup.qml"
harness="$repo_dir/config/dot/quickshell/settings-backup-harness.qml"
work="$(mktemp -d /tmp/panama-settings-backup-live.XXXXXX)"

fail() {
    printf 'settings backup live contract: %s\n' "$1" >&2
    exit 1
}

qs_test() {
    XDG_CONFIG_HOME="$work/config" XDG_STATE_HOME="$work/state" qs -p "$harness" "$@"
}

cleanup() {
    qs_test kill >/dev/null 2>&1 || true
    rm -rf "$work"
}
trap cleanup EXIT

# The production command boundary must remain argv-only.
rg -Fq 'actionRun.exec([root.helperPath, "save", root.serializeHomeState()]);' "$service" \
    || fail 'save does not pass live Home state as one argument'
rg -Fq 'actionRun.exec([root.helperPath, "restore", name]);' "$service" \
    || fail 'restore is not executed through an argument array'
if rg -q 'bash.*-c|sh.*-c' "$service"; then
    fail 'the restore service constructs a shell command'
fi

# The harness replaces these seams, while these mappings prove the production
# defaults still delegate to Panama's existing public service APIs.
for mapping in \
    'HomePreferences.resetHomeDefaults();' \
    'HomePreferences.initialize(ids);' \
    'HomePreferences.setAlias(id, alias);' \
    'DesktopPreferences.reload();' \
    'DesktopPreferences.set("displays", value);' \
    'Displays.currentLayout();' \
    'Displays.applyProtectedLayout(layout);' \
    'Displays.canConfirm;' \
    'Displays.confirm();' \
    'IdleLock.apply();' \
    'Displays.externalChangeBlocked = blocked;' \
    'SystemSettings.applyPersistedDisplayPolicy();' \
    'Keybinds.applyReload();' \
    'Wallpaper.applyCurrentPolicy(false);' \
    'LockScreen.regenerate();' \
    'Quickshell.reload(false);'; do
    rg -Fq "$mapping" "$service" || fail "production restore seam is missing: $mapping"
done

qs_test --daemonize >"$work/quickshell.log" 2>&1
ready=false
for _ in $(seq 1 60); do
    if qs_test ipc show 2>/dev/null | rg -q '^target settings-backup-behavior$'; then
        ready=true
        break
    fi
    sleep 0.1
done
if [[ "$ready" != true ]]; then
    sed -n '1,200p' "$work/quickshell.log" >&2
    fail 'isolated SettingsBackup harness did not start'
fi

qs_test ipc call settings-backup-behavior reset >/dev/null
payload='{"restored":"settings-20260818-010203004.json","home":{"present":true,"data":{"initialized":true,"favorites":[{"id":"light.desk","alias":"Desk"},{"id":"light.office","alias":"Office"}]}}}'
[[ "$(qs_test ipc call settings-backup-behavior apply "$payload")" == "true" ]] \
    || fail 'valid restore output was rejected'

status=""
for _ in $(seq 1 50); do
    status="$(qs_test ipc call settings-backup-behavior status)"
    jq -e '.calls[-1] == "shell.reload"' <<<"$status" >/dev/null 2>&1 && break
    sleep 0.1
done
jq -e '
    .calls == [
        "home.reset",
        "home.initialize:light.desk,light.office",
        "home.alias:light.desk=Desk",
        "home.alias:light.office=Office",
        "desktop.reload",
        "display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
        "display.confirm",
        "idle.apply",
        "lock.regenerate",
        "wallpaper.apply-policy",
        "keybinds.reload",
        "system.apply",
        "display.block:false",
        "shell.reload"
    ]
    and .initialized == true
    and .favorites == [
        {"id":"light.desk","alias":"Desk"},
        {"id":"light.office","alias":"Office"}
    ]
' <<<"$status" >/dev/null || fail "restore handoff order/state was wrong: $status"

# Invalid output is rejected before Home state or external consumers change.
qs_test ipc call settings-backup-behavior reset >/dev/null
invalid='{"home":{"present":true,"data":{"initialized":true,"favorites":[{"id":"light.desk","alias":"One"},{"id":"light.desk","alias":"Two"}]}}}'
[[ "$(qs_test ipc call settings-backup-behavior apply "$invalid")" == "false" ]] \
    || fail 'duplicate Home state was accepted'
status="$(qs_test ipc call settings-backup-behavior status)"
jq -e '.calls == [] and .initialized == false and .favorites == []' <<<"$status" >/dev/null \
    || fail 'invalid restore output caused partial live mutations'

# An absent Home generation uses the same ordered external handoff but leaves
# the live Home service reset rather than manufacturing an initialized store.
qs_test ipc call settings-backup-behavior reset >/dev/null
absent='{"restored":"settings-20260818-010203005.json","home":{"present":false}}'
[[ "$(qs_test ipc call settings-backup-behavior apply "$absent")" == "true" ]] \
    || fail 'absent Home restore output was rejected'
for _ in $(seq 1 50); do
    status="$(qs_test ipc call settings-backup-behavior status)"
    jq -e '.calls[-1] == "shell.reload"' <<<"$status" >/dev/null 2>&1 && break
    sleep 0.1
done
jq -e '
    .calls == [
        "home.reset",
        "desktop.reload",
        "display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
        "display.confirm",
        "idle.apply",
        "lock.regenerate",
        "wallpaper.apply-policy",
        "keybinds.reload",
        "system.apply",
        "display.block:false",
        "shell.reload"
    ]
    and .initialized == false
    and .favorites == []
' <<<"$status" >/dev/null || fail "absent Home handoff was wrong: $status"

# If the restored complete layout is rejected before it can be verified, the
# original persisted layout is put back and the shell is not reloaded over an
# unproven display state.
qs_test ipc call settings-backup-behavior reset >/dev/null
[[ "$(qs_test ipc call settings-backup-behavior applyDisplayFailure "$payload")" == "false" ]] \
    || fail 'a rejected restored display layout was reported as successful'
status="$(qs_test ipc call settings-backup-behavior status)"
jq -e '.calls == [
    "home.reset",
    "home.initialize:light.desk,light.office",
    "home.alias:light.desk=Desk",
    "home.alias:light.office=Office",
    "desktop.reload",
    "display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
    "display.protect:{\"DP-2\":{\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true},\"HDMI-A-1\":{\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":3000,\"y\":0,\"primary\":false}}",
    "display.block:false"
] and (.lastError | contains("display layout"))' <<<"$status" >/dev/null \
    || fail "rejected display restore did not retain the original layout: $status"

# Restore refuses before launching the helper while a display apply/recovery is
# active, so no snapshot can race the confirmation boundary.
qs_test ipc call settings-backup-behavior reset >/dev/null
[[ "$(qs_test ipc call settings-backup-behavior restoreWhileDisplayBusy)" == "false" ]] \
    || fail 'snapshot restore started during an active display operation'
status="$(qs_test ipc call settings-backup-behavior status)"
jq -e '.calls == [] and (.lastError | contains("display change"))' <<<"$status" >/dev/null \
    || fail "display-busy restore refusal was not clean: $status"

trap - EXIT
cleanup
printf 'settings backup live contract: PASS\n'
