#!/bin/bash
set -euo pipefail

if [ "$(id -u)" != "0" ]; then
    echo "[ERROR] Tento skript musĂ­ bĂ˝t spuĹˇtÄ›n jako root (sudo)." >&2
    exit 1
fi

TERKOM_HOME="/opt/terkom-ng/terkom"
SCRIPT_DIR="$TERKOM_HOME/scripts"
DIAG_SCRIPT="$SCRIPT_DIR/terkom-diagnostic.sh"
PERSIST_DIR="$SCRIPT_DIR/diagnostic"
CONFIG_FILE="$TERKOM_HOME/config-local.yaml"
SERVICE_FILE="/etc/systemd/system/terkom-diagnostic.service"
TIMER_FILE="/etc/systemd/system/terkom-diagnostic.timer"
ACTIVE_PROFILE_FILE="$TERKOM_HOME/terkom-diagnostic.active-profile"
INSTALLER_VERSION="2.2.14"
AI_REPORT_MAIL_ENABLED="${AI_REPORT_MAIL_ENABLED:-false}"
# PĹ™idej ÄŤĂ­slo profilu do markeru â€” pokud soubor neexistuje, defaultuj na P1
_inst_profile="$(cat "$ACTIVE_PROFILE_FILE" 2>/dev/null | tr -dc '0-9' || true)"
if [ -z "$_inst_profile" ]; then
    _inst_profile="1"
fi
# FormĂˇt markeru: " DIAG_v{verze}_P{N}" â€” pĹ™idĂˇvĂˇ se do description v config-local.yaml
_ai_mail_mark="OFF"
case "$(printf '%s' "$AI_REPORT_MAIL_ENABLED" | tr '[:upper:]' '[:lower:]')" in
    true|1|yes|on) _ai_mail_mark="ON" ;;
esac
DIAG_MARK=" DIAG_v${INSTALLER_VERSION}_P${_inst_profile}_mail_${_ai_mail_mark}"
unset _ai_mail_mark
unset _inst_profile
INSTALL_PSQL="${INSTALL_PSQL:-true}"
INSTALL_DIAG_MODE="${DIAG_MODE:-NORMAL}"  # NORMAL | FAST | ULTRA
FORCE_REINSTALL="${FORCE_REINSTALL:-0}"
# DIAG_UPDATE_ONLY=1: pĹ™i pĹ™einstalaci nesahej na hlavnĂ­ Terkom sluĹľbu (pouĹľĂ­vĂˇ update_diagnostic.sh; vĂ˝chozĂ­=plnĂ˝ restart u instalace)
DIAG_UPDATE_ONLY="${DIAG_UPDATE_ONLY:-0}"

# PĹ™einstalace/aktualizace: sudo env FORCE_REINSTALL=1 bash install_diagnostic.sh
if [ "$FORCE_REINSTALL" != "1" ] && [ -f "$DIAG_SCRIPT" ] && [ -f "$SERVICE_FILE" ] && [ -f "$TIMER_FILE" ]; then
    if systemctl is-enabled --quiet terkom-diagnostic.timer 2>/dev/null; then
        echo "[INFO] Terkom Diagnostic je na tomto stroji jiĹľ nainstalovĂˇn a timer (terkom-diagnostic.timer) je zapnutĂ˝."
        echo "       Tento krok se pĹ™eskoÄŤil, aby se nepĹ™episovala provoznĂ­ konfigurace omylem."
        echo "       PĹ™einstalace anebo vynucenĂˇ aktualizace: export FORCE_REINSTALL=1  a  stejnĂ˝ pĹ™Ă­kaz znovu."
        exit 0
    fi
fi

if [ "$DIAG_UPDATE_ONLY" = "1" ] && [ "$FORCE_REINSTALL" = "1" ]; then
    echo "[INFO] Update reĹľim (DIAG_UPDATE_ONLY) â€” Terkom app se na konci nerestartuje, jen diagnostika a systemd."
fi
echo "[INFO] Installing Terkom Diagnostic V${INSTALLER_VERSION}..."

remount_rw() {
    mount -o remount,rw / 2>/dev/null || true
    mount -o remount,rw /boot 2>/dev/null || true
}
remount_ro() {
    sync || true
    mount -o remount,ro /boot 2>/dev/null || true
    mount -o remount,ro / 2>/dev/null || true
}

trap remount_ro EXIT
remount_rw
mkdir -p "$SCRIPT_DIR" "$PERSIST_DIR"

# Startup kontrola: zobrazĂ­, kterĂ© helper skripty jsou/chybĂ­ (informativnĂ­)
echo "[INFO] Kontrola helper skriptĹŻ na zaĹ™Ă­zenĂ­ pĹ™ed instalacĂ­:"
for _chk in diag_info.sh apply_diagnostic_profile.sh check_diagnostic.sh set_ai_report_mail.sh; do
    if [ -f "$SCRIPT_DIR/$_chk" ]; then
        echo "[OK]   $SCRIPT_DIR/$_chk"
    else
        echo "[??]   $SCRIPT_DIR/$_chk â€” chybĂ­ (bude staĹľen)"
    fi
done
unset _chk

# ÄŚekĂˇ, aĹľ uvolnĂ­ zĂˇmek apt/dpkg (jinak: E: Could not get lock /var/lib/dpkg/lock-frontend).
# APT_LOCK_WAIT_SECONDS â€” max. ÄŤekĂˇnĂ­ v sekundĂˇch (vĂ˝chozĂ­ 600).
wait_for_dpkg_unlock() {
    local waited=0
    local max="${APT_LOCK_WAIT_SECONDS:-600}"
    local step=5
    local busy
    while true; do
        busy=0
        if command -v fuser >/dev/null 2>&1; then
            fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 && busy=1
            fuser /var/lib/dpkg/lock >/dev/null 2>&1 && busy=1
        elif command -v lsof >/dev/null 2>&1; then
            lsof /var/lib/dpkg/lock-frontend >/dev/null 2>&1 && busy=1
            lsof /var/lib/dpkg/lock >/dev/null 2>&1 && busy=1
        else
            pidof apt-get apt dpkg >/dev/null 2>&1 && busy=1
            pgrep -x unattended-upgrade >/dev/null 2>&1 && busy=1
        fi
        if [ "$busy" -eq 0 ]; then
            return 0
        fi
        if [ "$waited" -ge "$max" ]; then
            return 1
        fi
        echo "[INFO] apt/dpkg zĂˇmek je obsazenĂ˝ (jinĂ˝ apt nebo unattended-upgrades?), ÄŤekĂˇm ${step}s... (${waited}/${max}s max)"
        sleep "$step"
        waited=$((waited + step))
    done
}

install_psql_client_if_missing() {
    if [ "$INSTALL_PSQL" != "true" ]; then
        echo "[INFO] INSTALL_PSQL=false, skipping PostgreSQL client install."
        return 0
    fi

    if command -v psql >/dev/null 2>&1; then
        echo "[INFO] psql already installed: $(psql --version 2>/dev/null || true)"
        return 0
    fi

    echo "[INFO] psql missing, installing postgresql-client..."

    if ! wait_for_dpkg_unlock; then
        echo "[WARN] NepodaĹ™ilo se zĂ­skat volnĂ˝ apt/dpkg zĂˇmek bÄ›hem ${APT_LOCK_WAIT_SECONDS:-600}s."
        echo "[WARN] PĹ™eskakuji instalaci postgresql-client. Po uvolnÄ›nĂ­ apt spusĹĄte: sudo apt-get update && sudo apt-get install -y postgresql-client python3-cryptography"
        echo "[WARN] Nebo celou diagnostiku znovu po skonÄŤenĂ­ unattended-upgrades / apt."
        return 0
    fi

    cp -n /etc/apt/sources.list /etc/apt/sources.list.terkom-diagnostic.bak || true

    if grep -q 'raspbian.raspberrypi.org/raspbian' /etc/apt/sources.list 2>/dev/null; then
        sed -i 's|http://raspbian.raspberrypi.org/raspbian|http://legacy.raspbian.org/raspbian|g' /etc/apt/sources.list
        sed -i 's|https://raspbian.raspberrypi.org/raspbian|http://legacy.raspbian.org/raspbian|g' /etc/apt/sources.list
        echo "[INFO] Switched Raspbian repo to legacy.raspbian.org for old Buster devices."
    fi

    if [ -f /etc/apt/sources.list.d/yarn.list ]; then
        cp -n /etc/apt/sources.list.d/yarn.list /etc/apt/sources.list.d/yarn.list.terkom-diagnostic.bak || true
        sed -i 's|^deb .*dl.yarnpkg.com|# &|' /etc/apt/sources.list.d/yarn.list
        echo "[INFO] Temporarily disabled Yarn repo to avoid missing GPG key blocking apt update."
    fi

    apt-get update --allow-releaseinfo-change --allow-releaseinfo-change-suite || \
    apt-get update -o Acquire::AllowReleaseInfoChange=true -o Acquire::AllowReleaseInfoChange::Suite=true
    DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-client python3-cryptography zip

    echo "[INFO] Installed: $(psql --version 2>/dev/null || true)"

    # Restore original apt files when backups exist
    if [ -f /etc/apt/sources.list.terkom-diagnostic.bak ]; then
        cp /etc/apt/sources.list.terkom-diagnostic.bak /etc/apt/sources.list
        echo "[INFO] Restored /etc/apt/sources.list from backup."
    fi
    if [ -f /etc/apt/sources.list.d/yarn.list.terkom-diagnostic.bak ]; then
        cp /etc/apt/sources.list.d/yarn.list.terkom-diagnostic.bak /etc/apt/sources.list.d/yarn.list
        echo "[INFO] Restored yarn.list from backup."
    fi
}

add_config_marker() {
    if [ ! -f "$CONFIG_FILE" ]; then
        echo "[WARN] config-local.yaml not found: $CONFIG_FILE"
        return 0
    fi
    # Detekce obou formĂˇtĹŻ: YAML (description:) i JSON ("description":)
    if ! grep -qE '"?description"?[[:space:]]*:' "$CONFIG_FILE"; then
        echo "[WARN] description: not found in config-local.yaml, marker not added."
        return 0
    fi
    local is_json=false
    grep -qE '"description"[[:space:]]*:' "$CONFIG_FILE" && is_json=true

    # NovĂ˝ formĂˇt DIAG_v â€” jen aktualizuj verzi/profil
    if grep -qE '"?description"?[[:space:]]*:.*DIAG_v' "$CONFIG_FILE"; then
        local cur
        cur="$(grep -E '"?description"?[[:space:]]*:' "$CONFIG_FILE" \
            | grep -oE 'DIAG_v[^ "]*' | head -1 || true)"
        local new_bare="${DIAG_MARK# }"
        if [ "$cur" = "$new_bare" ]; then
            echo "[INFO] Marker ${DIAG_MARK} je jiĹľ aktuĂˇlnĂ­ v config-local.yaml."; return 0
        fi
        local mark_bare="${DIAG_MARK# }"  # bez leading space
        sed -i "s/DIAG_v[^ \"']*/${mark_bare}/g" "$CONFIG_FILE"
        echo "[INFO] Marker aktualizovĂˇn na ${DIAG_MARK} v config-local.yaml."; return 0
    fi

    # StarĂ˝ formĂˇt ---DIA_v* â€” migruj na novĂ˝
    if grep -qE '"?description"?[[:space:]]*:.*---DIA_v' "$CONFIG_FILE"; then
        sed -i "s/---DIA_v[^ \"']*/${DIAG_MARK}/g" "$CONFIG_FILE"
        echo "[INFO] StarĂ˝ marker ---DIA_v* migrovĂˇn na novĂ˝ formĂˇt (${DIAG_MARK})."; return 0
    fi

    # ĂšplnÄ› starĂ˝ formĂˇt ---DIAGNOSTIKA â€” migruj
    if grep -qE '"?description"?[[:space:]]*:.*---DIAGNOSTIKA' "$CONFIG_FILE"; then
        sed -i "s/---DIAGNOSTIKA/${DIAG_MARK}/g" "$CONFIG_FILE"
        echo "[INFO] StarĂ˝ marker ---DIAGNOSTIKA migrovĂˇn na ${DIAG_MARK}."; return 0
    fi

    # Ĺ˝ĂˇdnĂ˝ marker â€” pĹ™idej
    if [ "$is_json" = "true" ]; then
        # JSON: "description": "VALUE" â†’ "description": "VALUE DIAG_vX_P1"
        sed -i "s/\\(\"description\"[^:]*:[^\"]*\"[^\"]*\\)\"/\\1${DIAG_MARK}\"/" "$CONFIG_FILE"
    else
        # YAML: description: VALUE â†’ description: VALUE DIAG_vX_P1
        sed -i "/^[[:space:]]*description:/ s|$|${DIAG_MARK}|" "$CONFIG_FILE"
    fi
    echo "[INFO] PĹ™idĂˇn marker ${DIAG_MARK} do config-local.yaml."
}

install_psql_client_if_missing
add_config_marker

cat > "$DIAG_SCRIPT" <<'DIAG'
#!/bin/bash
set -u

SCRIPT_VERSION="2.2.14"

# ============================================================
# Terkom Diagnostic V2.2
# Diagnostic + recovery watchdog for Terkom terminals.
# ============================================================

# === MONITORING WINDOW ===
MONITOR_ENABLED=true
MONITOR_DAYS="1,2,3,4,5"       # 1=pondÄ›lĂ­ ... 7=nedÄ›le
MONITOR_TIME_FROM="9:00"
MONITOR_TIME_TO="16:00"

# === TERKOM ===
SERVICE="service"
UI_URL="http://127.0.0.1:3000"
CONFIG_FILE="/opt/terkom-ng/terkom/config-local.yaml"

# === CHECKS ===
CHECK_TERKOM_SERVICE=true
CHECK_NODE=true
CHECK_CHROMIUM=true
CHECK_LOCAL_UI=true
CHECK_GATEWAY=true
CHECK_INTERNET_IP=true
CHECK_REMOTE_SERVER=true
CHECK_DB_TCP=true
CHECK_DB_SQL=true
CHECK_DB_CLIENTS=true

# Internet check â€” ping na server. Pouze informativnĂ­, neblokuje NETWORK_PROBLEM trigger.
# Pokud server neodpovĂ­dĂˇ na ICMP (ping), ale TCP funguje, je to OK.
INTERNET_IP="${INTERNET_IP:-devel.altisima.cz}"
REMOTE_HOST="devel.altisima.cz"
REMOTE_PORT="80"

# === LOGGING / STATE ===
STATE_DIR="/run/terkom-diagnostic"
SPOOL_DIR="$STATE_DIR/spool"
DB_HISTORY_DIR="$STATE_DIR/db-history"
LOCK_FILE="$STATE_DIR/diagnostic.lock"
RUNTIME_EVENT_DIR="$STATE_DIR/current-event"
PERSIST_DIR="/opt/terkom-ng/terkom/scripts/diagnostic"
DIAG_RETENTION_DAYS=31
DB_HISTORY_KEEP=5
DB_CONNECTION_WARN_PERCENT=85

# === PHASE 2 RECOVERY ===
DIAG_MODE="NORMAL"                  # NORMAL | FAST | ULTRA
RECOVERY_ENABLED=true
RECOVERY_PROFILE="normal"            # safe | normal | aggressive | custom
GRACE_SECONDS=5
SERVICE_RESTART_AFTER_FAILS=1
NETWORK_RESTART_AFTER_FAILS=2
REBOOT_AFTER_FAILS=3
SERVICE_RESTART_COOLDOWN_SECONDS=30
NETWORK_RESTART_COOLDOWN_SECONDS=45
KIOSK_RESTART_COOLDOWN_SECONDS=20
REBOOT_COOLDOWN_SECONDS=600
MIN_FAILURE_DURATION_BEFORE_REBOOT=45
SNAPSHOT_COOLDOWN_SECONDS=60
# Po N po sobÄ› jdoucĂ­ch selhĂˇnĂ­ch jen sĂ­ĹĄ (dead_terminal=false, network_problem=true, db=false) provĂ©st reboot.
# 0 = vypnuto (vĂ˝chozĂ­). DoporuÄŤeno N > NETWORK_RESTART_AFTER_FAILS, aby nejdĹ™Ă­v probÄ›hl restart sĂ­ĹĄovĂ˝ch sluĹľeb.
NETWORK_ONLY_REBOOT_AFTER_FAILS=0
MAX_SERVICE_RESTARTS_PER_INCIDENT=1
MAX_NETWORK_RESTARTS_PER_INCIDENT=2
MAX_KIOSK_RESTARTS_PER_INCIDENT=1
MAX_REBOOTS_PER_INCIDENT=1
DEAD_REBOOT_AFTER_FAILED_SERVICE_RESTART=true
LOCAL_UI_FAIL_FAST_REBOOT=true
REMOTE_TCP_NO_LOOP_RECOVERY=true

# === EXTERNAL EVENT NOTIFICATIONS ===
# SMTP: vĂ˝chozĂ­ MailerSend relay (DomĂ©ny â†’ SMTP); jinĂ˝ provider pĹ™es .terdiag-notify.env / YAML.
# HTTPS fallback: MailerSend API (Bearer, typicky port 443).
# FTP: same Fernet-encrypted credentials pattern as mail_a_ftp/FTP_checker.py
EVENT_NOTIFY_ENABLED=true
EVENT_EMAIL_ENABLED=false
DIRECT_EVENT_EMAIL_ENABLED=false
AI_REPORT_MAIL_ENABLED="${AI_REPORT_MAIL_ENABLED:-false}"
EMAIL_THROTTLE_SECONDS=600
EVENT_EMAIL_TO="podpora@altisima.cz"
SMTP_SERVER="smtp.mailersend.net"
SMTP_PORT="587"
SMTP_USER="MS_9m8SPC@test-2p0347z3zyklzdrn.mlsender.net"
SMTP_PASS="mssp.86tnLYv.k68zxl2nm7elj905.Ib2pcw7"
SMTP_SENDER="MS_9m8SPC@test-2p0347z3zyklzdrn.mlsender.net"
# MailerSend API token (app.mailersend.com â†’ Sending / API tokens). OdchozĂ­ SMTP blokovanĂ˝ â†’ HTTPS na port 443.
MAILERSEND_API_TOKEN=""
MAILERSEND_API_URL="https://api.mailersend.com/v1/email"
EVENT_FTP_ENABLED=true
FTP_SERVER="ftp.altisima.cz"
FTP_USER="zalohydat.cz"
FTP_PASS=""
FTP_PASS_ENC="gAAAAABn0td_PN72bjMcNYWX6ddlK0kcYmk2nKq9ew6RlR1h4nN4kqUFzCQrP43x-tC3aQe11eHXXarDWwQhT5B-hXfGc3JuAA=="
FTP_FERNET_KEY="nnc4kTrgXxKgEhAcXEpXzt6xwOD1kVY5UEwsmzfIZH0="
FTP_BASE_DIR="diagnostika"
FTP_PUBLIC_BASE_URL="ftp://ftp.altisima.cz/diagnostika"
NOTIFY_ENV_FILE="/opt/terkom-ng/terkom/.terdiag-notify.env"
# CentrĂˇlnĂ­ chovĂˇnĂ­ (lze nasadit z public serveru / Ansible â€” pĹ™epĂ­Ĺˇe vĂ˝chozĂ­ pĹ™ed apply_recovery)
RUNTIME_BEHAVIOR_FILE="/opt/terkom-ng/terkom/terkom-diagnostic.runtime.yaml"
# Jeden Ĺ™Ăˇdek: naposledy vypoÄŤtenĂ© jmĂ©no sloĹľky na FTP (v /run â€” vĹľdy zapisovatelnĂ©)
# Po restartu RPi se znovu naÄŤte z DB, pokud jde; cache pomĹŻĹľe, kdyĹľ v incidentu nenĂ­ k dispozici psql
IDENTITY_CACHE_FILE="$STATE_DIR/ftp_client_folder.sav"
# Maily: all = kaĹľdĂˇ hlĂˇĹˇenĂˇ udĂˇlost, reboot_only = jen kdyĹľ by tento bÄ›h spustil reboot, digest = lehkĂ© chyby jen do souboru (kritika/reboot stĂˇle mail)
EMAIL_POLICY="all"

FAIL_COUNT_FILE="$STATE_DIR/fail_count"
FIRST_FAIL_TS_FILE="$STATE_DIR/first_fail_ts"
LAST_SERVICE_RESTART_FILE="$STATE_DIR/last_service_restart"
LAST_NETWORK_RESTART_FILE="$STATE_DIR/last_network_restart"
LAST_KIOSK_RESTART_FILE="$STATE_DIR/last_kiosk_restart"
LAST_REBOOT_FILE="$STATE_DIR/last_reboot"
SERVICE_RESTART_COUNT_FILE="$STATE_DIR/service_restart_count"
NETWORK_RESTART_COUNT_FILE="$STATE_DIR/network_restart_count"
KIOSK_RESTART_COUNT_FILE="$STATE_DIR/kiosk_restart_count"
REBOOT_COUNT_FILE="$STATE_DIR/reboot_count"
LAST_SNAPSHOT_FILE="$STATE_DIR/last_snapshot"
LAST_EVENT_NAME_FILE="$STATE_DIR/last_event_name"
LAST_EVENT_FTP_URL_FILE="$STATE_DIR/last_event_ftp_url"
LAST_EVENT_FTP_DIR_URL_FILE="$STATE_DIR/last_event_ftp_dir_url"
LAST_INCIDENT_NOTIFY_PREFIX="$STATE_DIR/last_incident_notify"
DEAD_FAIL_COUNT_FILE="$STATE_DIR/dead_fail_count"
NETWORK_FAIL_COUNT_FILE="$STATE_DIR/network_fail_count"
DB_FAIL_COUNT_FILE="$STATE_DIR/db_fail_count"

mkdir -p "$STATE_DIR" "$SPOOL_DIR" "$DB_HISTORY_DIR"

log() {
    local msg="$1"
    logger -t terkom-diagnostic "$msg"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $msg"
}

exec 9>"$LOCK_FILE"
if ! flock -n 9; then
    log "JinĂˇ instance diagnostic uĹľ bÄ›ĹľĂ­, konÄŤĂ­m."
    exit 0
fi

time_to_minutes() {
    local t="$1" h m
    h="${t%:*}"
    m="${t#*:}"
    echo $((10#$h * 60 + 10#$m))
}

is_monitoring_window() {
    if [ "$MONITOR_ENABLED" != "true" ]; then return 0; fi
    local day now_hm now_min from_min to_min
    day="$(date +%u)"
    case ",$MONITOR_DAYS," in *",$day,"*) ;; *) return 1 ;; esac
    now_hm="$(date '+%H:%M')"
    now_min="$(time_to_minutes "$now_hm")"
    from_min="$(time_to_minutes "$MONITOR_TIME_FROM")"
    to_min="$(time_to_minutes "$MONITOR_TIME_TO")"
    [ "$now_min" -ge "$from_min" ] && [ "$now_min" -le "$to_min" ]
}

apply_recovery_profile() {
    case "$RECOVERY_PROFILE" in
        safe)
            SERVICE_RESTART_AFTER_FAILS=2
            NETWORK_RESTART_AFTER_FAILS=3
            REBOOT_AFTER_FAILS=5
            SERVICE_RESTART_COOLDOWN_SECONDS=60
            NETWORK_RESTART_COOLDOWN_SECONDS=90
            REBOOT_COOLDOWN_SECONDS=1800
            MIN_FAILURE_DURATION_BEFORE_REBOOT=120
            SNAPSHOT_COOLDOWN_SECONDS=120
            ;;
        normal)
            SERVICE_RESTART_AFTER_FAILS=1
            NETWORK_RESTART_AFTER_FAILS=2
            REBOOT_AFTER_FAILS=3
            SERVICE_RESTART_COOLDOWN_SECONDS=30
            NETWORK_RESTART_COOLDOWN_SECONDS=45
            REBOOT_COOLDOWN_SECONDS=600
            MIN_FAILURE_DURATION_BEFORE_REBOOT=45
            SNAPSHOT_COOLDOWN_SECONDS=60
            ;;
        aggressive)
            SERVICE_RESTART_AFTER_FAILS=1
            NETWORK_RESTART_AFTER_FAILS=1
            REBOOT_AFTER_FAILS=2
            SERVICE_RESTART_COOLDOWN_SECONDS=20
            NETWORK_RESTART_COOLDOWN_SECONDS=25
            REBOOT_COOLDOWN_SECONDS=300
            MIN_FAILURE_DURATION_BEFORE_REBOOT=30
            SNAPSHOT_COOLDOWN_SECONDS=30
            ;;
        custom)
            # Keep manually configured thresholds as-is.
            ;;
        *)
            log "VAROVĂNĂŤ: NeznĂˇmĂ˝ RECOVERY_PROFILE='$RECOVERY_PROFILE', pouĹľĂ­vĂˇm normal."
            RECOVERY_PROFILE="normal"
            apply_recovery_profile
            ;;
    esac
}

apply_mode_profile() {
    # DIAG_MODE controls aggressiveness (detection cadence via timer + runtime thresholds).
    local mode
    mode="$(echo "${DIAG_MODE:-NORMAL}" | tr '[:lower:]' '[:upper:]')"
    DIAG_MODE="$mode"
    # Pokud runtime.yaml nastavĂ­ lock_recovery_thresholds: true, nech prahy i throttle z runtime / notify (email_throttle_seconds v YAML pĹ™epĂ­Ĺˇe .env v load_runtime).
    if [ "${LOCK_RECOVERY_THRESHOLDS:-0}" = "1" ]; then
        case "$mode" in NORMAL|FAST|ULTRA) ;; *) DIAG_MODE="NORMAL" ;; esac
        return 0
    fi
    case "$mode" in
        NORMAL)
            # Keep values from selected RECOVERY_PROFILE.
            [ -z "${EMAIL_THROTTLE_SECONDS:-}" ] && EMAIL_THROTTLE_SECONDS=600
            ;;
        FAST)
            NETWORK_RESTART_AFTER_FAILS=1
            REBOOT_AFTER_FAILS=2
            SERVICE_RESTART_COOLDOWN_SECONDS=15
            NETWORK_RESTART_COOLDOWN_SECONDS=20
            REBOOT_COOLDOWN_SECONDS=300
            MIN_FAILURE_DURATION_BEFORE_REBOOT=20
            SNAPSHOT_COOLDOWN_SECONDS=20
            EMAIL_THROTTLE_SECONDS=300
            ;;
        ULTRA)
            NETWORK_RESTART_AFTER_FAILS=1
            REBOOT_AFTER_FAILS=1
            SERVICE_RESTART_COOLDOWN_SECONDS=10
            NETWORK_RESTART_COOLDOWN_SECONDS=10
            REBOOT_COOLDOWN_SECONDS=120
            MIN_FAILURE_DURATION_BEFORE_REBOOT=10
            SNAPSHOT_COOLDOWN_SECONDS=10
            EMAIL_THROTTLE_SECONDS=120
            ;;
        *)
            log "VAROVĂNĂŤ: NeznĂˇmĂ˝ DIAG_MODE='$DIAG_MODE', pouĹľĂ­vĂˇm NORMAL."
            DIAG_MODE="NORMAL"
            ;;
    esac
}

yaml_get_root_key() {
    local key="$1"
    grep -E "^[[:space:]]*$key:[[:space:]]*" "$CONFIG_FILE" 2>/dev/null \
        | head -n 1 \
        | sed -E "s/^[[:space:]]*$key:[[:space:]]*//" \
        | tr -d '"' \
        | xargs
}

yaml_get_notification_key() {
    local key="$1"
    sed -n '/^[[:space:]]*terdiag_notify:[[:space:]]*$/,/^[^[:space:]]/p' "$CONFIG_FILE" 2>/dev/null \
        | grep -E "^[[:space:]]*$key:[[:space:]]*" \
        | head -n 1 \
        | sed -E "s/^[[:space:]]*$key:[[:space:]]*//" \
        | tr -d '"' \
        | xargs
}

load_notification_config() {
    if [ -f "$NOTIFY_ENV_FILE" ]; then
        # shellcheck disable=SC1090
        . "$NOTIFY_ENV_FILE"
    fi

    [ -f "$CONFIG_FILE" ] || return 0

    local v
    v="$(yaml_get_notification_key event_notify_enabled)"; [ -n "$v" ] && EVENT_NOTIFY_ENABLED="$v"
    v="$(yaml_get_notification_key event_email_enabled)"; [ -n "$v" ] && EVENT_EMAIL_ENABLED="$v"
    v="$(yaml_get_notification_key direct_event_email_enabled)"; [ -n "$v" ] && DIRECT_EVENT_EMAIL_ENABLED="$v"
    v="$(yaml_get_notification_key ai_report_mail_enabled)"; [ -n "$v" ] && AI_REPORT_MAIL_ENABLED="$v"
    v="$(yaml_get_notification_key mail_enabled)"; [ -n "$v" ] && AI_REPORT_MAIL_ENABLED="$v"
    v="$(yaml_get_notification_key diag_mode)"; [ -n "$v" ] && DIAG_MODE="$v"
    v="$(yaml_get_notification_key email_throttle_seconds)"; [ -n "$v" ] && EMAIL_THROTTLE_SECONDS="$v"
    v="$(yaml_get_notification_key event_email_to)"; [ -n "$v" ] && EVENT_EMAIL_TO="$v"
    v="$(yaml_get_notification_key smtp_server)"; [ -n "$v" ] && SMTP_SERVER="$v"
    v="$(yaml_get_notification_key smtp_port)"; [ -n "$v" ] && SMTP_PORT="$v"
    v="$(yaml_get_notification_key smtp_user)"; [ -n "$v" ] && SMTP_USER="$v"
    v="$(yaml_get_notification_key smtp_pass)"; [ -n "$v" ] && SMTP_PASS="$v"
    v="$(yaml_get_notification_key smtp_sender)"; [ -n "$v" ] && SMTP_SENDER="$v"
    v="$(yaml_get_notification_key mailersend_api_token)"; [ -n "$v" ] && MAILERSEND_API_TOKEN="$v"
    v="$(yaml_get_notification_key mailersend_api_url)"; [ -n "$v" ] && MAILERSEND_API_URL="$v"
    v="$(yaml_get_notification_key event_ftp_enabled)"; [ -n "$v" ] && EVENT_FTP_ENABLED="$v"
    v="$(yaml_get_notification_key ftp_server)"; [ -n "$v" ] && FTP_SERVER="$v"
    v="$(yaml_get_notification_key ftp_user)"; [ -n "$v" ] && FTP_USER="$v"
    v="$(yaml_get_notification_key ftp_pass)"; [ -n "$v" ] && FTP_PASS="$v"
    v="$(yaml_get_notification_key ftp_pass_enc)"; [ -n "$v" ] && FTP_PASS_ENC="$v"
    v="$(yaml_get_notification_key ftp_fernet_key)"; [ -n "$v" ] && FTP_FERNET_KEY="$v"
    v="$(yaml_get_notification_key ftp_base_dir)"; [ -n "$v" ] && FTP_BASE_DIR="$v"
    v="$(yaml_get_notification_key ftp_public_base_url)"; [ -n "$v" ] && FTP_PUBLIC_BASE_URL="$v"
    v="$(yaml_get_notification_key network_only_reboot_after_fails)"; [ -n "$v" ] && NETWORK_ONLY_REBOOT_AFTER_FAILS="$v"
    v="$(yaml_get_notification_key email_policy)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"; [ -n "$v" ] && case "$v" in all|reboot_only|digest) EMAIL_POLICY="$v" ;; esac

    if [ "$EVENT_NOTIFY_ENABLED" != "true" ]; then
        if [ "$EVENT_EMAIL_ENABLED" = "true" ] || [ "$EVENT_FTP_ENABLED" = "true" ]; then
            EVENT_NOTIFY_ENABLED=true
        fi
    fi

    # Backward compatibility aliases in .terdiag-notify.env
    [ -n "${EMAIL_SENDER:-}" ] && SMTP_SENDER="$EMAIL_SENDER"
    [ -n "${EMAIL_RECEIVER:-}" ] && EVENT_EMAIL_TO="$EMAIL_RECEIVER"
    [ -n "${EMAIL_USER:-}" ] && SMTP_USER="$EMAIL_USER"
    [ -n "${EMAIL_PASS:-}" ] && SMTP_PASS="$EMAIL_PASS"
}

# Block terkom_diagnostic: v terkom-diagnostic.runtime.yaml (nasazenĂ­ z centrĂˇlnĂ­ho serveru)
yaml_get_runtime_key() {
    local key="$1"
    [ -f "$RUNTIME_BEHAVIOR_FILE" ] || return 1
    sed -n '/^[[:space:]]*terkom_diagnostic:[[:space:]]*$/,/^[^[:space:]]/p' "$RUNTIME_BEHAVIOR_FILE" 2>/dev/null \
        | grep -E "^[[:space:]]*${key}:[[:space:]]*" \
        | head -n 1 \
        | sed -E "s/^[[:space:]]*${key}:[[:space:]]*//" \
        | tr -d '"' \
        | xargs
}

# Funkce 1/2/3: response (rychlost) â†’ recovery (politika) â†’ email_policy, volitelnĂ© pĹ™epsĂˇnĂ­ prahĹŻ
load_runtime_behavior() {
    [ -f "$RUNTIME_BEHAVIOR_FILE" ] || return 0
    local v sp
    v="$(yaml_get_runtime_key email_policy)"
    v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$v" ] && case "$v" in all|reboot_only|digest) EMAIL_POLICY="$v" ;; esac

    v="$(yaml_get_runtime_key response)"
    v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    case "$v" in
        relaxed) DIAG_MODE="NORMAL"; RECOVERY_PROFILE="safe"; EMAIL_THROTTLE_SECONDS="${EMAIL_THROTTLE_SECONDS:-900}" ;;
        balanced) ;;
        reactive) DIAG_MODE="FAST"; RECOVERY_PROFILE="aggressive"; ;;
    esac

    sp="$(yaml_get_runtime_key recovery)"
    sp="$(echo "$sp" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$sp" ] && case "$sp" in safe|normal|aggressive|custom) RECOVERY_PROFILE="$sp" ;; esac

    v="$(yaml_get_runtime_key diag_mode)"; v="$(echo "$v" | tr '[:lower:]' '[:upper:]' | xargs)"
    [ -n "$v" ] && case "$v" in NORMAL|FAST|ULTRA) DIAG_MODE="$v" ;; esac

    v="$(yaml_get_runtime_key recovery_profile)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$v" ] && case "$v" in safe|normal|aggressive|custom) RECOVERY_PROFILE="$v" ;; esac

    v="$(yaml_get_runtime_key service_restart_after_fails)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && SERVICE_RESTART_AFTER_FAILS="$v"
    v="$(yaml_get_runtime_key network_restart_after_fails)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && NETWORK_RESTART_AFTER_FAILS="$v"
    v="$(yaml_get_runtime_key reboot_after_fails)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && REBOOT_AFTER_FAILS="$v"
    v="$(yaml_get_runtime_key service_restart_cooldown_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && SERVICE_RESTART_COOLDOWN_SECONDS="$v"
    v="$(yaml_get_runtime_key network_restart_cooldown_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && NETWORK_RESTART_COOLDOWN_SECONDS="$v"
    v="$(yaml_get_runtime_key kiosk_restart_cooldown_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && KIOSK_RESTART_COOLDOWN_SECONDS="$v"
    v="$(yaml_get_runtime_key reboot_cooldown_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && REBOOT_COOLDOWN_SECONDS="$v"
    v="$(yaml_get_runtime_key min_failure_duration_before_reboot)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && MIN_FAILURE_DURATION_BEFORE_REBOOT="$v"
    v="$(yaml_get_runtime_key snapshot_cooldown_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && SNAPSHOT_COOLDOWN_SECONDS="$v"
    v="$(yaml_get_runtime_key network_only_reboot_after_fails)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && NETWORK_ONLY_REBOOT_AFTER_FAILS="$v"
    v="$(yaml_get_runtime_key max_service_restarts_per_incident)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && MAX_SERVICE_RESTARTS_PER_INCIDENT="$v"
    v="$(yaml_get_runtime_key max_network_restarts_per_incident)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && MAX_NETWORK_RESTARTS_PER_INCIDENT="$v"
    v="$(yaml_get_runtime_key max_kiosk_restarts_per_incident)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && MAX_KIOSK_RESTARTS_PER_INCIDENT="$v"
    v="$(yaml_get_runtime_key max_reboots_per_incident)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && MAX_REBOOTS_PER_INCIDENT="$v"
    v="$(yaml_get_runtime_key dead_reboot_after_failed_service_restart)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$v" ] && case "$v" in true|false) DEAD_REBOOT_AFTER_FAILED_SERVICE_RESTART="$v" ;; esac
    v="$(yaml_get_runtime_key local_ui_fail_fast_reboot)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$v" ] && case "$v" in true|false) LOCAL_UI_FAIL_FAST_REBOOT="$v" ;; esac
    v="$(yaml_get_runtime_key remote_tcp_no_loop_recovery)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    [ -n "$v" ] && case "$v" in true|false) REMOTE_TCP_NO_LOOP_RECOVERY="$v" ;; esac
    v="$(yaml_get_runtime_key recovery_enabled)"; [ -n "$v" ] && RECOVERY_ENABLED="$v"
    v="$(yaml_get_runtime_key email_throttle_seconds)"; [ -n "$v" ] && [ "$v" -ge 0 ] 2>/dev/null && EMAIL_THROTTLE_SECONDS="$v"
    v="$(yaml_get_runtime_key lock_recovery_thresholds)"; v="$(echo "$v" | tr '[:upper:]' '[:lower:]' | xargs)"
    if [ "$v" = "true" ]; then LOCK_RECOVERY_THRESHOLDS=1; else LOCK_RECOVERY_THRESHOLDS=0; fi
    log "Runtime: naÄŤten $RUNTIME_BEHAVIOR_FILE (email_policy=${EMAIL_POLICY:-all} lock_thresholds=${LOCK_RECOVERY_THRESHOLDS:-0})."
}

resolve_checker_default_secrets() {
    # If FTP_PASS is empty but encrypted token exists, decrypt like FTP_checker.py
    if [ -z "${FTP_PASS:-}" ] && [ -n "${FTP_PASS_ENC:-}" ] && [ -n "${FTP_FERNET_KEY:-}" ]; then
        if command -v python3 >/dev/null 2>&1; then
            if python3 -c "import cryptography" >/dev/null 2>&1; then
                FTP_PASS="$(export KEY="$FTP_FERNET_KEY" TOKEN="$FTP_PASS_ENC"; python3 - <<'PY'
import os
from cryptography.fernet import Fernet
key = os.environ["KEY"].encode()
token = os.environ["TOKEN"].encode()
print(Fernet(key).decrypt(token).decode(), end="")
PY
)" || true
            else
                log "VAROVĂNĂŤ: python3-cryptography chybĂ­, nelze rozĹˇifrovat FTP_PASS_ENC. Nainstaluj: apt-get install -y python3-cryptography"
            fi
        fi
    fi
}

yaml_get_connection_key() {
    local key="$1"
    sed -n '/^[[:space:]]*connection:[[:space:]]*$/,/^[^[:space:]]/p' "$CONFIG_FILE" \
        | grep -E "^[[:space:]]*$key:[[:space:]]*" \
        | head -n 1 \
        | sed -E "s/^[[:space:]]*$key:[[:space:]]*//" \
        | tr -d '"' \
        | xargs
}

read_db_config() {
    if [ ! -f "$CONFIG_FILE" ]; then DB_CONFIG_OK=false; return 1; fi
    DB_HOST="$(yaml_get_connection_key host)"
    DB_PORT="$(yaml_get_connection_key port)"
    DB_USER="$(yaml_get_connection_key user)"
    DB_PASSWORD="$(yaml_get_connection_key password)"
    DB_NAME="$(yaml_get_connection_key database)"
    DB_APP_NAME="$(yaml_get_connection_key application_name)"
    if [ -z "${DB_HOST:-}" ] || [ -z "${DB_PORT:-}" ]; then DB_CONFIG_OK=false; return 1; fi
    DB_CONFIG_OK=true
    return 0
}

read_customer_context() {
    CUSTOMER_ID="unknown-customer"
    CLIENT_NAME="unknown-client"
    CUSTOMER_DESCRIPTION=""
    STOJAN_ID="${STOJAN_ID:-}"

    if [ -f "$CONFIG_FILE" ]; then
        local cid raw_description stojan
        cid="$(yaml_get_root_key customerId)"
        [ -n "$cid" ] && CUSTOMER_ID="$cid"
        raw_description="$(yaml_get_root_key description)"
        CUSTOMER_DESCRIPTION="$(echo "$raw_description" | sed 's/ DIAG_v[^ "]*//g' | sed 's/---DIA_v[^ ]*//g' | sed 's/---DIAGNOSTIKA//g' | xargs || true)"
        # PĹ™eÄŤti idStojanu pokud jeĹˇtÄ› nenĂ­ nastaveno z MAIN
        if [ -z "$STOJAN_ID" ]; then
            stojan="$(grep -iE '"?idStojanu"?[[:space:]]*:' "$CONFIG_FILE" 2>/dev/null \
                | grep -oE '[0-9]+' | head -1 || true)"
            [ -n "$stojan" ] && STOJAN_ID="$stojan"
        fi
    fi

    if command -v psql >/dev/null 2>&1 && [ "${DB_CONFIG_OK:-false}" = "true" ]; then
        local client_name
        client_name="$(psql_cmd "SELECT hodnota FROM parametry WHERE parametr='NameSubject' LIMIT 1;" 2>/dev/null | xargs || true)"
        if [ -z "$client_name" ]; then
            client_name="$(psql_cmd "SELECT hodnota FROM parametry WHERE parametr='AdrSubject' LIMIT 1;" 2>/dev/null | xargs || true)"
        fi
        [ -n "$client_name" ] && CLIENT_NAME="$client_name"
    fi
    persist_ftp_folder_identity
}

# SloĹľka na FTP: customerId + NameSubject (sanitizovanĂ©), uloĹľĂ­ se do IDENTITY_CACHE_FILE
build_ftp_client_folder() {
    local cid name total_max=24 maxname
    cid="$(echo "$CUSTOMER_ID" | tr -cd '[:alnum:]_.-')"
    maxname=$(( total_max - ${#cid} - 1 ))
    [ "$maxname" -lt 4 ] && maxname=4

    if [ -n "$CLIENT_NAME" ] && [ "$CLIENT_NAME" != "unknown-client" ]; then
        name="$(abbrev_client_name "$CLIENT_NAME" "$maxname")"
    elif [ -f "$IDENTITY_CACHE_FILE" ]; then
        echo "$(head -c 24 "$IDENTITY_CACHE_FILE" 2>/dev/null)"; return
    else
        echo "$cid"; return
    fi

    if [ -n "$name" ]; then
        echo "${cid}_${name}"
    else
        echo "$cid"
    fi
}

build_ftp_terminal_folder() {
    local terminal
    terminal="${TERMINAL_SLUG:-}"
    if [ -z "$terminal" ] && [ -n "${STOJAN_ID:-}" ]; then
        terminal="stojan_${STOJAN_ID}"
    fi
    if [ -z "$terminal" ]; then
        terminal="$(hostname 2>/dev/null || echo terminal)"
    fi
    terminal="$(sanitize_name "$terminal")"
    [ -n "$terminal" ] || terminal="terminal"
    echo "$terminal"
}

build_ftp_event_folder() {
    local event_name="${1:-}" client day terminal
    client="$(build_ftp_client_folder 2>/dev/null || true)"
    [ -n "$client" ] || client="$(sanitize_name "$CUSTOMER_ID")"
    [ -n "$client" ] || client="unknown-customer"

    case "$event_name" in
        ????-??-??_*) day="${event_name:0:10}" ;;
        *) day="$(date '+%Y-%m-%d')" ;;
    esac

    terminal="$(build_ftp_terminal_folder)"
    echo "${client}/${day}/${terminal}"
}

persist_ftp_folder_identity() {
    local s
    s="$(build_ftp_client_folder 2>/dev/null || true)"
    [ -n "$s" ] && [ "$s" != "unknown" ] && echo "$s" > "$IDENTITY_CACHE_FILE" 2>/dev/null || true
}

upload_ai_report_mail_flag() {
    [ "$EVENT_FTP_ENABLED" = "true" ] || return 0
    [ -n "${FTP_SERVER:-}" ] || return 0
    [ -n "${FTP_USER:-}" ] || return 0
    [ -n "${FTP_PASS:-}" ] || return 0

    local client flag
    client="$(build_ftp_client_folder 2>/dev/null || true)"
    [ -n "$client" ] || client="$(sanitize_name "$CUSTOMER_ID")"
    [ -n "$client" ] || client="unknown-customer"
    flag="FALSE"
    case "$(printf '%s' "${AI_REPORT_MAIL_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')" in
        true|1|yes|on) flag="TRUE" ;;
    esac

    FTP_SERVER_ENV="$FTP_SERVER" FTP_USER_ENV="$FTP_USER" FTP_PASS_ENV="$FTP_PASS" FTP_BASE_DIR_ENV="$FTP_BASE_DIR" FTP_CLIENT_FOLDER_ENV="$client" FTP_MAIL_FLAG_ENV="$flag" \
    python3 - <<'PY' >/dev/null 2>&1
import ftplib
import io
import os

server = os.environ.get("FTP_SERVER_ENV", "")
user = os.environ.get("FTP_USER_ENV", "")
pw = os.environ.get("FTP_PASS_ENV", "")
base = os.environ.get("FTP_BASE_DIR_ENV", "diagnostika")
client = os.environ.get("FTP_CLIENT_FOLDER_ENV", "unknown-customer")
flag = os.environ.get("FTP_MAIL_FLAG_ENV", "FALSE").strip().upper()

if server and user and pw and client:
    ftp = ftplib.FTP(server, timeout=30)
    ftp.login(user, pw)
    for part in [p for p in (base + "/" + client).split("/") if p]:
        try:
            ftp.cwd(part)
        except Exception:
            ftp.mkd(part)
            ftp.cwd(part)
    ftp.storbinary("STOR mail", io.BytesIO((flag + "\n").encode("utf-8")))
    ftp.quit()
PY
    if [ $? -eq 0 ]; then
        log "AI mail flag nahran na FTP: /${FTP_BASE_DIR#/}/$client/mail = $flag"
    else
        log "VAROVANI: Upload AI mail flagu na FTP selhal."
    fi
}

set_notify_env_key() {
    local key="$1" val="$2"
    touch "$NOTIFY_ENV_FILE" 2>/dev/null || return 0
    if grep -qE "^${key}=" "$NOTIFY_ENV_FILE" 2>/dev/null; then
        sed -i "s|^${key}=.*|${key}=${val}|" "$NOTIFY_ENV_FILE" 2>/dev/null || true
    else
        printf '%s=%s\n' "$key" "$val" >> "$NOTIFY_ENV_FILE" 2>/dev/null || true
    fi
    chmod 600 "$NOTIFY_ENV_FILE" 2>/dev/null || true
}

update_ai_mail_marker_runtime() {
    [ -f "$CONFIG_FILE" ] || return 0
    local flag_marker current_marker new_marker
    flag_marker="OFF"
    case "$(printf '%s' "${AI_REPORT_MAIL_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')" in
        true|1|yes|on) flag_marker="ON" ;;
    esac
    current_marker="$(grep -oE 'DIAG_v[^ "'"'"']+' "$CONFIG_FILE" 2>/dev/null | head -1 || true)"
    [ -n "$current_marker" ] || return 0
    case "$current_marker" in
        *_mail_ON|*_mail_OFF) new_marker="${current_marker%_mail_*}_mail_${flag_marker}" ;;
        *) new_marker="${current_marker}_mail_${flag_marker}" ;;
    esac
    if [ "$current_marker" != "$new_marker" ]; then
        sed -i "s/${current_marker}/${new_marker}/g" "$CONFIG_FILE" 2>/dev/null || true
        log "AI mail marker opraven podle FTP flagu: ${new_marker}"
    fi
}

sync_ai_report_mail_flag_from_ftp() {
    [ "$EVENT_FTP_ENABLED" = "true" ] || return 0
    [ -n "${FTP_SERVER:-}" ] || return 0
    [ -n "${FTP_USER:-}" ] || return 0
    [ -n "${FTP_PASS:-}" ] || return 0

    local client remote_flag wanted_bool current_bool
    client="$(build_ftp_client_folder 2>/dev/null || true)"
    [ -n "$client" ] || client="$(sanitize_name "$CUSTOMER_ID")"
    [ -n "$client" ] || return 0

    remote_flag="$(FTP_SERVER_ENV="$FTP_SERVER" FTP_USER_ENV="$FTP_USER" FTP_PASS_ENV="$FTP_PASS" FTP_BASE_DIR_ENV="$FTP_BASE_DIR" FTP_CLIENT_FOLDER_ENV="$client" python3 - <<'PY'
import ftplib
import os
import sys

server = os.environ.get("FTP_SERVER_ENV", "")
user = os.environ.get("FTP_USER_ENV", "")
pw = os.environ.get("FTP_PASS_ENV", "")
base = os.environ.get("FTP_BASE_DIR_ENV", "diagnostika")
client = os.environ.get("FTP_CLIENT_FOLDER_ENV", "")

try:
    ftp = ftplib.FTP(server, timeout=30)
    ftp.login(user, pw)
    chunks = []
    ftp.retrbinary(f"RETR /{base.strip('/')}/{client}/mail", chunks.append)
    ftp.quit()
    raw = b"".join(chunks).decode("utf-8", errors="replace").strip().splitlines()
    print(raw[0].strip().upper() if raw else "")
except ftplib.error_perm:
    sys.exit(2)
except Exception:
    sys.exit(1)
PY
)" || return 0
    case "$remote_flag" in
        TRUE) wanted_bool="true" ;;
        FALSE) wanted_bool="false" ;;
        *) return 0 ;;
    esac

    current_bool="false"
    case "$(printf '%s' "${AI_REPORT_MAIL_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')" in
        true|1|yes|on) current_bool="true" ;;
    esac

    if [ "$current_bool" != "$wanted_bool" ]; then
        AI_REPORT_MAIL_ENABLED="$wanted_bool"
        set_notify_env_key "AI_REPORT_MAIL_ENABLED" "$wanted_bool"
        update_ai_mail_marker_runtime
        log "AI mail flag synchronizovan z FTP: /${FTP_BASE_DIR#/}/$client/mail = $remote_flag"
    else
        update_ai_mail_marker_runtime
    fi
}

sanitize_name() {
    echo "$1" | tr ' /:' '___' | sed 's/\\/__/g' | tr -cd '[:alnum:]_.-'
}

# ZkrĂˇtĂ­ nĂˇzev klienta: odstranĂ­ diakritiku, slova zkrĂˇtĂ­ na max 4 znaky, max_len znakĹŻ celkem
abbrev_client_name() {
    local name="$1" maxlen="${2:-18}"
    local clean abbr result="" word
    clean="$(echo "$name" | iconv -f utf-8 -t ascii//TRANSLIT 2>/dev/null || echo "$name")"
    clean="$(echo "$clean" | tr -cd '[:alnum:] ' | xargs)"
    local compact="${clean// /}"
    if [ "${#compact}" -le "$maxlen" ]; then
        echo "$compact"; return
    fi
    for word in $clean; do
        word="$(echo "$word" | tr -cd '[:alnum:]')"
        [ -z "$word" ] && continue
        abbr="${word:0:4}"
        abbr="$(echo "${abbr:0:1}" | tr '[:lower:]' '[:upper:]')${abbr:1}"
        result="${result}${abbr}"
    done
    echo "${result:0:$maxlen}"
}

send_email_message() {
    [ "${DIRECT_EVENT_EMAIL_ENABLED:-false}" = "true" ] || return 0
    [ "$EVENT_EMAIL_ENABLED" = "true" ] || return 0
    [ -n "${EVENT_EMAIL_TO:-}" ] || { log "VAROVĂNĂŤ: Email notifikace â€” chybĂ­ EVENT_EMAIL_TO."; return 0; }

    if [ -z "${SMTP_SENDER:-}" ] && [ -n "${SMTP_USER:-}" ]; then SMTP_SENDER="$SMTP_USER"; fi
    if [ -n "$SMTP_SERVER" ] && [ -n "$SMTP_USER" ] && [ -n "$SMTP_PASS" ] && [ -n "$SMTP_SENDER" ]; then
        :
    elif [ -n "${MAILERSEND_API_TOKEN:-}" ]; then
        [ -n "${SMTP_SENDER:-}" ] || { log "VAROVĂNĂŤ: MailerSend API â€” chybĂ­ SMTP_SENDER."; return 0; }
    else
        log "VAROVĂNĂŤ: Email nenĂ­ nakonfigurovanĂ˝ (celĂ© SMTP nebo MAILERSEND_API_TOKEN+FROM)."
        return 0
    fi

    local subject="$1" body_file="$2" label="${3:-Email}"
    SMTP_SERVER_ENV="$SMTP_SERVER" SMTP_PORT_ENV="$SMTP_PORT" SMTP_USER_ENV="$SMTP_USER" SMTP_PASS_ENV="$SMTP_PASS" SMTP_SENDER_ENV="$SMTP_SENDER" SMTP_TO_ENV="$EVENT_EMAIL_TO" SMTP_SUBJECT_ENV="$subject" SMTP_BODY_FILE_ENV="$body_file" MAILERSEND_API_TOKEN_ENV="${MAILERSEND_API_TOKEN:-}" MAILERSEND_API_URL_ENV="${MAILERSEND_API_URL:-https://api.mailersend.com/v1/email}" \
    python3 - <<'PY' >/dev/null 2>&1
import json
import os
import smtplib
import sys
import urllib.request
from email.mime.text import MIMEText

server = os.environ.get("SMTP_SERVER_ENV", "")
port = int(os.environ.get("SMTP_PORT_ENV", "587") or "587")
user = os.environ.get("SMTP_USER_ENV", "")
pw = os.environ.get("SMTP_PASS_ENV", "")
sender = os.environ.get("SMTP_SENDER_ENV", "")
to = os.environ.get("SMTP_TO_ENV", "")
subject = os.environ.get("SMTP_SUBJECT_ENV", "TerDiag event")
body_file = os.environ.get("SMTP_BODY_FILE_ENV", "")
ms_token = os.environ.get("MAILERSEND_API_TOKEN_ENV", "").strip()
ms_url = os.environ.get("MAILERSEND_API_URL_ENV", "https://api.mailersend.com/v1/email").strip()

body = ""
if body_file and os.path.isfile(body_file):
    with open(body_file, "r", encoding="utf-8") as fh:
        body = fh.read()
body = body or "NovĂˇ TerDiag udĂˇlost."


def send_mailersend_api():
    if not ms_token:
        raise RuntimeError("missing MailerSend API token")
    if "@" not in sender:
        raise RuntimeError("invalid sender email for MailerSend API")
    payload = {
        "from": {"name": "TerDiag", "email": sender},
        "to": [{"email": to}],
        "subject": subject,
        "text": body,
    }
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        ms_url,
        data=data,
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer " + ms_token,
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        code = int(getattr(resp, "status", 0) or 0)
        if code and code >= 400:
            raise RuntimeError("MailerSend API HTTP %d" % (code,))


def send_smtp():
    if not (server and user and pw and sender):
        raise RuntimeError("incomplete SMTP configuration")
    msg = MIMEText(body)
    msg["Subject"] = subject
    msg["From"] = sender
    msg["To"] = to
    smtp = smtplib.SMTP(server, port, timeout=30)
    try:
        smtp.starttls()
        smtp.login(user, pw)
        smtp.sendmail(sender, [to], msg.as_string())
    finally:
        try:
            smtp.quit()
        except Exception:
            pass


try:
    smtp_ok = False
    smtp_err = None
    if server and user and pw and sender:
        try:
            send_smtp()
            smtp_ok = True
        except Exception as exc:
            smtp_ok = False
            smtp_err = exc
    if not smtp_ok:
        if ms_token:
            send_mailersend_api()
        elif smtp_err:
            raise smtp_err
        else:
            raise RuntimeError("SMTP nenĂ­ nakonfigurovanĂ© a chybĂ­ MAILERSEND_API_TOKEN pro HTTPS fallback.")
except Exception as exc:
    print(str(exc), file=sys.stderr)
    sys.exit(1)
PY
    if [ $? -eq 0 ]; then
        log "$label odeslĂˇn na: $EVENT_EMAIL_TO"
    else
        log "VAROVĂNĂŤ: $label selhal (SMTP i pĹ™Ă­padnĂ˝ MailerSend HTTPS fallback)."
    fi
}

send_event_notification() {
    [ "$EVENT_NOTIFY_ENABLED" = "true" ] || return 0
    command -v python3 >/dev/null 2>&1 || { log "VAROVĂNĂŤ: python3 nenĂ­ dostupnĂ˝, event notifikace pĹ™eskoÄŤena."; return 0; }

    local event_name snapshot_path reason_key ftp_event_folder ftp_url summary_file summary_line subject body tmp_mail
    local reason_file reason_key_safe last_notify_ts now_notify_ts
    event_name="$1"
    snapshot_path="$2"
    reason_key="${3:-${event_name#*_}}"
    ftp_event_folder="$(build_ftp_event_folder "$event_name")"
    summary_file="$snapshot_path/summary.txt"
    summary_line="$(grep -E '^VĂ˝sledek:|^Kategorie:|^- dead_terminal:|^- dead_reason:|^- network_problem:|^- db_problem:|^- Akce:' "$summary_file" 2>/dev/null || true)"
    [ -z "$summary_line" ] && summary_line="VĂ˝pis ze summary nenĂ­ dostupnĂ˝."

    ftp_url=""
    ftp_dir_url=""
    local ftp_base_for_url
    ftp_base_for_url="/${FTP_BASE_DIR#/}"
    if [ -n "$FTP_PUBLIC_BASE_URL" ]; then
        ftp_dir_url="${FTP_PUBLIC_BASE_URL%/}/$ftp_event_folder/"
    else
        ftp_dir_url="ftp://$FTP_SERVER${ftp_base_for_url%/}/$ftp_event_folder/"
    fi
    if [ "$EVENT_FTP_ENABLED" = "true" ] && [ -n "$FTP_SERVER" ] && [ -n "$FTP_USER" ] && [ -n "$FTP_PASS" ]; then
        sync_ai_report_mail_flag_from_ftp || true
        upload_ai_report_mail_flag || true
        local archive_file archive_name archive_ext
        if command -v zip >/dev/null 2>&1; then
            archive_ext="zip"
            archive_file="$STATE_DIR/${event_name}.zip"
            (cd "$snapshot_path" && zip -qr "$archive_file" .) 2>/dev/null || true
        else
            archive_ext="tar"
            archive_file="$STATE_DIR/${event_name}.tar"
            tar -cf "$archive_file" -C "$snapshot_path" . 2>/dev/null || true
        fi
        archive_name="${event_name}.${archive_ext}"
        if [ -s "$archive_file" ]; then
            FTP_SERVER_ENV="$FTP_SERVER" FTP_USER_ENV="$FTP_USER" FTP_PASS_ENV="$FTP_PASS" FTP_BASE_DIR_ENV="$FTP_BASE_DIR" FTP_EVENT_FOLDER_ENV="$ftp_event_folder" ARCHIVE_FILE_ENV="$archive_file" ARCHIVE_NAME_ENV="$archive_name" \
            python3 - <<'PY' >/dev/null 2>&1
import ftplib, os
server = os.environ.get("FTP_SERVER_ENV", "")
user = os.environ.get("FTP_USER_ENV", "")
pw = os.environ.get("FTP_PASS_ENV", "")
base = os.environ.get("FTP_BASE_DIR_ENV", "/diagnostika")
event_folder = os.environ.get("FTP_EVENT_FOLDER_ENV", "unknown-customer")
archive_file = os.environ.get("ARCHIVE_FILE_ENV", "")
archive_name = os.environ.get("ARCHIVE_NAME_ENV", "event.zip")
if server and user and pw and archive_file:
    ftp = ftplib.FTP(server, timeout=30)
    ftp.login(user, pw)
    for part in [p for p in (base + "/" + event_folder).split("/") if p]:
        try:
            ftp.cwd(part)
        except Exception:
            ftp.mkd(part)
            ftp.cwd(part)
    with open(archive_file, "rb") as fh:
        ftp.storbinary(f"STOR {archive_name}", fh)
    ftp.quit()
PY
            if [ $? -eq 0 ]; then
                ftp_url="${ftp_dir_url}${archive_name}"
                log "Event archiv nahrĂˇn na FTP: $ftp_url"
            else
                log "VAROVĂNĂŤ: Upload event archivu na FTP selhal."
            fi
            rm -f "$archive_file"
        fi
    fi

    local subject_desc subject_name incident_start_ts incident_start_human now_human ongoing_duration
    subject_desc="$CUSTOMER_DESCRIPTION"
    [ -z "$subject_desc" ] && subject_desc="bez-popisu"
    subject_name="$CLIENT_NAME"
    [ -z "$subject_name" ] && subject_name="unknown-client"
    incident_start_ts="$(get_file_num "$FIRST_FAIL_TS_FILE")"
    incident_start_human="$(format_ts_human "$incident_start_ts")"
    now_human="$(date '+%Y-%m-%d %H:%M:%S %z')"
    ongoing_duration="$(format_duration_hms "$(( $(now_ts) - incident_start_ts ))")"
    subject="[TerDiag - ${subject_name} | ${subject_desc} | ${DECISION_ACTION}]"
    tmp_mail="$STATE_DIR/mail_body_${event_name}.txt"
    {
        echo "NovĂˇ diagnostickĂˇ udĂˇlost byla detekovĂˇna."
        echo
        echo "CustomerId: $CUSTOMER_ID"
        echo "Klient: $CLIENT_NAME"
        echo "UdĂˇlost: $event_name"
        echo "Typ incidentu: $reason_key"
        echo "Popis chyby: $DECISION_REASON"
        echo "RozhodnutĂ­: $DECISION_ACTION"
        echo "PoÄŤet pokusĹŻ (celkem): ${FAIL_COUNT:-0}"
        echo "PoÄŤet pokusĹŻ dead/network/db: ${DEAD_FAIL_COUNT:-0}/${NETWORK_FAIL_COUNT:-0}/${DB_FAIL_COUNT:-0}"
        echo "ÄŚas vzniku incidentu: $incident_start_human"
        echo "ÄŚas odeslĂˇnĂ­ notifikace: $now_human"
        echo "Doba trvĂˇnĂ­ incidentu zatĂ­m: $ongoing_duration"
        echo
        echo "StruÄŤnĂ˝ pĹ™ehled:"
        echo "$summary_line"
        echo
        if [ -n "$ftp_url" ]; then
            echo "FTP sloĹľka klienta: $ftp_dir_url"
            echo "PĹ™Ă­mĂ˝ FTP link na archiv: $ftp_url"
        else
            echo "FTP sloĹľka klienta: $ftp_dir_url"
            echo "PĹ™Ă­mĂ˝ FTP link na archiv: nebyl nahrĂˇn nebo FTP nenĂ­ zapnutĂ©."
        fi
        echo "LokĂˇlnĂ­ snapshot: $snapshot_path"
    } > "$tmp_mail"

    echo "$event_name" > "$LAST_EVENT_NAME_FILE"
    echo "$ftp_url" > "$LAST_EVENT_FTP_URL_FILE"
    echo "$ftp_dir_url" > "$LAST_EVENT_FTP_DIR_URL_FILE"

    if [ "${EMAIL_POLICY:-all}" = "digest" ] && ! would_reboot_soon; then
        { echo "$(date '+%Y-%m-%d %H:%M:%S') | customerId=$CUSTOMER_ID | $DECISION_ACTION | $REASON | $event_name"
        } >> "$STATE_DIR/digest_inbox.log" 2>/dev/null || true
        log "EMAIL_POLICY=digest: udĂˇlost zapsĂˇna do $STATE_DIR/digest_inbox.log (bez e-mailu â€” tento bÄ›h neeskaluje na reboot)."
        rm -f "$tmp_mail"
        return 0
    fi
    if [ "${EMAIL_POLICY:-all}" = "reboot_only" ] && ! would_reboot_soon; then
        log "EMAIL_POLICY=reboot_only: odeslĂˇnĂ­ e-mailu pĹ™eskoÄŤeno (v tomto bÄ›hu nenĂ­ plĂˇnovĂˇn reboot dle prahĹŻ)."
        rm -f "$tmp_mail"
        return 0
    fi

    reason_key_safe="$(sanitize_name "$reason_key")"
    reason_file="${LAST_INCIDENT_NOTIFY_PREFIX}_${reason_key_safe}.ts"
    now_notify_ts="$(now_ts)"
    last_notify_ts="$(get_file_num "$reason_file")"
    if [ "$last_notify_ts" -gt 0 ] 2>/dev/null && [ $((now_notify_ts-last_notify_ts)) -lt "${EMAIL_THROTTLE_SECONDS:-600}" ]; then
        log "Email throttle: incident '$reason_key' uĹľ byl notifikovĂˇn pĹ™ed $((now_notify_ts-last_notify_ts))s (< ${EMAIL_THROTTLE_SECONDS:-600}s), email pĹ™eskoÄŤen."
        rm -f "$tmp_mail"
        return 0
    fi

    send_email_message "$subject" "$tmp_mail" "Event email"
    set_file_num "$reason_file" "$now_notify_ts"
    rm -f "$tmp_mail"
}

send_resolution_notification() {
    [ "$EVENT_NOTIFY_ENABLED" = "true" ] || return 0
    command -v python3 >/dev/null 2>&1 || return 0

    local start_ts start_human end_ts end_human duration duration_hms event_name ftp_url ftp_dir_url subject tmp_mail
    start_ts="$(get_file_num "$FIRST_FAIL_TS_FILE")"
    [ "$start_ts" -gt 0 ] 2>/dev/null || return 0
    end_ts="$(now_ts)"
    start_human="$(format_ts_human "$start_ts")"
    end_human="$(format_ts_human "$end_ts")"
    duration="$((end_ts-start_ts))"
    duration_hms="$(format_duration_hms "$duration")"
    event_name="$( [ -f "$LAST_EVENT_NAME_FILE" ] && cat "$LAST_EVENT_NAME_FILE" 2>/dev/null || echo "unknown-event" )"
    ftp_url="$( [ -f "$LAST_EVENT_FTP_URL_FILE" ] && cat "$LAST_EVENT_FTP_URL_FILE" 2>/dev/null || true )"
    ftp_dir_url="$( [ -f "$LAST_EVENT_FTP_DIR_URL_FILE" ] && cat "$LAST_EVENT_FTP_DIR_URL_FILE" 2>/dev/null || true )"

    local fslug
    fslug="$(build_ftp_event_folder "$event_name" 2>/dev/null || true)"
    if [ -z "$ftp_dir_url" ] && [ -n "${FTP_PUBLIC_BASE_URL:-}" ]; then
        ftp_dir_url="${FTP_PUBLIC_BASE_URL%/}/$fslug/"
    fi
    if [ -z "$ftp_dir_url" ]; then
        ftp_dir_url="ftp://$FTP_SERVER/${FTP_BASE_DIR#/}/$fslug/"
    fi

    local res_desc
    res_desc="$CUSTOMER_DESCRIPTION"
    [ -z "$res_desc" ] && res_desc="bez-popisu"
    subject="[TerDiag - UZAVĹENO | $CLIENT_NAME | $res_desc]"
    tmp_mail="$STATE_DIR/mail_resolved_${end_ts}.txt"
    {
        echo "Incident byl vyĹ™eĹˇen."
        echo
        echo "CustomerId: $CUSTOMER_ID"
        echo "Klient: $CLIENT_NAME"
        echo "PoslednĂ­ udĂˇlost: $event_name"
        echo "ÄŚas vzniku incidentu: $start_human"
        echo "ÄŚas vyĹ™eĹˇenĂ­ incidentu: $end_human"
        echo "Doba trvĂˇnĂ­: ${duration_hms} (${duration}s)"
        echo
        echo "FTP sloĹľka klienta: $ftp_dir_url"
        if [ -n "$ftp_url" ]; then
            echo "PĹ™Ă­mĂ˝ FTP link na poslednĂ­ archiv: $ftp_url"
        else
            echo "PĹ™Ă­mĂ˝ FTP link na poslednĂ­ archiv: nenĂ­ dostupnĂ˝."
        fi
    } > "$tmp_mail"

    send_email_message "$subject" "$tmp_mail" "Resolution email"
    rm -f "$tmp_mail"
}

tcp_check() {
    local host="$1" port="$2"
    timeout 3 bash -c "echo > /dev/tcp/$host/$port" >/dev/null 2>&1
}

get_default_gateway() { ip route 2>/dev/null | awk '/default/ {print $3; exit}'; }

safe_bool() {
    if [ "$1" = "true" ]; then echo "OK"; elif [ "$1" = "skip" ]; then echo "SKIP"; else echo "FAIL"; fi
}

pool_status() {
    if [ "${DB_SQL_OK:-skip}" = "skip" ]; then echo "SKIP"; elif [ "${DB_POOL_EXHAUSTED:-false}" = "true" ]; then echo "FAIL"; else echo "OK"; fi
}

psql_cmd() {
    local sql="$1"
    PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -A -c "$sql"
}

save_db_history_check() {
    [ "${DB_CONFIG_OK:-false}" = "true" ] || return 0
    command -v psql >/dev/null 2>&1 || return 0
    [ "${DB_SQL_OK:-skip}" = "true" ] || return 0

    local ts file total max usage
    ts="$(date '+%Y-%m-%d_%H-%M-%S')"
    file="$DB_HISTORY_DIR/${ts}.txt"

    total="$(psql_cmd "SELECT count(*) FROM pg_stat_activity;" 2>/dev/null | xargs || true)"
    max="$(psql_cmd "SELECT setting FROM pg_settings WHERE name='max_connections';" 2>/dev/null | xargs || true)"

    if [ -n "$total" ] && [ -n "$max" ] && [ "$max" -gt 0 ] 2>/dev/null; then usage=$(( total * 100 / max )); else usage="unknown"; fi

    {
        echo "DB HISTORY CHECK"
        echo "Diagnostic version: $SCRIPT_VERSION"
        echo
        echo "ÄŚas: $(date '+%Y-%m-%d %H:%M:%S')"
        echo "DB: $DB_HOST:$DB_PORT/$DB_NAME"
        echo "User: $DB_USER"
        echo "Application name: ${DB_APP_NAME:-unknown}"
        echo
        echo "SELECT 1: OK"
        echo "Connections current: ${total:-unknown}"
        echo "Connections max: ${max:-unknown}"
        echo "Connections usage percent: ${usage}"
        echo
        echo "Connections by state:"
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COALESCE(state,'unknown') AS state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count(*) DESC;" 2>/dev/null || true
        echo
        if [ "$usage" != "unknown" ] && [ "$usage" -ge "$DB_CONNECTION_WARN_PERCENT" ] 2>/dev/null; then
            echo "WARNING: DB connection usage is high: ${usage}%"
        fi
    } > "$file"

    ls -1t "$DB_HISTORY_DIR"/*.txt 2>/dev/null | tail -n +$((DB_HISTORY_KEEP + 1)) | xargs -r rm -f
}

detect_power_cut() {
    POWER_CUT=false
    POWER_CUT_CLEAN_BOOT=false
    # /run je tmpfs â€” smaĹľe se pĹ™i kaĹľdĂ©m rebootu.
    # Pokud lock soubor neexistuje, jsme prvnĂ­ run po rebootu.
    local lock_file="/run/terkom-diagnostic-boot.lock"
    if [ ! -f "$lock_file" ]; then
        touch "$lock_file" 2>/dev/null || true
        # Zkontroluj, jestli pĹ™edchozĂ­ boot skonÄŤil ÄŤistÄ› (systemd shutdown)
        local clean
        clean=$(journalctl --boot=-1 --no-pager 2>/dev/null \
            | grep -cE 'Reached target.*[Ss]hutdown|Reached target.*[Rr]eboot|Halting system|Rebooting\.|systemd-halt|systemd-reboot' \
            || true)
        if [ "${clean:-0}" -eq 0 ] 2>/dev/null; then
            POWER_CUT=true
        else
            POWER_CUT_CLEAN_BOOT=true
        fi
    fi
}

run_checks() {
    TERKOM_SERVICE_OK=skip; NODE_OK=skip; CHROMIUM_OK=skip; LOCAL_UI_OK=skip
    GATEWAY_OK=skip; INTERNET_OK=skip; REMOTE_DNS_OK=skip; REMOTE_TCP_OK=skip
    DB_TCP_OK=skip; DB_SQL_OK=skip; DB_POOL_EXHAUSTED=false; DB_SQL_ERROR=""
    POWER_CUT=false; POWER_CUT_CLEAN_BOOT=false
    DEAD_REASON="none"
    DEFAULT_GATEWAY="$(get_default_gateway)"
    detect_power_cut

    if [ "$CHECK_TERKOM_SERVICE" = "true" ]; then systemctl is-active --quiet "$SERVICE" && TERKOM_SERVICE_OK=true || TERKOM_SERVICE_OK=false; fi
    if [ "$CHECK_NODE" = "true" ]; then pgrep -af "node run.js" >/dev/null && NODE_OK=true || NODE_OK=false; fi
    if [ "$CHECK_CHROMIUM" = "true" ]; then pgrep -af "chromium|chromium-browser" >/dev/null && CHROMIUM_OK=true || CHROMIUM_OK=false; fi
    if [ "$CHECK_LOCAL_UI" = "true" ]; then curl -fsS --max-time 3 "$UI_URL" >/dev/null 2>&1 && LOCAL_UI_OK=true || LOCAL_UI_OK=false; fi

    if [ "$CHECK_GATEWAY" = "true" ]; then [ -n "$DEFAULT_GATEWAY" ] && ping -c 1 -W 1 "$DEFAULT_GATEWAY" >/dev/null 2>&1 && GATEWAY_OK=true || GATEWAY_OK=false; fi
    if [ "$CHECK_INTERNET_IP" = "true" ]; then ping -c 1 -W 1 "$INTERNET_IP" >/dev/null 2>&1 && INTERNET_OK=true || INTERNET_OK=false; fi
    if [ "$CHECK_REMOTE_SERVER" = "true" ]; then
        getent hosts "$REMOTE_HOST" >/dev/null 2>&1 && REMOTE_DNS_OK=true || REMOTE_DNS_OK=false
        tcp_check "$REMOTE_HOST" "$REMOTE_PORT" && REMOTE_TCP_OK=true || REMOTE_TCP_OK=false
    fi

    if read_db_config; then
        if [ "$CHECK_DB_TCP" = "true" ]; then tcp_check "$DB_HOST" "$DB_PORT" && DB_TCP_OK=true || DB_TCP_OK=false; fi
        if [ "$CHECK_DB_SQL" = "true" ]; then
            if command -v psql >/dev/null 2>&1; then
                local tmpout
                tmpout="/tmp/terkom_diag_psql_out.$$"
                DB_SQL_ERROR="$(PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT 1;" 2>&1 >"$tmpout" || true)"
                if grep -q '^1$' "$tmpout" 2>/dev/null; then DB_SQL_OK=true; else DB_SQL_OK=false; fi
                echo "$DB_SQL_ERROR" | grep -qi "too many clients" && DB_POOL_EXHAUSTED=true
                rm -f "$tmpout"
            else
                DB_SQL_OK=skip
                DB_SQL_ERROR="psql nenĂ­ dostupnĂ˝ - DB SQL check pĹ™eskoÄŤen"
            fi
        fi
    else
        DB_TCP_OK=skip
        DB_SQL_OK=skip
        DB_SQL_ERROR="NepodaĹ™ilo se naÄŤĂ­st DB config: $CONFIG_FILE â€” DB checks pĹ™eskoÄŤeny."
        log "VAROVĂNĂŤ: $DB_SQL_ERROR"
    fi

    save_db_history_check

    DEAD_TERMINAL=false; NETWORK_PROBLEM=false; DB_PROBLEM=false
    if [ "$TERKOM_SERVICE_OK" = "false" ] || [ "$NODE_OK" = "false" ] || [ "$CHROMIUM_OK" = "false" ] || [ "$LOCAL_UI_OK" = "false" ]; then DEAD_TERMINAL=true; fi
    if [ "$DEAD_TERMINAL" = "true" ]; then
        DEAD_REASON=""
        [ "$TERKOM_SERVICE_OK" = "false" ] && DEAD_REASON="${DEAD_REASON}terkom_service "
        [ "$NODE_OK" = "false" ] && DEAD_REASON="${DEAD_REASON}node_process "
        [ "$CHROMIUM_OK" = "false" ] && DEAD_REASON="${DEAD_REASON}chromium_process "
        [ "$LOCAL_UI_OK" = "false" ] && DEAD_REASON="${DEAD_REASON}local_ui "
        DEAD_REASON="$(echo "$DEAD_REASON" | xargs)"
        [ -n "$DEAD_REASON" ] || DEAD_REASON="unknown"
    fi
    # INTERNET_OK (ping na INTERNET_IP) je pouze informativnĂ­ â€” nezpĹŻsobuje NETWORK_PROBLEM.
    # SĂ­tÄ› mohou blokovat ICMP (8.8.8.8, devel.altisima.cz), ale TCP funguje.
    # NETWORK_PROBLEM nastane jen pĹ™i selhĂˇnĂ­ gateway, remote DNS nebo remote TCP.
    if [ "$GATEWAY_OK" = "false" ] || [ "$REMOTE_DNS_OK" = "false" ] || [ "$REMOTE_TCP_OK" = "false" ]; then NETWORK_PROBLEM=true; fi
    if [ "$DB_TCP_OK" = "false" ] || [ "$DB_SQL_OK" = "false" ] || [ "$DB_POOL_EXHAUSTED" = "true" ]; then DB_PROBLEM=true; fi
    # power_cut je detekovĂˇn samostatnÄ› (detect_power_cut vĂ˝Ĺˇe)
}

pure_chromium_problem() {
    [ "${CHROMIUM_OK:-skip}" = "false" ] \
        && [ "${TERKOM_SERVICE_OK:-skip}" = "true" ] \
        && [ "${NODE_OK:-skip}" = "true" ] \
        && [ "${LOCAL_UI_OK:-skip}" = "true" ] \
        && [ "${NETWORK_PROBLEM:-false}" = "false" ] \
        && [ "${DB_PROBLEM:-false}" = "false" ]
}

get_decision() {
    if pure_chromium_problem; then
        DECISION_ACTION="RESTART_KIOSK"
        DECISION_REASON="Selhal pouze proces Chromium/kiosk, backend i lokalni UI odpovidaji."
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "false" ]; then DECISION_ACTION="OK"; DECISION_REASON="VĹˇechny sledovanĂ© ÄŤĂˇsti jsou v poĹ™Ăˇdku."
    elif [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "false" ]; then DECISION_ACTION="RESTART_TERKOM"; DECISION_REASON="TerminĂˇl mĂˇ lokĂˇlnĂ­ problĂ©m, sĂ­ĹĄ i DB vypadajĂ­ dostupnÄ›."
    elif [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then DECISION_ACTION="RESTART_NETWORK"; DECISION_REASON="LokĂˇlnĂ­ Terkom bÄ›ĹľĂ­, ale je problĂ©m se sĂ­ĹĄovou konektivitou."
        if [ "${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0}" -gt 0 ] 2>/dev/null; then DECISION_REASON="${DECISION_REASON} (VolitelnĂ˝ sĂ­ĹĄ-only reboot po ${NETWORK_ONLY_REBOOT_AFTER_FAILS} po sobÄ› jdoucĂ­ch sĂ­ĹĄovĂ˝ch selhĂˇnĂ­ch â€” viz NETWORK_ONLY_REBOOT_AFTER_FAILS.)"; fi
    elif [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then DECISION_ACTION="DB_DIAG_AND_TERKOM_RECONNECT"; DECISION_REASON="SĂ­ĹĄ i lokĂˇlnĂ­ Terkom vypadajĂ­ OK, ale DB mĂˇ problĂ©m."
    elif [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then DECISION_ACTION="RESTART_NETWORK_THEN_TERKOM_THEN_REBOOT"; DECISION_REASON="TerminĂˇl mĂˇ lokĂˇlnĂ­ problĂ©m a zĂˇroveĹ problĂ©m se sĂ­tĂ­."
    elif [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then DECISION_ACTION="RESTART_TERKOM_THEN_REBOOT"; DECISION_REASON="TerminĂˇl mĂˇ lokĂˇlnĂ­ problĂ©m a zĂˇroveĹ DB problĂ©m."
    elif [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "true" ]; then DECISION_ACTION="RESTART_NETWORK_NO_REBOOT"; DECISION_REASON="LokĂˇlnĂ­ Terkom bÄ›ĹľĂ­, ale sĂ­ĹĄ/DB majĂ­ problĂ©m. Reboot nenĂ­ povolen bez dead_terminal."
    else DECISION_ACTION="RESTART_NETWORK_TERKOM_THEN_REBOOT"; DECISION_REASON="SelhĂˇvĂˇ lokĂˇlnĂ­ terminĂˇl, sĂ­ĹĄ i DB. To mĹŻĹľe odpovĂ­dat lokĂˇlnĂ­mu zamrznutĂ­ nebo ĹˇirĹˇĂ­mu vĂ˝padku."
    fi
}

create_snapshot_runtime() {
    local reason="$1" ts
    ts="$(date '+%Y-%m-%d_%H-%M-%S')"
    EVENT_NAME="${ts}_${reason}"
    EVENT_DIR="$RUNTIME_EVENT_DIR/$EVENT_NAME"
    rm -rf "$RUNTIME_EVENT_DIR"
    mkdir -p "$EVENT_DIR/raw" "$EVENT_DIR/raw/db-history"
    cp -a "$DB_HISTORY_DIR/"*.txt "$EVENT_DIR/raw/db-history/" 2>/dev/null || true

    cat > "$EVENT_DIR/summary.txt" <<EOF
TERKOM DIAGNOSTIC REPORT

Diagnostic version: $SCRIPT_VERSION
ÄŚas: $(date '+%Y-%m-%d %H:%M:%S')
Hostname: $(hostname)
MonitorovacĂ­ okno: aktivnĂ­
VĂ˝sledek: PROBLĂ‰M DETEKOVĂN

Kategorie:
- dead_terminal: $DEAD_TERMINAL
- dead_reason: ${DEAD_REASON:-none}
- network_problem: $NETWORK_PROBLEM
- db_problem: $DB_PROBLEM
- power_cut: $POWER_CUT

Incidentove zasahy:
- service_restarts: $(incident_count "$SERVICE_RESTART_COUNT_FILE") / ${MAX_SERVICE_RESTARTS_PER_INCIDENT:-1}
- network_restarts: $(incident_count "$NETWORK_RESTART_COUNT_FILE") / ${MAX_NETWORK_RESTARTS_PER_INCIDENT:-2}
- kiosk_restarts: $(incident_count "$KIOSK_RESTART_COUNT_FILE") / ${MAX_KIOSK_RESTARTS_PER_INCIDENT:-1}
- reboots: $(incident_count "$REBOOT_COUNT_FILE") / ${MAX_REBOOTS_PER_INCIDENT:-1}

RozhodnutĂ­:
- Akce: $DECISION_ACTION
- DĹŻvod: $DECISION_REASON
EOF

    cat > "$EVENT_DIR/checks.txt" <<EOF
CHECKS

[ $(safe_bool "$TERKOM_SERVICE_OK") ] terkom_service
  VĂ˝znam: systemd sluĹľba $SERVICE bÄ›ĹľĂ­.

[ $(safe_bool "$NODE_OK") ] node_process
  VĂ˝znam: bÄ›ĹľĂ­ proces node run.js.

[ $(safe_bool "$CHROMIUM_OK") ] chromium_process
  VĂ˝znam: bÄ›ĹľĂ­ chromium nebo chromium-browser.

[ $(safe_bool "$LOCAL_UI_OK") ] local_ui
  VĂ˝znam: $UI_URL odpovÄ›dÄ›lo do timeoutu.

[ $(safe_bool "$GATEWAY_OK") ] gateway
  VĂ˝znam: default gateway odpovÄ›dÄ›la na ping.

[ $(safe_bool "$INTERNET_OK") ] internet_ip  [informativnĂ­]
  VĂ˝znam: $INTERNET_IP odpovÄ›dÄ›lo na ping. NezpĹŻsobuje NETWORK_PROBLEM â€” jen informace.

[ $(safe_bool "$REMOTE_DNS_OK") ] remote_dns
  VĂ˝znam: $REMOTE_HOST lze pĹ™eloĹľit pĹ™es DNS.

[ $(safe_bool "$REMOTE_TCP_OK") ] remote_tcp
  VĂ˝znam: TCP spojenĂ­ na $REMOTE_HOST:$REMOTE_PORT funguje.

[ $(safe_bool "$DB_TCP_OK") ] db_tcp
  VĂ˝znam: DB host:port je TCP dostupnĂ˝.

[ $(safe_bool "$DB_SQL_OK") ] db_sql
  VĂ˝znam: PostgreSQL odpovÄ›dÄ›l na SELECT 1.

[ $(pool_status) ] db_pool_exhausted
  VĂ˝znam: SQL chyba obsahovala "too many clients". OK znamenĂˇ, Ĺľe nebyla detekovĂˇna.

[ $(safe_bool "$POWER_CUT") ] power_cut
  VĂ˝znam: DetekovĂˇno neoÄŤekĂˇvanĂ© odpojenĂ­ napĂˇjenĂ­ (vĂ˝padek proudu / vytaĹľenĂ­ ze zĂˇsuvky).
  Detekce: pĹ™edchozĂ­ boot neobsahuje zĂˇznam ÄŤistĂ©ho vypnutĂ­ (systemd shutdown) v journalu.
EOF

    cat > "$EVENT_DIR/decision.txt" <<EOF
DECISION

dead_terminal=$DEAD_TERMINAL
dead_reason=${DEAD_REASON:-none}
network_problem=$NETWORK_PROBLEM
db_problem=$DB_PROBLEM
power_cut=$POWER_CUT
service_restarts=$(incident_count "$SERVICE_RESTART_COUNT_FILE")/${MAX_SERVICE_RESTARTS_PER_INCIDENT:-1}
network_restarts=$(incident_count "$NETWORK_RESTART_COUNT_FILE")/${MAX_NETWORK_RESTARTS_PER_INCIDENT:-2}
kiosk_restarts=$(incident_count "$KIOSK_RESTART_COUNT_FILE")/${MAX_KIOSK_RESTARTS_PER_INCIDENT:-1}
reboots=$(incident_count "$REBOOT_COUNT_FILE")/${MAX_REBOOTS_PER_INCIDENT:-1}

Matched action:
$DECISION_ACTION

Explanation:
$DECISION_REASON

PoznĂˇmka:
Toto je Phase 2 diagnosticko-recovery verze. Snapshot byl vytvoĹ™en pĹ™ed pĹ™Ă­padnĂ˝m recovery zĂˇsahem.
EOF

    cat > "$EVENT_DIR/db.txt" <<EOF
DB SUMMARY

Config:
- host: ${DB_HOST:-unknown}
- port: ${DB_PORT:-unknown}
- database: ${DB_NAME:-unknown}
- user: ${DB_USER:-unknown}
- password: hidden
- application_name: ${DB_APP_NAME:-unknown}

Checks:
- DB TCP: $(safe_bool "$DB_TCP_OK")
- DB SQL: $(safe_bool "$DB_SQL_OK")
- DB pool exhausted: $(pool_status)

DB SQL error:
${DB_SQL_ERROR:-none}
EOF

    cat > "$EVENT_DIR/network.txt" <<EOF
NETWORK SUMMARY

Default gateway:
${DEFAULT_GATEWAY:-not detected}

Gateway check:
$(safe_bool "$GATEWAY_OK")

Internet check:
$INTERNET_IP = $(safe_bool "$INTERNET_OK")

Remote server:
$REMOTE_HOST:$REMOTE_PORT = $(safe_bool "$REMOTE_TCP_OK")

DNS:
$REMOTE_HOST = $(safe_bool "$REMOTE_DNS_OK")
EOF

    cat > "$EVENT_DIR/terkom.txt" <<EOF
TERKOM SUMMARY

Service:
$SERVICE = $(safe_bool "$TERKOM_SERVICE_OK")

Node:
node run.js = $(safe_bool "$NODE_OK")

Chromium:
chromium/chromium-browser = $(safe_bool "$CHROMIUM_OK")

Local UI:
$UI_URL = $(safe_bool "$LOCAL_UI_OK")
EOF

    cat > "$EVENT_DIR/db-explanation.txt" <<EOF
DB DIAGNOSTIKA - VYSVÄšTLENĂŤ

DB TCP:
  Kontroluje, jestli je host:port databĂˇze dostupnĂ˝ na sĂ­ĹĄovĂ© Ăşrovni.
  OK znamenĂˇ, Ĺľe port odpovĂ­dĂˇ. NeznamenĂˇ to ale, Ĺľe DB umĂ­ provĂ©st SQL dotaz.

DB SQL:
  Kontroluje reĂˇlnĂ˝ SQL dotaz SELECT 1 pĹ™es psql.
  OK znamenĂˇ, Ĺľe DB pĹ™ijala pĹ™ipojenĂ­, ovÄ›Ĺ™ila uĹľivatele a provedla dotaz.

too many clients:
  ZnamenĂˇ, Ĺľe PostgreSQL odmĂ­tĂˇ dalĹˇĂ­ spojenĂ­, protoĹľe je dosaĹľen limit max_connections.
  Typicky to ukazuje na connection leak, ĹˇpatnĂ˝ pool, nebo pĹ™Ă­liĹˇ mnoho souÄŤasnĂ˝ch klientĹŻ.

active:
  SpojenĂ­ prĂˇvÄ› provĂˇdĂ­ SQL dotaz.

idle:
  SpojenĂ­ je otevĹ™enĂ©, ale aktuĂˇlnÄ› nic nedÄ›lĂˇ. V urÄŤitĂ©m mnoĹľstvĂ­ je normĂˇlnĂ­ u connection poolu.

idle in transaction:
  RizikovĂ˝ stav. SpojenĂ­ drĹľĂ­ otevĹ™enou transakci, ale nic nedÄ›lĂˇ.
  MĹŻĹľe blokovat vacuum, drĹľet locky nebo zpĹŻsobovat narĹŻstajĂ­cĂ­ problĂ©my.

wait_event / wait_event_type:
  Ukazuje, Ĺľe query nebo spojenĂ­ na nÄ›co ÄŤekĂˇ. MĹŻĹľe jĂ­t o lock, IO, klienta, sĂ­ĹĄ nebo internĂ­ PostgreSQL ÄŤekĂˇnĂ­.

long queries:
  Dlouho bÄ›ĹľĂ­cĂ­ SQL dotazy. Mohou znaÄŤit zaseknutĂ˝ vĂ˝dej, pomalou DB, lock nebo ĹˇpatnĂ˝ dotaz.

connection usage percent:
  PomÄ›r aktuĂˇlnĂ­ch spojenĂ­ k max_connections. Nad ${DB_CONNECTION_WARN_PERCENT}% je varovĂˇnĂ­.

raw/db-history:
  PoslednĂ­ch $DB_HISTORY_KEEP prĹŻbÄ›ĹľnĂ˝ch DB mini-checkĹŻ pĹ™ed incidentem. HodĂ­ se, kdyĹľ se DB pozdÄ›ji stane nedostupnou.

DoporuÄŤenĂ˝ postup po incidentu:
  1. Zkontroluj pg_connection_usage.txt â€” je vyuĹľitĂ­ blĂ­zko 100 %?
  2. Zkontroluj pg_idle_in_transaction.txt â€” jsou zde starĂ© nezavĹ™enĂ© transakce?
  3. Zkontroluj pg_long_queries.txt â€” bÄ›ĹľĂ­ nÄ›jakĂ˝ SQL dotaz pĹ™Ă­liĹˇ dlouho?
  4. Zkontroluj pg_wait_events.txt â€” ÄŤekĂˇ nÄ›jakĂ© spojenĂ­ na lock nebo IO?
  5. Zkontroluj raw/db-history â€” jak vypadala DB tÄ›snÄ› pĹ™ed incidentem?
  6. Pokud byl pool exhausted: zvaĹľ snĂ­ĹľenĂ­ max klientĹŻ v aplikaci nebo navĂ˝ĹˇenĂ­ max_connections v PostgreSQL.
  7. Pokud byl problĂ©m idle in transaction: hledej connection leak nebo zamrzlou session v aplikaci.
EOF

    journalctl --list-boots --no-pager 2>/dev/null | tail -n 10 > "$EVENT_DIR/raw/last_boots.txt" 2>&1 || true
    journalctl -b -1 -n 300 --no-pager > "$EVENT_DIR/raw/journal_previous_boot_tail.txt" 2>&1 || true
    last -x | head -n 30 > "$EVENT_DIR/raw/last_x.txt" 2>&1 || true
    ip addr > "$EVENT_DIR/raw/ip_addr.txt" 2>&1 || true
    ip route > "$EVENT_DIR/raw/ip_route.txt" 2>&1 || true
    systemctl status "$SERVICE" --no-pager > "$EVENT_DIR/raw/systemctl_terkom.txt" 2>&1 || true
    systemctl status terkom-browser.service --no-pager > "$EVENT_DIR/raw/systemctl_terkom_browser.txt" 2>&1 || true
    systemctl status kiosk.service --no-pager > "$EVENT_DIR/raw/systemctl_kiosk.txt" 2>&1 || true
    ps aux > "$EVENT_DIR/raw/ps_aux.txt" 2>&1 || true
    ps -eo pid,ppid,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -n 40 > "$EVENT_DIR/raw/ps_top_cpu.txt" 2>&1 || true
    dmesg | tail -n 300 > "$EVENT_DIR/raw/dmesg_tail.txt" 2>&1 || true
    journalctl -u "$SERVICE" -n 200 --no-pager > "$EVENT_DIR/journal-terkom.txt" 2>&1 || true
    journalctl -k -n 200 --no-pager > "$EVENT_DIR/journal-kernel.txt" 2>&1 || true
    journalctl -n 300 --no-pager | grep -Ei 'network|dhcp|eth|wlan|link|carrier|terkom|node|chromium|postgres|knex|error|fail|timeout|too many clients' > "$EVENT_DIR/journal-network.txt" 2>&1 || true
    journalctl --since "-30 min" -u "$SERVICE" --no-pager > "$EVENT_DIR/journal-terkom-last30m.txt" 2>&1 || true
    journalctl --since "-30 min" -k --no-pager > "$EVENT_DIR/journal-kernel-last30m.txt" 2>&1 || true
    journalctl --since "-30 min" --no-pager | grep -Ei 'network|dhcp|eth|wlan|link|carrier|terkom|node|chromium|postgres|knex|error|fail|timeout|too many clients|oom|hang|blocked' > "$EVENT_DIR/journal-events-last30m.txt" 2>&1 || true
    uptime > "$EVENT_DIR/raw/uptime.txt" 2>&1 || true
    cat /proc/uptime > "$EVENT_DIR/raw/proc_uptime.txt" 2>&1 || true
    cat /proc/loadavg > "$EVENT_DIR/raw/loadavg.txt" 2>&1 || true
    free -m > "$EVENT_DIR/raw/free_m.txt" 2>&1 || true
    df -h > "$EVENT_DIR/raw/df_h.txt" 2>&1 || true
    top -b -n 1 | head -n 200 > "$EVENT_DIR/raw/top.txt" 2>&1 || true
    ss -s > "$EVENT_DIR/raw/ss_summary.txt" 2>&1 || true
    ss -lntp > "$EVENT_DIR/raw/ss_listen_tcp.txt" 2>&1 || true
    ss -tanp | head -n 200 > "$EVENT_DIR/raw/ss_tcp.txt" 2>&1 || true
    cat /proc/meminfo > "$EVENT_DIR/raw/meminfo.txt" 2>&1 || true
    if command -v vcgencmd >/dev/null 2>&1; then
        vcgencmd get_throttled > "$EVENT_DIR/raw/vcgencmd_get_throttled.txt" 2>&1 || true
        vcgencmd measure_temp > "$EVENT_DIR/raw/vcgencmd_measure_temp.txt" 2>&1 || true
    fi
    for pressure_file in /proc/pressure/cpu /proc/pressure/io /proc/pressure/memory; do
        [ -r "$pressure_file" ] && cat "$pressure_file" > "$EVENT_DIR/raw/$(basename "$pressure_file").pressure.txt" 2>&1 || true
    done

    if command -v psql >/dev/null 2>&1 && [ "${DB_CONFIG_OK:-false}" = "true" ]; then
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COALESCE(state,'unknown') AS state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count(*) DESC;" > "$EVENT_DIR/raw/pg_stat_activity_state.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT usename, application_name, client_addr, state, count(*) FROM pg_stat_activity GROUP BY usename, application_name, client_addr, state ORDER BY count(*) DESC;" > "$EVENT_DIR/raw/pg_stat_activity_clients.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT count(*) AS current_connections FROM pg_stat_activity;" > "$EVENT_DIR/raw/pg_connections_total.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT setting AS max_connections FROM pg_settings WHERE name='max_connections';" > "$EVENT_DIR/raw/pg_max_connections.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT count(*) AS current_connections, setting::int AS max_connections, round(count(*) * 100.0 / setting::int, 1) AS usage_percent FROM pg_stat_activity, pg_settings WHERE pg_settings.name='max_connections' GROUP BY setting;" > "$EVENT_DIR/raw/pg_connection_usage.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT pid, usename, application_name, client_addr, state, now()-xact_start AS tx_age, query FROM pg_stat_activity WHERE state='idle in transaction' ORDER BY tx_age DESC;" > "$EVENT_DIR/raw/pg_idle_in_transaction.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT pid, usename, application_name, client_addr, now()-query_start AS duration, query FROM pg_stat_activity WHERE state='active' ORDER BY duration DESC LIMIT 20;" > "$EVENT_DIR/raw/pg_long_queries.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT pid, usename, application_name, client_addr, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE wait_event IS NOT NULL;" > "$EVENT_DIR/raw/pg_wait_events.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" > "$EVENT_DIR/raw/pg_version.txt" 2>&1 || true
        PGPASSWORD="$DB_PASSWORD" timeout 5 psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT now() - pg_postmaster_start_time() AS postgres_uptime;" > "$EVENT_DIR/raw/pg_uptime.txt" 2>&1 || true
    fi
}

persist_snapshot() {
    local src_dir dst_dir spool_dst remount_ro_ok=true
    src_dir="$RUNTIME_EVENT_DIR/$EVENT_NAME"
    dst_dir="$PERSIST_DIR/$EVENT_NAME"
    spool_dst="$SPOOL_DIR/$EVENT_NAME"
    mkdir -p "$SPOOL_DIR"
    rm -rf "$spool_dst"
    cp -a "$src_dir" "$spool_dst" 2>/dev/null || { log "VAROVĂNĂŤ: NepodaĹ™ilo se uloĹľit snapshot ani do spoolu: $spool_dst"; return 1; }
    if ! mount -o remount,rw /; then
        log "VAROVĂNĂŤ: NepodaĹ™ilo se pĹ™epnout rootfs do RW. Snapshot zĹŻstĂˇvĂˇ ve spoolu: $spool_dst"
        echo "WARNING: NepodaĹ™ilo se pĹ™epnout rootfs do RW. Snapshot zĹŻstal ve spoolu: $spool_dst" >> "$spool_dst/summary.txt"
        return 1
    fi
    mkdir -p "$PERSIST_DIR"
    find "$PERSIST_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$DIAG_RETENTION_DAYS" -exec rm -rf {} \; 2>/dev/null || true
    rm -rf "$dst_dir"
    if cp -a "$spool_dst" "$dst_dir" 2>/dev/null; then
        rm -rf "$spool_dst"
        LAST_EVENT_SNAPSHOT_DIR="$dst_dir"
        log "Snapshot uloĹľen do: $dst_dir"
    else
        LAST_EVENT_SNAPSHOT_DIR="$spool_dst"
        log "VAROVĂNĂŤ: NepodaĹ™ilo se zapsat snapshot do $dst_dir. ZĹŻstĂˇvĂˇ ve spoolu: $spool_dst"
    fi
    sync
    if ! mount -o remount,ro /; then
        remount_ro_ok=false
        log "VAROVĂNĂŤ: NepodaĹ™ilo se vrĂˇtit rootfs do RO. SystĂ©m mĹŻĹľe zĹŻstat v RW reĹľimu."
        [ -d "$dst_dir" ] && echo "WARNING: NepodaĹ™ilo se vrĂˇtit rootfs do RO po uloĹľenĂ­ snapshotu." >> "$dst_dir/summary.txt"
    fi
    [ "$remount_ro_ok" = "true" ]
}

flush_spool_snapshots() {
    mkdir -p "$SPOOL_DIR"
    find "$SPOOL_DIR" -mindepth 1 -maxdepth 1 -type d | grep -q . || return 0
    log "Spool obsahuje ÄŤekajĂ­cĂ­ snapshoty, pokouĹˇĂ­m se je uloĹľit do persistent sloĹľky."
    mount -o remount,rw / || { log "VAROVĂNĂŤ: Rootfs nejde pĹ™epnout do RW, spool zatĂ­m zĹŻstĂˇvĂˇ v /run."; return 1; }
    mkdir -p "$PERSIST_DIR"
    local item name dst
    for item in "$SPOOL_DIR"/*; do
        [ -d "$item" ] || continue
        name="$(basename "$item")"
        dst="$PERSIST_DIR/$name"
        rm -rf "$dst"
        if cp -a "$item" "$dst" 2>/dev/null; then rm -rf "$item"; log "Spool snapshot uloĹľen do: $dst"; else log "VAROVĂNĂŤ: NepodaĹ™ilo se uloĹľit spool snapshot: $item"; fi
    done
    sync
    mount -o remount,ro / || { log "VAROVĂNĂŤ: Po flush spoolu se nepodaĹ™ilo vrĂˇtit rootfs do RO."; return 1; }
}

get_file_num() { [ -f "$1" ] && cat "$1" 2>/dev/null || echo 0; }
set_file_num() { echo "$2" > "$1"; }
now_ts() { date +%s; }
format_ts_human() {
    local ts="$1"
    [ -n "$ts" ] && [ "$ts" -gt 0 ] 2>/dev/null || { echo "unknown"; return 0; }
    date -d "@$ts" '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || echo "$ts"
}
format_duration_hms() {
    local total="$1" h m s
    [ -n "$total" ] || total=0
    [ "$total" -ge 0 ] 2>/dev/null || total=0
    h=$((total / 3600))
    m=$(((total % 3600) / 60))
    s=$((total % 60))
    printf "%02d:%02d:%02d" "$h" "$m" "$s"
}
cooldown_ok() { local now last diff; now="$(now_ts)"; last="$(get_file_num "$1")"; diff=$((now-last)); [ "$diff" -ge "$2" ]; }
mark_action_ts() { set_file_num "$1" "$(now_ts)"; }
get_fail_count() { get_file_num "$FAIL_COUNT_FILE"; }
increment_fail_count() { local c; c="$(get_fail_count)"; c=$((c+1)); set_file_num "$FAIL_COUNT_FILE" "$c"; echo "$c"; }
increment_named_count() { local f="$1" c; c="$(get_file_num "$f")"; c=$((c+1)); set_file_num "$f" "$c"; echo "$c"; }
reset_named_count() { rm -f "$1"; }
update_category_fail_counts() {
    if [ "$DEAD_TERMINAL" = "true" ]; then DEAD_FAIL_COUNT="$(increment_named_count "$DEAD_FAIL_COUNT_FILE")"; else DEAD_FAIL_COUNT=0; reset_named_count "$DEAD_FAIL_COUNT_FILE"; fi
    if [ "$NETWORK_PROBLEM" = "true" ]; then NETWORK_FAIL_COUNT="$(increment_named_count "$NETWORK_FAIL_COUNT_FILE")"; else NETWORK_FAIL_COUNT=0; reset_named_count "$NETWORK_FAIL_COUNT_FILE"; fi
    if [ "$DB_PROBLEM" = "true" ]; then DB_FAIL_COUNT="$(increment_named_count "$DB_FAIL_COUNT_FILE")"; else DB_FAIL_COUNT=0; reset_named_count "$DB_FAIL_COUNT_FILE"; fi
}
read_category_fail_counts() {
    DEAD_FAIL_COUNT="$(get_file_num "$DEAD_FAIL_COUNT_FILE")"
    NETWORK_FAIL_COUNT="$(get_file_num "$NETWORK_FAIL_COUNT_FILE")"
    DB_FAIL_COUNT="$(get_file_num "$DB_FAIL_COUNT_FILE")"
}
reset_failure_state() {
    rm -f "$FAIL_COUNT_FILE" "$FIRST_FAIL_TS_FILE"
    rm -f "$LAST_EVENT_NAME_FILE" "$LAST_EVENT_FTP_URL_FILE" "$LAST_EVENT_FTP_DIR_URL_FILE"
    rm -f "${LAST_INCIDENT_NOTIFY_PREFIX}"_*.ts 2>/dev/null || true
    reset_named_count "$DEAD_FAIL_COUNT_FILE"
    reset_named_count "$NETWORK_FAIL_COUNT_FILE"
    reset_named_count "$DB_FAIL_COUNT_FILE"
    reset_named_count "$SERVICE_RESTART_COUNT_FILE"
    reset_named_count "$NETWORK_RESTART_COUNT_FILE"
    reset_named_count "$KIOSK_RESTART_COUNT_FILE"
    reset_named_count "$REBOOT_COUNT_FILE"
}
ensure_first_fail_ts() { [ ! -f "$FIRST_FAIL_TS_FILE" ] && set_file_num "$FIRST_FAIL_TS_FILE" "$(now_ts)"; }
failure_duration_seconds() { local f n; f="$(get_file_num "$FIRST_FAIL_TS_FILE")"; n="$(now_ts)"; echo $((n-f)); }
snapshot_allowed() { cooldown_ok "$LAST_SNAPSHOT_FILE" "$SNAPSHOT_COOLDOWN_SECONDS"; }
mark_snapshot_ts() { mark_action_ts "$LAST_SNAPSHOT_FILE"; }
incident_count() { get_file_num "$1"; }
incident_limit_ok() {
    local file="$1" max="$2" label="$3" count
    case "${max:-}" in ''|*[!0-9]*) max=0 ;; esac
    count="$(incident_count "$file")"
    if [ "$count" -ge "$max" ] 2>/dev/null; then
        log "Recovery: $label pĹ™eskoÄŤeno - limit pro incident vyÄŤerpĂˇn ($count/$max)."
        return 1
    fi
    return 0
}
mark_incident_action() { increment_named_count "$1" >/dev/null; }
service_restart_tried() { [ "$(incident_count "$SERVICE_RESTART_COUNT_FILE")" -gt 0 ] 2>/dev/null; }
kiosk_restart_tried() { [ "$(incident_count "$KIOSK_RESTART_COUNT_FILE")" -gt 0 ] 2>/dev/null; }
network_restart_limit_exhausted() { ! incident_limit_ok "$NETWORK_RESTART_COUNT_FILE" "$MAX_NETWORK_RESTARTS_PER_INCIDENT" "restart sĂ­tÄ›"; }
reboot_limit_available() { incident_limit_ok "$REBOOT_COUNT_FILE" "$MAX_REBOOTS_PER_INCIDENT" "reboot"; }
pure_remote_tcp_problem() {
    [ "${REMOTE_TCP_OK:-skip}" = "false" ] && [ "${GATEWAY_OK:-skip}" != "false" ] && [ "${REMOTE_DNS_OK:-skip}" != "false" ]
}
dead_should_escalate_after_service_restart() {
    service_restart_tried || return 1
    if [ "${DEAD_REBOOT_AFTER_FAILED_SERVICE_RESTART:-true}" = "true" ]; then return 0; fi
    if [ "${LOCAL_UI_FAIL_FAST_REBOOT:-true}" = "true" ] && [ "${LOCAL_UI_OK:-skip}" = "false" ]; then return 0; fi
    return 1
}

restart_kiosk_service() {
    incident_limit_ok "$KIOSK_RESTART_COUNT_FILE" "$MAX_KIOSK_RESTARTS_PER_INCIDENT" "restart kiosku/Chromia" || return 0
    if ! cooldown_ok "$LAST_KIOSK_RESTART_FILE" "$KIOSK_RESTART_COOLDOWN_SECONDS"; then log "Recovery: restart kiosku/Chromia preskocen - cooldown aktivni."; return 0; fi
    log "Recovery: restartuji pouze kiosk/Chromium."
    mark_incident_action "$KIOSK_RESTART_COUNT_FILE"
    mark_action_ts "$LAST_KIOSK_RESTART_FILE"
    pkill -f chromium || true
    pkill -f chromium-browser || true
    if systemctl cat terkom-browser.service >/dev/null 2>&1; then
        systemctl restart terkom-browser.service || true
    elif systemctl cat kiosk.service >/dev/null 2>&1; then
        systemctl restart kiosk.service || true
    else
        log "Recovery: kiosk service nenalezena, Chromium bylo pouze ukonceno."
    fi
    sleep 5
}

restart_terkom_service() {
    incident_limit_ok "$SERVICE_RESTART_COUNT_FILE" "$MAX_SERVICE_RESTARTS_PER_INCIDENT" "restart Terkom sluĹľby" || return 0
    if ! cooldown_ok "$LAST_SERVICE_RESTART_FILE" "$SERVICE_RESTART_COOLDOWN_SECONDS"; then log "Recovery: restart Terkom sluĹľby pĹ™eskoÄŤen - cooldown aktivnĂ­."; return 0; fi
    log "Recovery: restartuji Terkom sluĹľbu."
    mark_incident_action "$SERVICE_RESTART_COUNT_FILE"
    mark_action_ts "$LAST_SERVICE_RESTART_FILE"
    systemctl stop "$SERVICE" || true
    killall node || true
    sleep 2
    systemctl start "$SERVICE" || true
    sleep 10
}

restart_network_service() {
    if [ "${REMOTE_TCP_NO_LOOP_RECOVERY:-true}" = "true" ] && pure_remote_tcp_problem; then
        local net_count net_max
        net_count="$(incident_count "$NETWORK_RESTART_COUNT_FILE")"
        net_max="${MAX_NETWORK_RESTARTS_PER_INCIDENT:-2}"
        case "$net_max" in ''|*[!0-9]*) net_max=0 ;; esac
        if [ "$net_count" -ge "$net_max" ] 2>/dev/null; then
            log "Recovery: remote_tcp fail trvĂˇ, ale DNS/gateway vypadajĂ­ OK - dalĹˇĂ­ restart sĂ­tÄ› pĹ™eskoÄŤen, limit incidentu vyÄŤerpĂˇn ($net_count/$net_max)."
            return 0
        fi
    fi
    incident_limit_ok "$NETWORK_RESTART_COUNT_FILE" "$MAX_NETWORK_RESTARTS_PER_INCIDENT" "restart sĂ­tÄ›" || return 0
    if ! cooldown_ok "$LAST_NETWORK_RESTART_FILE" "$NETWORK_RESTART_COOLDOWN_SECONDS"; then log "Recovery: restart sĂ­tÄ› pĹ™eskoÄŤen - cooldown aktivnĂ­."; return 0; fi
    log "Recovery: restartuji sĂ­ĹĄovĂ© sluĹľby."
    mark_incident_action "$NETWORK_RESTART_COUNT_FILE"
    mark_action_ts "$LAST_NETWORK_RESTART_FILE"
    if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then systemctl restart NetworkManager.service || true
    elif systemctl is-active --quiet dhcpcd.service 2>/dev/null; then systemctl restart dhcpcd.service || true
    elif systemctl is-active --quiet systemd-networkd.service 2>/dev/null; then systemctl restart systemd-networkd.service || true
    elif systemctl list-unit-files | grep -q '^networking.service'; then systemctl restart networking.service || true
    else log "Recovery: nenalezena znĂˇmĂˇ sĂ­ĹĄovĂˇ sluĹľba k restartu."; fi
    sleep 10
}

reboot_device() {
    local duration
    duration="$(failure_duration_seconds)"
    if [ "$DEAD_TERMINAL" != "true" ]; then log "Recovery: reboot nepovolen, protoĹľe dead_terminal=false."; return 0; fi
    if [ "$duration" -lt "$MIN_FAILURE_DURATION_BEFORE_REBOOT" ]; then log "Recovery: reboot zatĂ­m nepovolen - problĂ©m trvĂˇ ${duration}s < ${MIN_FAILURE_DURATION_BEFORE_REBOOT}s."; return 0; fi
    if ! cooldown_ok "$LAST_REBOOT_FILE" "$REBOOT_COOLDOWN_SECONDS"; then log "Recovery: reboot pĹ™eskoÄŤen - cooldown aktivnĂ­."; return 0; fi
    reboot_limit_available || return 0
    log "Recovery: provĂˇdĂ­m REBOOT zaĹ™Ă­zenĂ­."
    mark_incident_action "$REBOOT_COUNT_FILE"
    mark_action_ts "$LAST_REBOOT_FILE"
    sync
    sleep 2
    reboot
}

# Reboot pĹ™i ÄŤistÄ› sĂ­ĹĄovĂ©m problĂ©mu (bez dead_terminal). Pouze pokud NETWORK_ONLY_REBOOT_AFTER_FAILS > 0.
# StejnĂ© pojistky jako reboot_device: MIN_FAILURE_DURATION_BEFORE_REBOOT, REBOOT_COOLDOWN_SECONDS.
network_only_reboot_device() {
    local duration
    duration="$(failure_duration_seconds)"
    if [ "${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0}" -le 0 ] 2>/dev/null; then return 0; fi
    if [ "$DEAD_TERMINAL" = "true" ]; then return 0; fi
    if [ "$NETWORK_PROBLEM" != "true" ] || [ "$DB_PROBLEM" = "true" ]; then return 0; fi
    if [ "$duration" -lt "$MIN_FAILURE_DURATION_BEFORE_REBOOT" ]; then log "Recovery: sĂ­ĹĄ-only reboot zatĂ­m nepovolen - problĂ©m trvĂˇ ${duration}s < ${MIN_FAILURE_DURATION_BEFORE_REBOOT}s."; return 0; fi
    if ! cooldown_ok "$LAST_REBOOT_FILE" "$REBOOT_COOLDOWN_SECONDS"; then log "Recovery: sĂ­ĹĄ-only reboot pĹ™eskoÄŤen - cooldown aktivnĂ­."; return 0; fi
    reboot_limit_available || return 0
    log "Recovery: sĂ­ĹĄ-only eskalace â€” provĂˇdĂ­m REBOOT zaĹ™Ă­zenĂ­ (network_fail=$NETWORK_FAIL_COUNT, prĂˇh NETWORK_ONLY_REBOOT_AFTER_FAILS=$NETWORK_ONLY_REBOOT_AFTER_FAILS)."
    mark_incident_action "$REBOOT_COUNT_FILE"
    mark_action_ts "$LAST_REBOOT_FILE"
    sync
    sleep 2
    reboot
}

# PĹ™edpovÄ›ÄŹ, zda run_recovery v totĂ©Ĺľ situaci zavolĂˇ reboot (pro EMAIL_POLICY=reboot_only pĹ™ed odeslĂˇnĂ­m mailu).
would_reboot_soon() {
    [ "$RECOVERY_ENABLED" = "true" ] || return 1
    read_category_fail_counts
    local duration
    duration="$(failure_duration_seconds)" || duration=0
    [ "$duration" -ge "$MIN_FAILURE_DURATION_BEFORE_REBOOT" ] 2>/dev/null || return 1
    cooldown_ok "$LAST_REBOOT_FILE" "$REBOOT_COOLDOWN_SECONDS" || return 1
    [ "$(incident_count "$REBOOT_COUNT_FILE")" -lt "${MAX_REBOOTS_PER_INCIDENT:-1}" ] 2>/dev/null || return 1
    pure_chromium_problem && return 1

    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then return 1; fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "false" ]; then
        dead_should_escalate_after_service_restart && return 0
        [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ] && return 0
        return 1
    fi
    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then
        if [ "${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0}" -gt 0 ] 2>/dev/null && [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_ONLY_REBOOT_AFTER_FAILS" ]; then return 0; fi
        return 1
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then
        dead_should_escalate_after_service_restart && return 0
        if [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ]; then return 0; fi
        return 1
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then
        dead_should_escalate_after_service_restart && return 0
        if [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ]; then return 0; fi
        return 1
    fi
    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "true" ]; then return 1; fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "true" ]; then
        dead_should_escalate_after_service_restart && return 0
        if [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ]; then return 0; fi
        return 1
    fi
    return 1
}

run_recovery() {
    local fail_count duration service_restarts network_restarts kiosk_restarts reboots
    fail_count="$(get_fail_count)"
    read_category_fail_counts
    duration="$(failure_duration_seconds)"
    service_restarts="$(incident_count "$SERVICE_RESTART_COUNT_FILE")"
    network_restarts="$(incident_count "$NETWORK_RESTART_COUNT_FILE")"
    kiosk_restarts="$(incident_count "$KIOSK_RESTART_COUNT_FILE")"
    reboots="$(incident_count "$REBOOT_COUNT_FILE")"
    log "Recovery decision: profile=$RECOVERY_PROFILE fail_count=$fail_count dead_fail=$DEAD_FAIL_COUNT dead_reason=${DEAD_REASON:-none} network_fail=$NETWORK_FAIL_COUNT db_fail=$DB_FAIL_COUNT service_restarts=$service_restarts/${MAX_SERVICE_RESTARTS_PER_INCIDENT:-1} network_restarts=$network_restarts/${MAX_NETWORK_RESTARTS_PER_INCIDENT:-2} kiosk_restarts=$kiosk_restarts/${MAX_KIOSK_RESTARTS_PER_INCIDENT:-1} reboots=$reboots/${MAX_REBOOTS_PER_INCIDENT:-1} network_only_reboot=${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0} duration=${duration}s dead=$DEAD_TERMINAL network=$NETWORK_PROBLEM db=$DB_PROBLEM"
    [ "$RECOVERY_ENABLED" = "true" ] || { log "Recovery je vypnutĂˇ."; return 0; }

    if pure_chromium_problem; then
        [ "$DEAD_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && restart_kiosk_service
        return 0
    fi

    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then
        [ "$DB_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && { log "Recovery: DB-only problĂ©m â†’ restart Terkom sluĹľby jako DB reconnect."; restart_terkom_service; }
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "false" ]; then
        if dead_should_escalate_after_service_restart; then
            log "Recovery: dead/local UI fail trvĂˇ po restartu Terkom sluĹľby v tomto incidentu â†’ eskaluji na reboot."
            reboot_device
            return 0
        fi
        [ "$DEAD_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && restart_terkom_service
        [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ] && reboot_device
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then
        if [ "${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0}" -gt 0 ] 2>/dev/null && [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_ONLY_REBOOT_AFTER_FAILS" ]; then
            network_only_reboot_device
        elif [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_RESTART_AFTER_FAILS" ]; then
            restart_network_service
        fi
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "false" ]; then
        if dead_should_escalate_after_service_restart; then
            log "Recovery: dead/local UI fail trvĂˇ po restartu Terkom sluĹľby v tomto incidentu â†’ eskaluji na reboot."
            reboot_device
            return 0
        fi
        [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_RESTART_AFTER_FAILS" ] && restart_network_service
        [ "$DEAD_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && restart_terkom_service
        [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ] && reboot_device
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "true" ]; then
        if dead_should_escalate_after_service_restart; then
            log "Recovery: dead/local UI fail trvĂˇ po restartu Terkom sluĹľby v tomto incidentu â†’ eskaluji na reboot."
            reboot_device
            return 0
        fi
        [ "$DEAD_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && restart_terkom_service
        [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ] && reboot_device
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "true" ]; then
        [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_RESTART_AFTER_FAILS" ] && restart_network_service
        return 0
    fi
    if [ "$DEAD_TERMINAL" = "true" ] && [ "$NETWORK_PROBLEM" = "true" ] && [ "$DB_PROBLEM" = "true" ]; then
        if dead_should_escalate_after_service_restart; then
            log "Recovery: dead/local UI fail trvĂˇ po restartu Terkom sluĹľby v tomto incidentu â†’ eskaluji na reboot."
            reboot_device
            return 0
        fi
        [ "$NETWORK_FAIL_COUNT" -ge "$NETWORK_RESTART_AFTER_FAILS" ] && restart_network_service
        [ "$DEAD_FAIL_COUNT" -ge "$SERVICE_RESTART_AFTER_FAILS" ] && restart_terkom_service
        [ "$DEAD_FAIL_COUNT" -ge "$REBOOT_AFTER_FAILS" ] && reboot_device
        return 0
    fi
}

# MAIN
load_notification_config
# PĹ™eÄŤti popis stojanu z description v config-local.yaml (bez DIAG markeru) pro pojmenovĂˇnĂ­ sloĹľek
_raw_desc="$(grep -iE '"?description"?[[:space:]]*:' "${CONFIG_FILE:-}" 2>/dev/null \
    | head -1 | sed 's/.*:[[:space:]]*//' | tr -d '"' | xargs 2>/dev/null || true)"
_clean_desc="$(echo "${_raw_desc:-}" \
    | sed 's/ DIAG_v[^ "]*//g' | sed 's/---DIA_v[^ ]*//g' | sed 's/---DIAGNOSTIKA//g' \
    | xargs 2>/dev/null || true)"
TERMINAL_SLUG="$(echo "${_clean_desc:-}" | tr ' /:' '___' | tr -cd '[:alnum:]_.-' || true)"
unset _raw_desc _clean_desc
if [ -n "${TERMINAL_SLUG:-}" ]; then
    PERSIST_DIR="${PERSIST_DIR}/${TERMINAL_SLUG}"
fi
load_runtime_behavior
apply_recovery_profile
apply_mode_profile
case "${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0}" in (*[!0-9]*) NETWORK_ONLY_REBOOT_AFTER_FAILS=0 ;; esac
resolve_checker_default_secrets
log "Mode settings: DIAG_MODE=$DIAG_MODE profile=$RECOVERY_PROFILE email_policy=${EMAIL_POLICY:-all} restart_fails(service/network/reboot)=$SERVICE_RESTART_AFTER_FAILS/$NETWORK_RESTART_AFTER_FAILS/$REBOOT_AFTER_FAILS incident_limits(service/network/kiosk/reboot)=${MAX_SERVICE_RESTARTS_PER_INCIDENT:-1}/${MAX_NETWORK_RESTARTS_PER_INCIDENT:-2}/${MAX_KIOSK_RESTARTS_PER_INCIDENT:-1}/${MAX_REBOOTS_PER_INCIDENT:-1} network_only_reboot=${NETWORK_ONLY_REBOOT_AFTER_FAILS:-0} cooldown(service/network/kiosk/reboot)=${SERVICE_RESTART_COOLDOWN_SECONDS}s/${NETWORK_RESTART_COOLDOWN_SECONDS}s/${KIOSK_RESTART_COOLDOWN_SECONDS}s/${REBOOT_COOLDOWN_SECONDS}s email_throttle=${EMAIL_THROTTLE_SECONDS}s"
if ! is_monitoring_window; then exit 0; fi
flush_spool_snapshots || true
run_checks
get_decision

if [ "$DEAD_TERMINAL" = "false" ] && [ "$NETWORK_PROBLEM" = "false" ] && [ "$DB_PROBLEM" = "false" ] && [ "$POWER_CUT" = "false" ]; then
    log "OK - diagnostic checks passed."
    if [ -f "$FIRST_FAIL_TS_FILE" ]; then
        read_customer_context
        send_resolution_notification
    fi
    reset_failure_state
    exit 0
fi

ensure_first_fail_ts
FAIL_COUNT="$(increment_fail_count)"
update_category_fail_counts
REASON=""
if [ "$DEAD_TERMINAL" = "true" ]; then
    _dead_reason_slug="$(echo "${DEAD_REASON:-unknown}" | tr ' ' '-' | tr -cd 'A-Za-z0-9._-')"
    [ -n "$_dead_reason_slug" ] || _dead_reason_slug="unknown"
    REASON="${REASON}dead-${_dead_reason_slug}_"
    unset _dead_reason_slug
fi
[ "$NETWORK_PROBLEM" = "true" ] && REASON="${REASON}network-problem_"
[ "$DB_PROBLEM" = "true" ] && REASON="${REASON}db-problem_"
[ "$POWER_CUT" = "true" ]    && REASON="${REASON}power-cut_"
REASON="${REASON%_}"

log "PROBLĂ‰M DETEKOVĂN: $REASON"
log "Phase 2: fail_count=$FAIL_COUNT, vytvĂˇĹ™Ă­m diagnostiku a spouĹˇtĂ­m recovery podle matice."

if snapshot_allowed; then
    create_snapshot_runtime "$REASON"
    persist_snapshot || true
    read_customer_context
    if [ -n "${LAST_EVENT_SNAPSHOT_DIR:-}" ]; then
        send_event_notification "$EVENT_NAME" "$LAST_EVENT_SNAPSHOT_DIR" "$REASON"
    fi
    mark_snapshot_ts
else
    log "Snapshot pĹ™eskoÄŤen - snapshot cooldown aktivnĂ­."
fi

run_recovery
exit 0
DIAG

chown root:root "$DIAG_SCRIPT"
chmod 755 "$DIAG_SCRIPT"

cat > "$SERVICE_FILE" <<'EOF'
[Unit]
Description=Terkom Diagnostic V2
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/bin/bash /opt/terkom-ng/terkom/scripts/terkom-diagnostic.sh
User=root
Group=root
StandardOutput=journal
StandardError=journal
EOF

TIMER_INTERVAL_SECONDS=10
INSTALL_DIAG_MODE="$(echo "$INSTALL_DIAG_MODE" | tr '[:lower:]' '[:upper:]')"
case "$INSTALL_DIAG_MODE" in
    NORMAL) TIMER_INTERVAL_SECONDS=20 ;;
    FAST) TIMER_INTERVAL_SECONDS=10 ;;
    ULTRA) TIMER_INTERVAL_SECONDS=5 ;;
    *)
        echo "[WARN] Unknown DIAG_MODE '$INSTALL_DIAG_MODE' during install, using NORMAL timer interval (20s)."
        INSTALL_DIAG_MODE="ULTRA"
        TIMER_INTERVAL_SECONDS=10
        ;;
esac
echo "[INFO] Install DIAG_MODE=$INSTALL_DIAG_MODE, timer interval=${TIMER_INTERVAL_SECONDS}s."

cat > "$TIMER_FILE" <<EOF
[Unit]
Description=Run Terkom Diagnostic V2 every ${TIMER_INTERVAL_SECONDS} seconds

[Timer]
OnBootSec=20
OnUnitActiveSec=${TIMER_INTERVAL_SECONDS}
AccuracySec=3
Unit=terkom-diagnostic.service

[Install]
WantedBy=timers.target
EOF

chown root:root "$SERVICE_FILE" "$TIMER_FILE"
chmod 644 "$SERVICE_FILE" "$TIMER_FILE"

NOTIFY_ENV_FILE="$TERKOM_HOME/.terdiag-notify.env"
if [ ! -f "$NOTIFY_ENV_FILE" ]; then
    cat > "$NOTIFY_ENV_FILE" <<EOF
# Auto-generated by install_diagnostic.sh
EVENT_NOTIFY_ENABLED=true
EVENT_EMAIL_ENABLED=false
DIRECT_EVENT_EMAIL_ENABLED=false
AI_REPORT_MAIL_ENABLED=false
DIAG_MODE=${INSTALL_DIAG_MODE}
EMAIL_THROTTLE_SECONDS=600
EVENT_EMAIL_TO=podpora@altisima.cz
SMTP_SERVER=smtp.mailersend.net
SMTP_PORT=587
SMTP_USER=MS_9m8SPC@test-2p0347z3zyklzdrn.mlsender.net
SMTP_PASS=mssp.86tnLYv.k68zxl2nm7elj905.Ib2pcw7
SMTP_SENDER=MS_9m8SPC@test-2p0347z3zyklzdrn.mlsender.net
MAILERSEND_API_TOKEN=
MAILERSEND_API_URL=https://api.mailersend.com/v1/email
EVENT_FTP_ENABLED=true
FTP_SERVER=ftp.altisima.cz
FTP_USER=zalohydat.cz
FTP_PASS=
FTP_BASE_DIR=diagnostika
FTP_PUBLIC_BASE_URL=ftp://ftp.altisima.cz/diagnostika
# VolitelnĂ©: reboot terminĂˇlu po N po sobÄ› jdoucĂ­ch ÄŤistÄ› sĂ­ĹĄovĂ˝ch selhĂˇnĂ­ch (dead_terminal=false). 0=vypnuto.
# DoporuÄŤeno N vÄ›tĹˇĂ­ neĹľ NETWORK_RESTART_AFTER_FAILS v terkom-diagnostic.sh (napĹ™. 6 aĹľ 10).
#NETWORK_ONLY_REBOOT_AFTER_FAILS=0
EOF
    chown root:root "$NOTIFY_ENV_FILE"
    chmod 600 "$NOTIFY_ENV_FILE"
    echo "[INFO] Created notify env: $NOTIFY_ENV_FILE"
else
    echo "[INFO] Notify env already exists, keeping current values: $NOTIFY_ENV_FILE"
fi

RUNTIME_BEHAVIOR_INSTALL="$TERKOM_HOME/terkom-diagnostic.runtime.yaml"
if [ ! -f "$RUNTIME_BEHAVIOR_INSTALL" ]; then
    cat > "$RUNTIME_BEHAVIOR_INSTALL" <<'RUNEOF'
# Terkom Diagnostic â€” centrĂˇlnĂ­ chovĂˇnĂ­ (lze nasadit z cdn / Ansible na vĹˇechny terminĂˇly)
# SledovanĂ˝ skript: /opt/terkom-ng/terkom/scripts/terkom-diagnostic.sh
#
# === Funkce 1 â€” rychlost reakce (response) ===
#   relaxed  = pomalĂˇ eskalace (DIAG_MODE=NORMAL, profil safe, delĹˇĂ­ throttling e-mailĹŻ)
#   balanced = vyvĂˇĹľenĂ© (vĂ˝chozĂ­, pokud nic nenastavĂ­Ĺˇ)
#   reactive = rychlĂˇ reakce (DIAG_MODE=FAST, profil aggressive)
#
# === Funkce 2 â€” politika zotavenĂ­ (recovery) ===
#   safe | normal | aggressive | custom
#   Nebo pĹ™Ă­mo konkrĂ©tnĂ­ ÄŤĂ­sla: service_restart_after_fails, network_restart_after_fails, â€¦
#
# === Funkce 3 â€” maily (email_policy) ===
#   all         = e-mail u kaĹľdĂ© hlĂˇĹˇenĂ© udĂˇlosti (kromÄ› throttlingu stejnĂ©ho dĹŻvodu)
#   reboot_only = e-mail jen pokud tento bÄ›h dosĂˇhne prahu pro reboot
#   digest      = bÄ›ĹľnĂ© chyby jen do /run/terkom-diagnostic/digest_inbox.log, e-mail u plĂˇnovanĂ©ho rebootu v bÄ›hu
#
#   lock_recovery_thresholds: true  = FAST/ULTRA nepĹ™epĂ­Ĺˇou ÄŤĂ­selnĂ© prahy z YAML (profily z apply_diagnostic_profile.sh)
#   email_throttle_seconds: N        = volitelnÄ› pĹ™epĂ­Ĺˇe throttling z .terdiag-notify.env
#
terkom_diagnostic:
  response: balanced
  recovery: normal
  email_policy: all
  # recovery_enabled: "true"
  # diag_mode: NORMAL
  # network_only_reboot_after_fails: 0
  # kiosk_restart_cooldown_seconds: 20
  # max_service_restarts_per_incident: 1
  # max_network_restarts_per_incident: 2
  # max_kiosk_restarts_per_incident: 1
  # max_reboots_per_incident: 1
  # dead_reboot_after_failed_service_restart: true
  # local_ui_fail_fast_reboot: true
  # remote_tcp_no_loop_recovery: true
RUNEOF
    chown root:root "$RUNTIME_BEHAVIOR_INSTALL"
    chmod 644 "$RUNTIME_BEHAVIOR_INSTALL"
    echo "[INFO] VytvoĹ™en vĂ˝chozĂ­ $RUNTIME_BEHAVIOR_INSTALL (uprav z centrĂˇlnĂ­ho serveru)."
else
    echo "[INFO] Runtime chovĂˇnĂ­ jiĹľ existuje, ponechĂˇvĂˇm: $RUNTIME_BEHAVIOR_INSTALL"
fi

# ZapiĹˇ vĂ˝chozĂ­ profil (P1) pokud soubor jeĹˇtÄ› neexistuje â€” marker ---DIA_v*_P1 bude vĹľdy pĹ™Ă­tomen
if [ ! -f "$ACTIVE_PROFILE_FILE" ]; then
    echo "1" > "$ACTIVE_PROFILE_FILE"
    chown root:root "$ACTIVE_PROFILE_FILE"
    chmod 644 "$ACTIVE_PROFILE_FILE"
    echo "[INFO] VĂ˝chozĂ­ aktivnĂ­ profil: 1 â†’ $ACTIVE_PROFILE_FILE"
fi

bash -n "$DIAG_SCRIPT"
systemctl daemon-reload
systemctl enable --now terkom-diagnostic.timer
systemctl start terkom-diagnostic.service || true

# HlavnĂ­ Terkom (obal instalĂˇtoru) â€” u ÄŤistĂ© instalace restart kvĹŻli markeru / uvolnÄ›nĂ­ :3000. U bezkontaktnĂ­ aktualizace diagnostiky pĹ™eskoÄŤit (DIAG_UPDATE_ONLY=1).
_detect_terkom_service() {
    local svc

    # 1. Env override
    svc="${TERKOM_SERVICE:-}"
    if [ -n "$svc" ] && systemctl cat "$svc" &>/dev/null; then echo "$svc"; return; fi

    # 2. PĹ™eÄŤti SERVICE= z prĂˇvÄ› nainstalovanĂ©ho watchdog skriptu
    if [ -f "$DIAG_SCRIPT" ]; then
        svc="$(grep -m1 '^SERVICE=' "$DIAG_SCRIPT" 2>/dev/null | tr -d '"'"'" | cut -d= -f2 | xargs || true)"
        if [ -n "$svc" ] && [ "$svc" != "service" ] && systemctl cat "$svc" &>/dev/null; then
            echo "$svc"; return
        fi
    fi

    # 3. Zkus znĂˇmĂˇ jmĂ©na Terkom units
    for _c in terkom-ng-server terkom-ng terkom-server terkom kiosk; do
        if systemctl cat "$_c" &>/dev/null; then echo "$_c"; return; fi
    done

    # 4. Hledej pĹ™es systemctl list-units
    local found
    found="$(systemctl list-units --type=service --no-legend 2>/dev/null \
        | awk '{print $1}' \
        | grep -iE 'terkom|kiosk' \
        | grep -v 'terkom-diagnostic' \
        | head -1 || true)"
    if [ -n "$found" ] && systemctl cat "$found" &>/dev/null; then echo "$found"; return; fi

    echo ""
}

MAIN_SERVICE="$(_detect_terkom_service)"

if [ "$DIAG_UPDATE_ONLY" = "1" ] && [ "$FORCE_REINSTALL" = "1" ]; then
    echo "[INFO] PĹ™eskakuji restart hlavnĂ­ aplikace Terkom (${MAIN_SERVICE:-nedetekovĂˇno}) â€” restart provede update_diagnostic.sh po dokonÄŤenĂ­ (naÄŤtenĂ­ markeru ${DIAG_MARK:-} z config-local.yaml)."
elif [ -z "$MAIN_SERVICE" ]; then
    echo "[WARN] NepodaĹ™ilo se detekovat Terkom systemd unit â€” Terkom NEBYL restartovĂˇn."
    echo "       Zjisti jmĂ©no service: sudo systemctl list-units --type=service | grep -iE 'terkom|kiosk'"
    echo "       Pak restartuj ruÄŤnÄ›:  sudo systemctl restart <jmeno-service>"
else
    echo "[INFO] Nalezena Terkom unit: $MAIN_SERVICE"
    echo "[INFO] ObnovenĂ­ Terkomu po instalaci: stop â†’ killall node â†’ start ($MAIN_SERVICE) kvĹŻli markeru v popisu / uvolnÄ›nĂ­ portu 3000..."
    systemctl stop "$MAIN_SERVICE" || true
    killall node 2>/dev/null || true
    sleep 2
    systemctl reset-failed "$MAIN_SERVICE" 2>/dev/null || true
    if systemctl start "$MAIN_SERVICE"; then
        sleep 2
        if systemctl is-active --quiet "$MAIN_SERVICE"; then
            echo "[OK] Terkom ($MAIN_SERVICE) je active po instalaci."
        else
            echo "[WARN] start vrĂˇtil 0, ale jednotka nenĂ­ active:"
            systemctl status "$MAIN_SERVICE" --no-pager -l 2>&1 | head -n 25 || true
        fi
    else
        echo "[WARN] systemctl start '$MAIN_SERVICE' selhal, zkouĹˇĂ­m restart..."
        if systemctl restart "$MAIN_SERVICE"; then
            sleep 2
            systemctl is-active --quiet "$MAIN_SERVICE" \
                && echo "[OK] Terkom ($MAIN_SERVICE) active po restart." \
                || systemctl status "$MAIN_SERVICE" --no-pager -l 2>&1 | head -n 25 || true
        else
            echo "[WARN] NepodaĹ™ilo se spustit Terkom ($MAIN_SERVICE)."
            echo "       RuÄŤnÄ›: sudo systemctl stop $MAIN_SERVICE && killall node && sudo systemctl start $MAIN_SERVICE"
            systemctl status "$MAIN_SERVICE" --no-pager -l 2>&1 | head -n 20 || true
        fi
    fi
fi

# StĂˇhni pomocnĂ© scripty na zaĹ™Ă­zenĂ­ (diag_info, apply_profile) â€” spustitelnĂ© lokĂˇlnÄ› bez internetu
CDN_BASE="${CDN_BASE:-https://cdn.public.altisima.cz/diagnostika}"
_install_helper() {
    local name="$1" dest="$2"
    if curl -fsSL --connect-timeout 10 "${CDN_BASE}/${name}" -o "$dest" 2>/dev/null; then
        chmod 755 "$dest"
        chown root:root "$dest"
        echo "[OK]   $dest"
    else
        echo "[WARN] NepodaĹ™ilo se stĂˇhnout ${name} z CDN â€” helper bude dostupnĂ˝ pĹ™es: curl -fsSL ${CDN_BASE}/${name} | sudo bash"
    fi
}
echo "[INFO] Instalace pomocnĂ˝ch skriptĹŻ (vĹľdy stahuje aktuĂˇlnĂ­ verze z CDN):"
_install_helper "diag_info.sh"               "$SCRIPT_DIR/diag_info.sh"
_install_helper "apply_diagnostic_profile.sh" "$SCRIPT_DIR/apply_diagnostic_profile.sh"
_install_helper "check_diagnostic.sh"         "$SCRIPT_DIR/check_diagnostic.sh"
_install_helper "set_ai_report_mail.sh"       "$SCRIPT_DIR/set_ai_report_mail.sh"

echo "[OK] Terkom Diagnostic V${INSTALLER_VERSION} installation complete."
echo "[INFO] Helper skripty: $SCRIPT_DIR/diag_info.sh | $SCRIPT_DIR/apply_diagnostic_profile.sh | $SCRIPT_DIR/check_diagnostic.sh | $SCRIPT_DIR/set_ai_report_mail.sh"
echo "[INFO] Timer status:"
systemctl status terkom-diagnostic.timer --no-pager || true
echo "[INFO] Recent logs:"
journalctl -u terkom-diagnostic.service -n 30 --no-pager || true

remount_ro
