#!/usr/bin/env bash
#
# Rugix OTA update reconciliation loop.

set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then
    echo "[ERROR] script must be run as root" >&2
    exit 1
fi

LOCKFILE="/var/lock/nexigon-rugix-ota.lock"
exec 200>"$LOCKFILE"
if ! flock -n 200; then
    echo "[ERROR] another instance of this script is already running" >&2
    exit 1
fi

OTA_STATUS='{}'

emit_event() {
    local message="$1"
    shift
    local args=()
    while [ $# -ge 2 ]; do
        args+=(--attribute "$1=$(jq -n --arg v "$2" '$v')")
        shift 2
    done
    nexigon-agent events emit --category "ota" "${args[@]}" "\"$message\"" || true
}

set_status() {
    local expr="$1"
    shift
    OTA_STATUS=$(echo "$OTA_STATUS" | jq "$@" "$expr")
    nexigon-agent device properties set "dev.nexigon.ota.status" "$OTA_STATUS" || true
}

record_failure() {
    local message="$1"
    shift
    echo "[ERROR] $message" >&2
    emit_event "update failed" "error" "$message" "$@"
    set_status '.state = "failed" | .failureCount = ((.failureCount // 0) + 1) | .lastError = $msg' \
        --arg msg "$message"
}

if [ -f /etc/rugix/system-build-info.json ]; then
    BUILD_INFO=$(cat /etc/rugix/system-build-info.json)
    SYSTEM_NAME=$(echo "$BUILD_INFO" | jq -r '.name')
    CURRENT_VERSION=$(echo "$BUILD_INFO" | jq -r '.release.version')
elif [ -f /etc/os-release ]; then
    source /etc/os-release
    SYSTEM_NAME="${IMAGE_ID:?IMAGE_ID not set in /etc/os-release}"
    CURRENT_VERSION="${IMAGE_VERSION:?IMAGE_VERSION not set in /etc/os-release}"
else
    echo "[ERROR] cannot determine system name and version" >&2
    exit 1
fi

echo "[INFO] system: $SYSTEM_NAME, version: $CURRENT_VERSION" >&2

OTA_CONFIG=$(cat /etc/nexigon-rugix-ota.json)

# Read security settings from the local config before cloud merge.
USE_BUNDLE_HASH=$(echo "$OTA_CONFIG" | jq -r '.rugix.useBundleHash // false')

if ! OTA_CONFIG_PROPERTY=$(nexigon-agent device properties get "dev.nexigon.ota.config"); then
    if [ ! -s /var/lib/nexigon/agent/credentials.json ] \
        && ! grep -Eq '^[[:space:]]*token[[:space:]]*=' /etc/nexigon/agent.toml; then
        echo "[INFO] Nexigon Agent is not paired; OTA remains idle" >&2
        exit 0
    fi

    record_failure "unable to read OTA configuration from Nexigon"
    exit 1
fi

if [ "$(echo "$OTA_CONFIG_PROPERTY" | jq -r '.result')" == "Found" ]; then
    echo "[INFO] merging device-specific OTA configuration" >&2
    OTA_CONFIG=$(echo "$OTA_CONFIG" "$(echo "$OTA_CONFIG_PROPERTY" | jq -r '.value')" | jq -s '.[0] * .[1]')
fi

OTA_STATUS_PROPERTY=$(nexigon-agent device properties get "dev.nexigon.ota.status")

if [ "$(echo "$OTA_STATUS_PROPERTY" | jq -r '.result')" == "Found" ]; then
    OTA_STATUS=$(echo "$OTA_STATUS_PROPERTY" | jq -r '.value')
else
    OTA_STATUS='{}'
fi

set_status '.currentVersion = $version | .config = $config' \
    --arg version "$CURRENT_VERSION" \
    --argjson config "$OTA_CONFIG"

RUGIX_SYSTEM_INFO=$(rugix-ctrl system info)
ACTIVE_GROUP=$(echo "$RUGIX_SYSTEM_INFO" | jq -r '.boot.activeGroup')
DEFAULT_GROUP=$(echo "$RUGIX_SYSTEM_INFO" | jq -r '.boot.defaultGroup')

PREV_STATE=$(echo "$OTA_STATUS" | jq -r '.state // "idle"')
TARGET_VERSION=$(echo "$OTA_STATUS" | jq -r '.targetVersion // empty')

if [ "$ACTIVE_GROUP" != "$DEFAULT_GROUP" ]; then
    echo "[INFO] committing system (active=$ACTIVE_GROUP, default=$DEFAULT_GROUP)" >&2
    emit_event "committing system" \
        "activeGroup" "$ACTIVE_GROUP" \
        "defaultGroup" "$DEFAULT_GROUP"
    set_status '.state = "committing"'

    if rugix-ctrl system commit; then
        emit_event "update completed"
        set_status '.state = "completed" | .failureCount = 0 | .lastError = null'
    fi
    exit 0
elif [ "$PREV_STATE" == "installing" ] || [ "$PREV_STATE" == "rebooting" ] || [ "$PREV_STATE" == "committing" ]; then
    if [ -n "$TARGET_VERSION" ] && [ "$TARGET_VERSION" != "$CURRENT_VERSION" ]; then
        record_failure "update rolled back (expected $TARGET_VERSION, running $CURRENT_VERSION)" \
            "targetVersion" "$TARGET_VERSION" \
            "currentVersion" "$CURRENT_VERSION"
        exit 0
    else
        echo "[INFO] update completed (committed externally)" >&2
        emit_event "update completed"
        set_status '.state = "completed" | .failureCount = 0 | .lastError = null'
        exit 0
    fi
fi

VERSION_PATH=$(echo "$OTA_CONFIG" | jq -r '.path')
if [ -z "$VERSION_PATH" ] || [ "$VERSION_PATH" = "null" ]; then
    echo "[INFO] no OTA repository path configured" >&2
    set_status '.state = "idle" | .targetVersion = null | .lastError = null'
    exit 0
fi

emit_event "checking for updates" \
    "path" "$VERSION_PATH" \
    "system" "$SYSTEM_NAME" \
    "currentVersion" "$CURRENT_VERSION"

if ! VERSION_INFO=$(nexigon-agent repositories versions info "$VERSION_PATH"); then
    record_failure "unable to resolve version at $VERSION_PATH"
    exit 1
fi

TARGET_VERSION=$(echo "$VERSION_INFO" | jq -r '.metadata.imageVersion // empty')

if [ "$TARGET_VERSION" == "$CURRENT_VERSION" ]; then
    echo "[INFO] no updates available" >&2
    emit_event "no update available" \
        "currentVersion" "$CURRENT_VERSION"
    set_status '.state = "completed" | .targetVersion = null | .failureCount = 0 | .lastError = null'
    exit 0
fi

BUNDLE_ASSET=$(echo "$VERSION_INFO" | jq -r \
    --arg f "$SYSTEM_NAME.rugixb" \
    '.assets[] | select(.filename == $f)')

if [ -z "$BUNDLE_ASSET" ]; then
    record_failure "bundle asset $SYSTEM_NAME.rugixb not found in version"
    exit 1
fi

INSTALL_ARGS=()
if [ "$USE_BUNDLE_HASH" == "true" ]; then
    BUNDLE_HASH=$(echo "$BUNDLE_ASSET" | jq -r '.metadata.rugix.bundleHash // empty')
    if [ -n "$BUNDLE_HASH" ]; then
        INSTALL_ARGS+=(--bundle-hash "$BUNDLE_HASH")
    fi
fi

emit_event "update available, installing" \
    "currentVersion" "$CURRENT_VERSION" \
    "targetVersion" "$TARGET_VERSION"

if [ "$ACTIVE_GROUP" == "a" ]; then
    TARGET_GROUP="b"
else
    TARGET_GROUP="a"
fi

echo "[INFO] installing update to group $TARGET_GROUP" >&2

emit_event "installing update" \
    "activeGroup" "$ACTIVE_GROUP" \
    "targetGroup" "$TARGET_GROUP"

BUNDLE_URL=$(nexigon-agent repositories issue-url \
    "$VERSION_PATH/$SYSTEM_NAME.rugixb" | jq -r '.url')

echo "[INFO] installing bundle from $BUNDLE_URL" >&2

set_status '.state = "installing" | .targetVersion = $version' \
    --arg version "$TARGET_VERSION"

if ! rugix-ctrl update install "${INSTALL_ARGS[@]}" --reboot no "$BUNDLE_URL"; then
    record_failure "rugix-ctrl update install failed"
    exit 1
fi

emit_event "update installed, rebooting"
set_status '.state = "rebooting"'

echo "[INFO] rebooting" >&2
rugix-ctrl system reboot --spare
