#!/bin/bash

# WebUI App Updater — data generator
# ---------------------------------------------------------------------------
# Writes the read-only JSON the features/updater WebUI reads:
#   frontend/data/updater/generated/updates.json   (per-app version state)
#   frontend/data/updater/generated/cves.json      (per-app known CVEs)
#   frontend/data/updater/generated/history.json   (created/owned by `apply`)
#
# Run by `libreportal updater check` — manually, or automatically from the task
# processor's idle poll via `updater check auto`, which only falls through to a
# real scan when updates.json is older than CFG_UPDATER_SCAN_INTERVAL minutes.
# Kept OFF the regen path so a slow registry/scanner call never stalls the
# periodic WebUI refresh. The WebUI degrades gracefully when these files are
# absent (it derives the app list from the installed-apps data), so this
# generator is a bonus, never a dependency.
#
# Version + CVE discovery is intentionally pluggable: the loop below records
# each installed app's CURRENT image (from its compose file) and leaves clearly
# marked hooks for an available-version source (registry: `docker manifest` /
# skopoe) and a vulnerability scanner (trivy / grype). Until those are wired the
# output is honest: scanned=true, update_available=false, cves=[]. Always writes
# valid JSON even when data gathering fails — it must never emit a broken file.

webuiUpdaterScan() {
    local out_dir="$containers_dir/libreportal/frontend/data/updater/generated"
    runFileOp mkdir -p "$out_dir" 2>/dev/null || mkdir -p "$out_dir" 2>/dev/null

    local now; now="$(date -Iseconds 2>/dev/null || date)"

    # Installed apps from the apps DB (status=1). Fall back to listing config
    # dirs if the DB is unavailable, so we still produce useful output.
    local apps=()
    if command -v sqlite3 >/dev/null 2>&1 && [ -f "$docker_dir/$db_file" ]; then
        while IFS= read -r a; do [ -n "$a" ] && apps+=("$a"); done < <(
            runInstallOp sqlite3 "$docker_dir/$db_file" \
                "SELECT name FROM apps WHERE status=1 AND name!='libreportal';" 2>/dev/null
        )
    fi
    if [ ${#apps[@]} -eq 0 ]; then
        while IFS= read -r d; do
            local b; b="$(basename "$d")"
            [[ "$b" == "libreportal" ]] && continue
            [ -f "$d/$b.config" ] && apps+=("$b")
        done < <(runFileOp find "$containers_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
    fi

    # Build the per-app updates array.
    local entries="" first=1
    for app in "${apps[@]}"; do
        local image="" compose="$containers_dir/$app/docker-compose.yml"
        if [ -f "$compose" ]; then
            image="$(grep -m1 -E '^\s*image:' "$compose" 2>/dev/null | sed -E 's/^\s*image:\s*//; s/["'"'"']//g')"
        fi
        # --- available-version hook -------------------------------------------
        # Wire a registry check here (e.g. `docker manifest inspect`/skopeo) to
        # set available_image + update_available. Honest default: up to date.
        local available="$image" update_available="false"

        [ $first -eq 0 ] && entries+=","
        first=0
        entries+=$(cat <<EOF

    { "name": "$app", "displayName": "$app", "current_image": "$(printf '%s' "$image" | sed 's/"/\\"/g')", "current_version": "$(printf '%s' "${image##*:}" | sed 's/"/\\"/g')", "available_image": "$(printf '%s' "$available" | sed 's/"/\\"/g')", "available_version": "$(printf '%s' "${available##*:}" | sed 's/"/\\"/g')", "update_available": $update_available, "scanned": true, "last_checked": "$now" }
EOF
)
    done

    local tmp; tmp="$(mktemp)"
    cat > "$tmp" <<EOF
{ "generated_at": "$now", "apps": [${entries}
] }
EOF
    # Write as the container user that owns out_dir. cp'ing the manager-owned
    # mktemp would fail (the container user can't read a 600 /tmp file), so the
    # old `runFileOp cp || cp` fell through to a manager cp that EACCES'd on the
    # container-owned dir. runFileWrite reads the tmp in this (manager) shell and
    # tees it as the container user — works in both modes.
    runFileWrite "$out_dir/updates.json" < "$tmp"
    rm -f "$tmp"

    # CVE data — pluggable. Wire trivy/grype per image here and emit per-app
    # cves[]. Honest empty-but-valid default until a scanner is configured.
    if [ ! -f "$out_dir/cves.json" ]; then
        local ctmp; ctmp="$(mktemp)"
        printf '{ "generated_at": "%s", "apps": [], "totals": { "critical": 0, "high": 0, "medium": 0, "low": 0 } }\n' "$now" > "$ctmp"
        runFileWrite "$out_dir/cves.json" < "$ctmp"
        rm -f "$ctmp"
    fi
    # Ensure a valid (possibly empty) history file exists for the WebUI.
    if [ ! -f "$out_dir/history.json" ]; then
        local htmp; htmp="$(mktemp)"
        printf '{ "entries": [] }\n' > "$htmp"
        runFileWrite "$out_dir/history.json" < "$htmp"
        rm -f "$htmp"
    fi

    # Make the generated tree readable by the container user that serves /data.
    runFileOp chown -R "$docker_install_user":"$docker_install_user" "$out_dir" 2>/dev/null || true
    isSuccessful "App updater data refreshed (${#apps[@]} app(s))."
}
