#!/bin/bash

# WebUI System Update Coordinator
# Coordinates the update of all system information

webuiSystemUpdate() {
    webuiSystemInfo
    webuiSystemDisk
    webuiSystemMemory
    webuiSystemMetrics
    webuiSystemUpdateCheck
    isSuccessful "System information updated!"
}

# ---------------------------------------------------------------------------
#  WebUI "Out of Date" detection
# ---------------------------------------------------------------------------
# Writes frontend/data/system/update_status.json so the dashboard + topbar can
# tell the user when their LibrePortal install is behind the upstream release.
#
# Detection mirrors the model the CLI updater already trusts (scripts/update):
# a git working copy at $script_dir tracking a remote branch. We `git fetch`
# (throttled — the network call is the expensive part) and compare the local
# HEAD to origin/<branch>. The local VERSION file vs the remote VERSION file
# turns the raw "N commits behind" into a friendly "v0.1.0 -> v0.2.0".
#
# The JSON is deliberately source-agnostic: when get.libreportal.org is ready
# in the website session, only the `latest_version` / `latest_commit` /
# `behind` values need to come from the HTTP endpoint instead of git — the
# frontend and the rest of this file stay the same.
#
# Pass "force" as $1 to bypass the fetch throttle (used by the manual
# "Check for updates" action: `libreportal update check`).
webuiSystemUpdateCheck() {
    local force_flag="$1"

    local repo_dir="${script_dir}"
    local system_dir="$containers_dir/libreportal/frontend/data/system"
    local final_file="${system_dir}/update_status.json"
    local stamp_file="${system_dir}/.update_check_stamp"
    # How long (seconds) a fetch result stays "fresh" before we hit the network
    # again. Overridable via CFG_UPDATE_CHECK_INTERVAL; defaults to 3 hours.
    local fetch_interval="${CFG_UPDATE_CHECK_INTERVAL:-10800}"

    createFolders "quiet" "$sudo_user_name" "$system_dir"

    local install_mode="${CFG_INSTALL_MODE:-git}"
    local git_updates="${CFG_GIT_UPDATES:-true}"
    local auto_updates="${CFG_GIT_AUTO_UPDATES:-false}"

    # Local version: VERSION file first, fall back to the latest reachable tag.
    local current_version=""
    if [[ -f "$repo_dir/VERSION" ]]; then
        current_version=$(tr -d ' \t\n\r' < "$repo_dir/VERSION")
    fi
    if [[ -z "$current_version" ]]; then
        current_version=$(runAsManager git -C "$repo_dir" describe --tags --abbrev=0 2>/dev/null)
    fi
    [[ -z "$current_version" ]] && current_version="unknown"

    # Atomic JSON writer. Args (positional):
    #   1 update_available  2 can_update  3 current_version  4 latest_version
    #   5 current_commit    6 latest_commit  7 behind  8 ahead  9 branch
    #   10 source           11 error_message (empty = null)
    #   12 footprint_update_needed (optional, default false) — the release bumps a
    #      root-owned component, so applying it needs a root re-install, not a
    #      manager-run `update apply`.
    _webuiWriteUpdateStatus() {
        local _update_available="$1" _can_update="$2"
        local _current_version="$3" _latest_version="$4"
        local _current_commit="$5" _latest_commit="$6"
        local _behind="$7" _ahead="$8" _branch="$9" _source="${10}" _error="${11}"
        local _footprint_needed="${12:-false}"

        local _error_json="null"
        [[ -n "$_error" ]] && _error_json="\"$_error\""

        local temp_file="$(mktemp)"
        cat << EOF > "$temp_file"
{
  "update_available": ${_update_available},
  "can_update": ${_can_update},
  "current_version": "${_current_version}",
  "latest_version": "${_latest_version}",
  "current_commit": "${_current_commit}",
  "latest_commit": "${_latest_commit}",
  "behind": ${_behind},
  "ahead": ${_ahead},
  "branch": "${_branch}",
  "install_mode": "${install_mode}",
  "git_updates_enabled": ${git_updates},
  "auto_updates": ${auto_updates},
  "source": "${_source}",
  "footprint_update_needed": ${_footprint_needed},
  "error": ${_error_json},
  "checked_at": "$(date -Iseconds)"
}
EOF
        if [ $? -eq 0 ]; then
            runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
        else
            rm -f "$temp_file" 2>/dev/null
        fi
    }

    # Release mode: compare the local VERSION against the channel's published
    # latest.json (no git). Same throttle + same JSON schema, source="release".
    if [[ "$install_mode" == "release" ]]; then
        local latest_version="$current_version" rel_error=""
        local do_fetch="false"
        if [[ "$force_flag" == "force" || ! -f "$stamp_file" ]]; then
            do_fetch="true"
        else
            local _now _last; _now=$(date +%s); _last=$(stat -c '%Y' "$stamp_file" 2>/dev/null || echo 0)
            (( _now - _last >= fetch_interval )) && do_fetch="true"
        fi
        local footprint_needed="false"
        if [[ "$do_fetch" == "true" ]] && declare -f lpReleaseLatestVersion >/dev/null 2>&1; then
            local _lv; _lv=$(lpReleaseLatestVersion 2>/dev/null)
            if [[ -n "$_lv" ]]; then
                latest_version="$_lv"
                runAsManager touch "$stamp_file" 2>/dev/null || touch "$stamp_file" 2>/dev/null
                # Does the published release bump the root-owned footprint? If so,
                # applying it needs a root re-install, not a WebUI `update apply`.
                if declare -f lpReleaseLatestFootprint >/dev/null 2>&1; then
                    local _rfp _ifp; _rfp=$(lpReleaseLatestFootprint 2>/dev/null); _ifp=$(lpInstalledFootprintVersion 2>/dev/null)
                    [[ -n "$_rfp" && "${_rfp:-0}" -gt "${_ifp:-0}" ]] && footprint_needed="true"
                fi
            else
                rel_error="Could not reach the update server."
            fi
        elif [[ -f "$final_file" ]]; then
            # Throttled: reuse the last-known latest + footprint flag (no flicker).
            local _prev; _prev=$(grep -oE '"latest_version"[^,]*' "$final_file" 2>/dev/null | head -1 | sed -E 's/.*"([^"]*)"$/\1/')
            [[ -n "$_prev" ]] && latest_version="$_prev"
            grep -q '"footprint_update_needed": true' "$final_file" 2>/dev/null && footprint_needed="true"
        fi
        local update_available="false"
        if declare -f lpVersionGt >/dev/null 2>&1 && lpVersionGt "$latest_version" "$current_version"; then
            update_available="true"
        fi
        # A footprint-bumping release can't be applied from the WebUI (needs root).
        local can_update="false"
        [[ "$git_updates" == "true" && "$footprint_needed" != "true" ]] && can_update="true"
        _webuiWriteUpdateStatus "$update_available" "$can_update" \
            "$current_version" "$latest_version" \
            "" "" "0" "0" "${CFG_RELEASE_CHANNEL:-stable}" "release" "$rel_error" "$footprint_needed"
        return 0
    fi

    # Not a git working copy, or a deliberately local install: we can't compare
    # against an upstream, so report "managed manually" rather than an error.
    if [[ ! -d "$repo_dir/.git" || "$install_mode" == "local" ]]; then
        _webuiWriteUpdateStatus "false" "false" \
            "$current_version" "$current_version" \
            "" "" "0" "0" "" "local" ""
        return 0
    fi

    local branch
    branch=$(runAsManager git -C "$repo_dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
    [[ -z "$branch" || "$branch" == "HEAD" ]] && branch="main"

    runAsManager git -C "$repo_dir" config core.fileMode false >/dev/null 2>&1

    # Decide whether to hit the network this run.
    local do_fetch="false"
    if [[ "$force_flag" == "force" || ! -f "$stamp_file" ]]; then
        do_fetch="true"
    else
        local _now _last
        _now=$(date +%s)
        _last=$(stat -c '%Y' "$stamp_file" 2>/dev/null || echo 0)
        (( _now - _last >= fetch_interval )) && do_fetch="true"
    fi

    local fetch_error=""
    if [[ "$do_fetch" == "true" ]]; then
        local _fetched="false"
        if [[ "$install_mode" == "git" && -n "$CFG_GIT_USER" && "$CFG_GIT_USER" != "empty" && "$CFG_GIT_USER" != "changeme" ]]; then
            if runAsManager git -C "$repo_dir" \
                -c "credential.helper=" \
                -c "credential.helper=!f() { echo username=$CFG_GIT_USER; echo password=$CFG_GIT_KEY; }; f" \
                fetch --quiet origin "$branch" >/dev/null 2>&1; then
                _fetched="true"
            fi
        else
            if runAsManager git -C "$repo_dir" fetch --quiet origin "$branch" >/dev/null 2>&1; then
                _fetched="true"
            fi
        fi

        if [[ "$_fetched" == "true" ]]; then
            runAsManager touch "$stamp_file" 2>/dev/null || touch "$stamp_file" 2>/dev/null
        else
            fetch_error="Could not reach the update server."
        fi
    fi

    # Compare local HEAD against the (possibly just-fetched) remote ref.
    local current_commit latest_commit behind ahead latest_version
    current_commit=$(runAsManager git -C "$repo_dir" rev-parse --short HEAD 2>/dev/null)
    latest_commit=$(runAsManager git -C "$repo_dir" rev-parse --short "refs/remotes/origin/$branch" 2>/dev/null)
    behind=$(runAsManager git -C "$repo_dir" rev-list --count "HEAD..refs/remotes/origin/$branch" 2>/dev/null)
    ahead=$(runAsManager git -C "$repo_dir" rev-list --count "refs/remotes/origin/$branch..HEAD" 2>/dev/null)
    [[ -z "$behind" ]] && behind=0
    [[ -z "$ahead" ]] && ahead=0
    [[ -z "$current_commit" ]] && current_commit="unknown"
    [[ -z "$latest_commit" ]] && latest_commit="$current_commit"

    latest_version=$(runAsManager git -C "$repo_dir" show "refs/remotes/origin/$branch:VERSION" 2>/dev/null | tr -d ' \t\n\r')
    [[ -z "$latest_version" ]] && latest_version="$current_version"

    local update_available="false"
    [[ "$behind" -gt 0 ]] && update_available="true"

    local can_update="false"
    [[ "$install_mode" == "git" && "$git_updates" == "true" ]] && can_update="true"

    _webuiWriteUpdateStatus "$update_available" "$can_update" \
        "$current_version" "$latest_version" \
        "$current_commit" "$latest_commit" \
        "$behind" "$ahead" "$branch" "git" "$fetch_error"
}
