#!/usr/bin/env bash

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
state_home="$(mktemp -d /tmp/panama-control-center-state.XXXXXX)"
source_config_path="$repo_dir/config/dot/quickshell"
config_path="$state_home/quickshell"
helper_log="$state_home/home-helper.log"
shell_log="$state_home/quickshell.log"

: >"$helper_log"
cp -a "$source_config_path" "$config_path"
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' "$*" >>"$PANAMA_HOME_HELPER_LOG"
case "${1:-}" in
    catalog)
        printf '%s\n' '{"ok":false,"error":"test-helper"}'
        ;;
    toggle|brightness)
        printf '%s\n' '{"ok":true}'
        ;;
esac
EOF
chmod +x "$config_path/scripts/panama-home-assistant"

fail() {
    printf 'Control Center services contract: %s\n' "$1" >&2
    exit 1
}

qs_for_test() {
    QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
        PANAMA_HOME_HELPER_LOG="$helper_log" \
        qs -p "$config_path" "$@"
}

stop_test_shell() {
    qs_for_test kill >/dev/null 2>&1 || true
    for _ in $(seq 1 80); do
        if ! qs_for_test list 2>/dev/null | rg '^Instance ' >/dev/null \
                && ! qs_for_test ipc show >/dev/null 2>&1; then
            return 0
        fi
        sleep 0.1
    done
    return 1
}

cleanup() {
    qs_for_test ipc call kdeconnect reset >/dev/null 2>&1 || true
    qs_for_test ipc call home-assistant reset >/dev/null 2>&1 || true
    qs_for_test ipc call status-events reset >/dev/null 2>&1 || true
    qs_for_test ipc call quicksettings close >/dev/null 2>&1 || true
    if stop_test_shell; then
        rm -rf "$state_home"
    else
        printf 'Control Center services contract: branch shell did not stop; retained %s\n' \
            "$state_home" >&2
    fi
}
trap cleanup EXIT

start_test_shell() {
    stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
    for _attempt in 1 2; do
        qs_for_test --daemonize >"$shell_log" 2>&1
        for _ in $(seq 1 80); do
            if qs_for_test ipc show 2>/dev/null | rg '^target home-assistant$' >/dev/null; then
                return
            fi
            sleep 0.1
        done
        stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
    done
    sed -n '1,200p' "$shell_log" >&2
    fail 'isolated branch shell did not start'
}

wait_for_home_status() {
    local filter="$1"
    local message="$2"
    local status=""

    for _ in $(seq 1 80); do
        status="$(qs_for_test ipc call home-assistant status)"
        if jq -e "$filter" <<<"$status" >/dev/null; then
            return
        fi
        sleep 0.1
    done
    printf 'Last Home status: %s\n' "$status" >&2
    fail "$message"
}

start_test_shell

qs_for_test ipc show | rg '^target kdeconnect$' >/dev/null \
    || fail 'KDE Connect IPC target is missing'
qs_for_test ipc show | rg '^target home-assistant$' >/dev/null \
    || fail 'Home Assistant IPC target is missing'

qs_for_test ipc call kdeconnect fixture reachable >/dev/null
jq -e '
    .fixture == true and
    .available == true and
    .reachable == true and
    .actionCount == 4 and
    .transferActive == false and
    .ongoingCount == 0
' <<<"$(qs_for_test ipc call kdeconnect status)" >/dev/null \
    || fail 'reachable phone fixture is malformed'

qs_for_test ipc call kdeconnect fixture offline >/dev/null
jq -e '
    .fixture == true and
    .available == true and
    .reachable == false and
    .pairedCount == 1 and
    .ongoingCount == 0
' <<<"$(qs_for_test ipc call kdeconnect status)" >/dev/null \
    || fail 'offline phone fixture is malformed'

qs_for_test ipc call kdeconnect fixture transfer >/dev/null
jq -e '
    .fixture == true and
    .transferActive == true and
    .transferFileName == "Fixture document.pdf" and
    .ongoingCount == 1
' <<<"$(qs_for_test ipc call kdeconnect status)" >/dev/null \
    || fail 'phone transfer did not enter Ongoing'

qs_for_test ipc call kdeconnect cancel >/dev/null
jq -e '.transferActive == false and .ongoingCount == 0' \
    <<<"$(qs_for_test ipc call kdeconnect status)" >/dev/null \
    || fail 'phone transfer did not leave Ongoing'

qs_for_test ipc call home-assistant fixture ready >/dev/null
home_ready="$(qs_for_test ipc call home-assistant status)"
jq -e '
    .fixture == true and
    .phase == "ready" and
    .discoveredCount == 7 and
    .configuredCount == 7 and
    .visibleCount == 4 and
    .selectedIds[0:4] == [
        "light.fixture_all",
        "light.fixture_kitchen",
        "light.fixture_living",
        "light.fixture_bedroom"
    ] and
    (.entities[0:4] | map(.name)) == [
        "Whole home",
        "Kitchen island",
        "Living room",
        "Bedroom"
    ] and
    .stale == false and
    .busy == false and
    .lastError == ""
' <<<"$home_ready" >/dev/null \
    || fail 'ready Home fixture is malformed'

qs_for_test ipc call home-assistant brightness light.fixture_living 64 >/dev/null
jq -e '
    .entities[] |
    select(.id == "light.fixture_living") |
    .active == true and .state == "on" and .brightnessPct == 64
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'Home brightness fixture did not update local catalog state'

qs_for_test ipc call home-assistant toggle light.fixture_living >/dev/null
jq -e '
    .entities[] |
    select(.id == "light.fixture_living") |
    .active == false and .state == "off"
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'Home toggle fixture did not update local catalog state'

qs_for_test ipc call home-assistant fixture process-actions >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 64 >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null
jq -e '
    .busy == true and
    .busyEntityIds == ["light.fixture_living", "light.fixture_hall"] and
    .pendingBrightness["light.fixture_living"] == 64
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'process-backed Home actions did not remain per-entity busy'
wait_for_home_status '
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    (.entities[] | select(.id == "light.fixture_living") | .active == true and .brightnessPct == 64) and
    (.entities[] | select(.id == "light.fixture_hall") | .active == true)
' 'process-backed Home actions did not complete in queue order'

qs_for_test ipc call home-assistant fixture process-no-output >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_kitchen >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null
wait_for_home_status '
    .phase == "ready" and
    .busy == false and
    .busyEntityIds == [] and
    .entityErrors["light.fixture_kitchen"] == "action-failed" and
    (.entityErrors["light.fixture_hall"] // "") == "" and
    (.entities[] | select(.id == "light.fixture_hall") | .active == true)
' 'nonzero no-output Home action did not fail safely and continue the queue'

qs_for_test ipc call home-assistant fixture process-actions >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 88 >/dev/null
jq -e '
    .busyEntityIds == ["light.fixture_living"] and
    .pendingBrightness["light.fixture_living"] == 88
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'fixture transition setup did not start an in-flight action'
qs_for_test ipc call home-assistant fixture process-no-output >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null
wait_for_home_status '
    .phase == "ready" and
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    .entityErrors == {} and
    (.entities[] |
        select(.id == "light.fixture_living") |
        .active == false and .brightnessPct == 36) and
    (.entities[] | select(.id == "light.fixture_hall") | .active == true)
' 'switching process fixtures stranded or misattributed the new action'

qs_for_test ipc call home-assistant fixture process-delayed-exit >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 88 >/dev/null
wait_for_home_status '
    .busyEntityIds == ["light.fixture_living"] and
    .pendingBrightness["light.fixture_living"] == 88 and
    .actionProcessRunning == false and
    .actionStreamFinished == true
' 'nested fixture transition setup did not reach its delayed-exit window'
qs_for_test ipc call home-assistant fixture process-no-output >/dev/null
qs_for_test ipc call home-assistant fixture process-slow-actions >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_desk 57 >/dev/null
jq -e '
    .fixture == true and
    .fixtureTransitionDraining == true and
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    .entityErrors == {} and
    .queuedActionCount == 0 and
    .actionActive == false and
    .actionProcessRunning == false and
    (.entities[] |
        select(.id == "light.fixture_desk") |
        .active == true and .brightnessPct == 24)
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'nested fixture transition accepted an action during its drain'
wait_for_home_status '
    .fixture == true and
    .fixtureTransitionDraining == false and
    .busy == false and
    .queuedActionCount == 0 and
    .actionActive == false and
    .actionProcessRunning == false
' 'nested fixture transition did not install the latest stable fixture'
qs_for_test ipc call home-assistant brightness light.fixture_desk 57 >/dev/null
wait_for_home_status '
    .phase == "ready" and
    .fixtureTransitionDraining == false and
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    .entityErrors == {} and
    .queuedActionCount == 0 and
    .actionActive == false and
    (.entities[] |
        select(.id == "light.fixture_living") |
        .active == false and .brightnessPct == 36) and
    (.entities[] |
        select(.id == "light.fixture_desk") |
        .active == true and .brightnessPct == 57)
' 'nested fixture transitions released the drain or corrupted the latest action'

qs_for_test ipc call home-assistant fixture process-delayed-exit >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 88 >/dev/null
wait_for_home_status '
    .fixture == true and
    .busyEntityIds == ["light.fixture_living"] and
    .pendingBrightness["light.fixture_living"] == 88 and
    .actionProcessRunning == false and
    .actionStreamFinished == true
' 'reset-to-live setup did not reach its process drain window'
qs_for_test ipc call home-assistant reset >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 63 >/dev/null
jq -e '
    .fixture == true and
    .fixtureTransitionDraining == true and
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    .entityErrors == {} and
    .queuedActionCount == 0 and
    .actionActive == false and
    .actionProcessRunning == false
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'reset-to-live drain accepted an action from the old fixture'
wait_for_home_status '
    .fixture == false and
    .fixtureTransitionDraining == false and
    .busy == false and
    .busyEntityIds == [] and
    .pendingBrightness == {} and
    .entityErrors == {} and
    .queuedActionCount == 0 and
    .actionActive == false and
    .actionProcessRunning == false
' 'reset-to-live left stale fixture action state or started a replacement process'
if rg '^(toggle|brightness)( |$)' "$helper_log" >&2; then
    fail 'reset-to-live issued a stale fixture action to the live helper'
fi

qs_for_test ipc call home-assistant fixture missing-selected >/dev/null
jq -e '
    .fixture == true and
    .phase == "ready" and
    .discoveredCount == 7 and
    .configuredCount == 8 and
    (.entities[] |
        select(.id == "light.fixture_missing") |
        .sourceName == "fixture missing" and
        .name == "Porch" and
        .state == "unavailable" and
        .available == false and
        .active == false and
        .dimmable == false and
        .brightnessPct == 0)
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'missing Home selection was not retained as unavailable'

qs_for_test ipc call home-assistant fixture action-error >/dev/null
jq -e '
    .fixture == true and
    .phase == "ready" and
    .stale == false and
    .entityErrors["light.fixture_kitchen"] == "request-failed" and
    (.entityErrors["light.fixture_hall"] // "") == "" and
    .lastError == ""
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'Home action error was not isolated to one entity'

qs_for_test ipc call home-assistant fixture stale >/dev/null
jq -e '
    .fixture == true and
    .phase == "degraded" and
    .discoveredCount == 7 and
    .configuredCount == 7 and
    .visibleCount == 4 and
    .stale == true and
    .lastError == "unreachable"
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'stale Home fixture did not retain entities'

qs_for_test ipc call home-assistant fixture unavailable >/dev/null
jq -e '
    .fixture == true and
    .phase == "unavailable" and
    .discoveredCount == 0 and
    .configuredCount == 0 and
    .visibleCount == 0 and
    .lastError == "not-configured"
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
    || fail 'unavailable Home fixture is malformed'

trap - EXIT
cleanup
[[ ! -e "$state_home" ]] \
    || fail 'temporary Home preferences state was not removed after shell exit'
printf 'Control Center services contract: PASS\n'
