#!/usr/bin/env bash
# app-update — unattended update check+execute, run by app-update.timer.
#
# Usage: app-update [--config <path>] [--show-config] [-h|--help]
#
# Gates: runs `app-deploy --auto` only when AUTOUPDATE=on in the config (default
# off = inert, so the timer can be installed everywhere and enabled per instance
# with one config line), and only OUTSIDE the AUTOUPDATE_WORKHOURS window
# (default 'Mo-Fr 06:00-18:00'; empty = any hour). The timer ticks hourly, so a
# newly confirmed version is picked up within the hour — evenings, nights and
# weekends by default. All update logic lives in app-deploy --auto: version source
# (CSC-confirmed when an account is configured, else the public channel file),
# pre-update backup, deploy, CSC reporting. AUTOUPDATE_BACKUP=off skips the
# pre-update backup for the update path (honored by app-deploy --auto itself).
#
# Exit: 0 when disabled, inside working hours, or already up to date; non-zero
# when the deploy fails — including a CSC account with NO confirmed version
# (strict, by design).
#
# The unit runs as root (no sudo setup needed). Concurrent manual deploys are
# excluded by app-deploy's own lock.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# help = the header comment block above, printed until the first non-# line. The
# generic command names (app-update, app-deploy, …) are rewritten to THIS script's
# own name/prefix, so a renamed <product>-update — or a custom CLI sourcing the
# libs — prints the right names with no install-time edit. pfx='app' (a no-op)
# when not renamed.
usage() {
    local cmd pfx; cmd="$(basename -- "$0")"; pfx="${cmd%-update}"
    sed -n '2,${/^#/!q;s/^# \{0,1\}//;s/^#$//;p}' "$0" \
        | sed -E "s/app-(deploy|backup|health|update)/$pfx-\1/g"
}

# day abbreviation (Mo Tu We Th Fr Sa Su, case-insensitive) -> 1..7; '' if unknown
dow_num() {
    case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
        mo) echo 1 ;; tu) echo 2 ;; we) echo 3 ;; th) echo 4 ;;
        fr) echo 5 ;; sa) echo 6 ;; su) echo 7 ;; *) echo '' ;;
    esac
}

# True when now falls inside AUTOUPDATE_WORKHOURS ('<Day>[-<Day>] HH:MM-HH:MM';
# empty = never). Dies on a malformed spec — a silent mis-parse could otherwise
# run updates during working hours. NB: dies happen here, in the function body,
# not inside $(...) where they would be swallowed by the subshell.
in_work_hours() {
    [ -n "$AUTOUPDATE_WORKHOURS" ] || return 1
    local spec="$AUTOUPDATE_WORKHOURS" days times d1 d2 t1 t2 dow now
    local hint="(expected e.g. 'Mo-Fr 06:00-18:00')"
    days="${spec%% *}"; times="${spec#* }"
    [ "$days" != "$spec" ] || de_die "invalid AUTOUPDATE_WORKHOURS '$spec' $hint"
    if [ "${days#*-}" != "$days" ]; then
        d1="$(dow_num "${days%-*}")"; d2="$(dow_num "${days#*-}")"
    else
        d1="$(dow_num "$days")"; d2="$d1"
    fi
    [ -n "$d1" ] && [ -n "$d2" ] || de_die "invalid day(s) in AUTOUPDATE_WORKHOURS '$spec' $hint"
    [ "$d1" -le "$d2" ] || de_die "day range must not wrap in AUTOUPDATE_WORKHOURS '$spec' (use e.g. 'Mo-Su')"
    t1="${times%-*}"; t2="${times#*-}"
    case "$t1" in [0-2][0-9]:[0-5][0-9]) ;; *) de_die "invalid time '$t1' in AUTOUPDATE_WORKHOURS '$spec' $hint" ;; esac
    case "$t2" in [0-2][0-9]:[0-5][0-9]) ;; *) de_die "invalid time '$t2' in AUTOUPDATE_WORKHOURS '$spec' $hint" ;; esac
    [ "$t1" \< "$t2" ] || de_die "time range must not wrap in AUTOUPDATE_WORKHOURS '$spec'"
    dow="$(date +%u)"; now="$(date +%H:%M)"
    [ "$dow" -ge "$d1" ] && [ "$dow" -le "$d2" ] || return 1
    # zero-padded HH:MM compares correctly as a string: t1 <= now < t2
    { [ "$now" = "$t1" ] || [ "$now" \> "$t1" ]; } && [ "$now" \< "$t2" ]
}

SHOW_CONFIG=0
while [ $# -gt 0 ]; do
    case "$1" in
        --config)      CONFIG_FILE="$2"; shift ;;
        --config=*)    CONFIG_FILE="${1#*=}" ;;
        --show-config) SHOW_CONFIG=1 ;;
        -h|--help)     usage; exit 0 ;;
        *)             echo "Unknown option: $1" >&2; exit 2 ;;
    esac
    shift
done

. "$SCRIPT_DIR/common.sh"

[ "$SHOW_CONFIG" = 1 ] && { de_show_config; exit 0; }

if ! is_on "$AUTOUPDATE" AUTOUPDATE; then
    de_log "AUTOUPDATE is off; skipping (enable with AUTOUPDATE=on in the config)"
    exit 0
fi

if in_work_hours; then
    de_log "Inside working hours ($AUTOUPDATE_WORKHOURS); postponing update check"
    exit 0
fi

deploy_args=(--auto)
[ -n "${CONFIG_FILE:-}" ] && deploy_args+=(--config "$CONFIG_FILE")

de_log "Unattended update check ($(csc_enabled && echo 'CSC-confirmed version' || echo "public channel '$CHANNEL'"))"
exec "$SCRIPT_DIR/$PRODUCT-deploy" "${deploy_args[@]}"
