#!/bin/bash
#########################################################
# nwhlt.sh – Network Health Check
#
# Modi:
#   check  – Layer-Checks + LTE-Signal akkumulieren (nwHealth + sigDaily)
#            (Cron: jede Minute)
#   upload – State auslesen, LTE-Snapshot, JSON hochladen,
#            State zurücksetzen
#            (Cron: alle 15 Minuten)
#   daily  – Tagesupload sigDaily (RSRP/RSRQ/SNR pro Stunde)
#            (Cron: 45 3 * * * – kurz vor 04:00-Reboot)
#
# State-Files: ~/bin/nwhlt_state.json, ~/bin/nwhlt_sig_daily.json
# Upload-Keys: "nwHealth", "sigDaily"
#########################################################

MODE="${1:-check}"
BIN=/home/snowtv/bin
LOG=/var/log/stv.log
STATE_FILE="$BIN/nwhlt_state.json"
STATE_TMP="${STATE_FILE}.tmp"
SIG_DAILY_FILE="$BIN/nwhlt_sig_daily.json"
MC_CFG="$BIN/mc.cfg"
TS=$(date '+%Y-%m-%dT%H:%M:%S')

_log() { echo "$(date '+%Y-%m-%d %H:%M:%S') nwhlt[$MODE]: $*" >> "$LOG"; }

# ── State-Verwaltung ──────────────────────────────────────

_init_state() {
    jq -n --arg ts "$TS" '{
        period_start:  $ts,
        checks:        0,
        gw_ok: 0,   gw_fail: 0,
        inet_ok: 0, inet_fail: 0,
        dns_ok: 0,  dns_fail: 0,
        srv_ok: 0,  srv_fail: 0,
        outage_active: 0,
        outage_layer:  "",
        outage_start:  "",
        outages:       [],
        lte_sig_sum:   0,
        lte_sig_min:   100,
        lte_sig_max:   0,
        lte_sig_count: 0
    }'
}

_load_state() {
    [ -f "$STATE_FILE" ] && cat "$STATE_FILE" || _init_state
}

_save_state() {
    echo "$1" > "$STATE_TMP" && mv "$STATE_TMP" "$STATE_FILE"
}

# ── Layer-Checks ──────────────────────────────────────────

_check_layers() {
    local gw=0 inet=0 dns=0 srv=0

    # L1+L2: Default-Gateway erreichbar?
    local GW_IP
    GW_IP=$(ip route show default 2>/dev/null | grep -oP 'via \K\S+' | head -1)
    if [ -n "$GW_IP" ] && ping -c1 -W1 -q "$GW_IP" &>/dev/null; then
        gw=1
    fi

    # L3: Internet erreichbar? (nur wenn Gateway ok)
    if [ "$gw" = "1" ] && ping -c1 -W1 -q 1.1.1.1 &>/dev/null; then
        inet=1
    fi

    # L4: DNS funktioniert? (nur wenn Internet ok)
    if [ "$inet" = "1" ] && getent hosts google.com &>/dev/null; then
        dns=1
    fi

    # L5: infosnow-Server erreichbar? (nur wenn Internet ok)
    if [ "$inet" = "1" ]; then
        local HTTP
        HTTP=$(curl -k --connect-timeout 3 --max-time 5 -s \
            -o /dev/null -w "%{http_code}" "https://infosnow.ch/" 2>/dev/null)
        case "$HTTP" in 200|301|302|304) srv=1 ;; esac
    fi

    echo "$gw $inet $dns $srv"
}

# ── LTE-Signal (schnell) ──────────────────────────────────

_get_lte_signal() {
    local MODEM_IDX
    MODEM_IDX=$(mmcli -L 2>/dev/null | grep -oP 'Modem/\K[0-9]+' | head -1)
    [ -z "$MODEM_IDX" ] && return
    mmcli -m "$MODEM_IDX" 2>/dev/null | grep -oP 'signal quality:\s+\K[0-9]+'
}

# ── LTE-Signal pro Stunde akkumulieren (für sigDaily) ─────

_sig_accumulate() {
    # Zeitfenster prüfen: START−2h bis END+2h aus mc.cfg
    local START END
    START=$(grep '^START=' "$MC_CFG" 2>/dev/null | cut -d= -f2 | tr -d '"' | xargs)
    END=$(grep   '^END='   "$MC_CFG" 2>/dev/null | cut -d= -f2 | tr -d '"' | xargs)
    START="${START:-06:00}"
    END="${END:-22:00}"

    local NOW_EPOCH WIN_START WIN_END
    NOW_EPOCH=$(date +%s)
    WIN_START=$(date -d "$START today" +%s 2>/dev/null)
    WIN_END=$(date   -d "$END today"   +%s 2>/dev/null)
    WIN_START=$((WIN_START - 7200))   # −2h
    WIN_END=$((WIN_END     + 7200))   # +2h

    [ "$NOW_EPOCH" -lt "$WIN_START" ] && return
    [ "$NOW_EPOCH" -gt "$WIN_END"   ] && return

    # Kein Modem → überspringen
    local MODEM_IDX
    MODEM_IDX=$(mmcli -L 2>/dev/null | grep -oP 'Modem/\K[0-9]+' | head -1)
    [ -z "$MODEM_IDX" ] && return

    # Detaillierte Signalwerte lesen
    local SIG_DETAIL rsrp rsrq snr
    SIG_DETAIL=$(mmcli -m "$MODEM_IDX" --signal-get 2>/dev/null)
    rsrp=$(echo "$SIG_DETAIL" | grep -oP 'rsrp:\s+\K[-0-9.]+')
    rsrq=$(echo "$SIG_DETAIL" | grep -oP 'rsrq:\s+\K[-0-9.]+')
    snr=$(echo  "$SIG_DETAIL" | grep -oP 's/n:\s+\K[-0-9.]+')

    # Mindestens ein Wert muss lesbar sein
    [ -z "$rsrp" ] && [ -z "$rsrq" ] && return

    rsrp="${rsrp:-0}"
    rsrq="${rsrq:-0}"
    snr="${snr:-0}"

    local TODAY HOUR
    TODAY=$(date '+%Y-%m-%d')
    HOUR=$(date  '+%H')

    # State-File lesen – bei neuem Tag oder fehlendem File neu initialisieren
    local DAILY
    if [ -f "$SIG_DAILY_FILE" ]; then
        DAILY=$(cat "$SIG_DAILY_FILE")
        local FILE_DATE
        FILE_DATE=$(echo "$DAILY" | jq -r '.date // ""')
        if [ "$FILE_DATE" != "$TODAY" ]; then
            DAILY=$(jq -n --arg date "$TODAY" '{"date":$date,"hours":{}}')
        fi
    else
        DAILY=$(jq -n --arg date "$TODAY" '{"date":$date,"hours":{}}')
    fi

    # Stunden-Bucket aktualisieren
    DAILY=$(echo "$DAILY" | jq \
        --arg  hour "$HOUR" \
        --argjson rsrp "$rsrp" \
        --argjson rsrq "$rsrq" \
        --argjson snr  "$snr"  \
        '.hours[$hour] //= {rsrp_sum:0,rsrp_min:999,rsrp_max:-999,
                             rsrq_sum:0,rsrq_min:999,rsrq_max:-999,
                             snr_sum:0, snr_min:999, snr_max:-999, n:0} |
         .hours[$hour].rsrp_sum += $rsrp |
         .hours[$hour].rsrp_min  = (if $rsrp < .hours[$hour].rsrp_min then $rsrp else .hours[$hour].rsrp_min end) |
         .hours[$hour].rsrp_max  = (if $rsrp > .hours[$hour].rsrp_max then $rsrp else .hours[$hour].rsrp_max end) |
         .hours[$hour].rsrq_sum += $rsrq |
         .hours[$hour].rsrq_min  = (if $rsrq < .hours[$hour].rsrq_min then $rsrq else .hours[$hour].rsrq_min end) |
         .hours[$hour].rsrq_max  = (if $rsrq > .hours[$hour].rsrq_max then $rsrq else .hours[$hour].rsrq_max end) |
         .hours[$hour].snr_sum  += $snr  |
         .hours[$hour].snr_min   = (if $snr  < .hours[$hour].snr_min  then $snr  else .hours[$hour].snr_min  end) |
         .hours[$hour].snr_max   = (if $snr  > .hours[$hour].snr_max  then $snr  else .hours[$hour].snr_max  end) |
         .hours[$hour].n        += 1')

    echo "$DAILY" > "${SIG_DAILY_FILE}.tmp" && mv "${SIG_DAILY_FILE}.tmp" "$SIG_DAILY_FILE"
}

# ── Modus: check (jede Minute) ────────────────────────────

_mode_check() {
    local STATE
    STATE=$(_load_state)

    read -r gw inet dns srv <<< "$(_check_layers)"

    # Outage-State-Machine
    local any_fail=0
    [ "$gw" = "0" ] || [ "$inet" = "0" ] || [ "$dns" = "0" ] || [ "$srv" = "0" ] && any_fail=1

    local outage_active
    outage_active=$(echo "$STATE" | jq '.outage_active')

    if [ "$any_fail" = "1" ] && [ "$outage_active" = "0" ]; then
        # Outage beginnt – erste ausgefallene Schicht bestimmen
        local first_fail="srv"
        [ "$dns"  = "0" ] && first_fail="dns"
        [ "$inet" = "0" ] && first_fail="inet"
        [ "$gw"   = "0" ] && first_fail="gw"
        STATE=$(echo "$STATE" | jq \
            --arg layer "$first_fail" \
            --arg ts    "$TS" \
            '.outage_active = 1 | .outage_layer = $layer | .outage_start = $ts')

    elif [ "$any_fail" = "0" ] && [ "$outage_active" = "1" ]; then
        # Outage endet – Dauer berechnen und in Liste schreiben
        local outage_layer outage_start dur_s
        outage_layer=$(echo "$STATE" | jq -r '.outage_layer')
        outage_start=$(echo "$STATE" | jq -r '.outage_start')
        dur_s=$(( $(date +%s) - $(date -d "$outage_start" +%s 2>/dev/null || date +%s) ))
        STATE=$(echo "$STATE" | jq \
            --arg layer "$outage_layer" \
            --arg start "$outage_start" \
            --argjson dur "$dur_s" \
            '.outages += [{"layer":$layer,"start":$start,"dur_s":$dur}] |
             .outage_active = 0 | .outage_layer = "" | .outage_start = ""')
    fi

    # Zähler aktualisieren
    STATE=$(echo "$STATE" | jq \
        --argjson gw   "$gw"   \
        --argjson inet "$inet" \
        --argjson dns  "$dns"  \
        --argjson srv  "$srv"  \
        '.checks    += 1 |
         .gw_ok     += $gw        | .gw_fail   += (1-$gw)   |
         .inet_ok   += $inet      | .inet_fail += (1-$inet) |
         .dns_ok    += $dns       | .dns_fail  += (1-$dns)  |
         .srv_ok    += $srv       | .srv_fail  += (1-$srv)')

    # LTE-Signal akkumulieren
    local SIG
    SIG=$(_get_lte_signal)
    if [[ "$SIG" =~ ^[0-9]+$ ]]; then
        STATE=$(echo "$STATE" | jq \
            --argjson sig "$SIG" \
            '.lte_sig_sum   += $sig |
             .lte_sig_count += 1 |
             .lte_sig_min    = (if $sig < .lte_sig_min then $sig else .lte_sig_min end) |
             .lte_sig_max    = (if $sig > .lte_sig_max then $sig else .lte_sig_max end)')
    fi

    _save_state "$STATE"

    # LTE-Signalwerte für sigDaily akkumulieren (innerhalb Betriebszeit ±2h)
    _sig_accumulate
}

# ── Modus: upload (alle 15 Minuten) ──────────────────────

_mode_upload() {
    local TOK ID DOMAIN
    TOK="$(cat $BIN/token 2>/dev/null)"
    ID="$($BIN/getid.sh)"
    DOMAIN="$($BIN/getdomain.sh | tr -d "[']")"

    # State lesen + sofort zurücksetzen (neue Periode beginnt)
    local STATE
    STATE=$(_load_state)
    _save_state "$(_init_state)"

    # Noch laufende Outage abschliessen
    local outage_active
    outage_active=$(echo "$STATE" | jq '.outage_active')
    if [ "$outage_active" = "1" ]; then
        local outage_layer outage_start dur_s
        outage_layer=$(echo "$STATE" | jq -r '.outage_layer')
        outage_start=$(echo "$STATE" | jq -r '.outage_start')
        dur_s=$(( $(date +%s) - $(date -d "$outage_start" +%s 2>/dev/null || date +%s) ))
        STATE=$(echo "$STATE" | jq \
            --arg layer "$outage_layer" \
            --arg start "$outage_start" \
            --argjson dur "$dur_s" \
            '.outages += [{"layer":$layer,"start":$start,"dur_s":$dur,"open":true}]')
    fi

    # ETH-Info
    local DEF_IFACE ETH_STATE ETH_IP
    DEF_IFACE=$(ip route show default 2>/dev/null | grep -oP 'dev \K\S+' | head -1)
    ETH_STATE=$(ip link show "$DEF_IFACE" 2>/dev/null | grep -oP 'state \K\S+' | head -1 | tr '[:upper:]' '[:lower:]')
    ETH_IP=$(ip addr show "$DEF_IFACE" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1)

    # LTE-Snapshot
    local LTE_JSON="null"
    local MODEM_IDX
    MODEM_IDX=$(mmcli -L 2>/dev/null | grep -oP 'Modem/\K[0-9]+' | head -1)
    if [ -n "$MODEM_IDX" ]; then
        local MODEM_INFO SIM_IDX SIM_INFO
        MODEM_INFO=$(mmcli -m "$MODEM_IDX" 2>/dev/null)
        SIM_IDX=$(echo "$MODEM_INFO" | grep -oP 'primary sim path:.*SIM/\K[0-9]+')
        SIM_INFO=$(mmcli -i "$SIM_IDX" 2>/dev/null)

        local lte_state lte_ip lte_operator lte_tech lte_reg lte_sig
        lte_state=$(echo "$MODEM_INFO" | grep -oP 'state:\s+\K\S+'    | tail -1)
        lte_ip=$(ip addr 2>/dev/null   | grep -oP 'inet \K10\.\S+|inet \K172\.\S+' | grep -v '127\.' | tail -1 | cut -d/ -f1)
        lte_operator=$(echo "$SIM_INFO"  | grep -oP 'operator name:\s+\K.+' | head -1 | xargs)
        lte_tech=$(echo "$MODEM_INFO"    | grep -oP 'access tech:\s+\K\S+')
        lte_reg=$(echo "$MODEM_INFO"     | grep -oP 'registration:\s+\K\S+')
        lte_sig=$(echo "$MODEM_INFO"     | grep -oP 'signal quality:\s+\K[0-9]+')

        # Aggregierte Signalwerte aus State (Fallback: aktueller Wert)
        local sig_count sig_avg sig_min sig_max
        sig_count=$(echo "$STATE" | jq '.lte_sig_count')
        if [ "${sig_count:-0}" -gt 0 ] 2>/dev/null; then
            sig_avg=$(echo "$STATE" | jq '.lte_sig_sum / .lte_sig_count | round')
            sig_min=$(echo "$STATE" | jq '.lte_sig_min')
            sig_max=$(echo "$STATE" | jq '.lte_sig_max')
        else
            sig_avg="${lte_sig:-0}"
            sig_min="${lte_sig:-0}"
            sig_max="${lte_sig:-0}"
        fi

        # Detaillierte Signalwerte (RSSI/RSRP/RSRQ/SNR) via --signal-get
        local SIG_DETAIL lte_rssi lte_rsrp lte_rsrq lte_snr
        SIG_DETAIL=$(mmcli -m "$MODEM_IDX" --signal-get 2>/dev/null)
        lte_rssi=$(echo "$SIG_DETAIL" | grep -oP 'rssi:\s+\K[-0-9.]+')
        lte_rsrp=$(echo "$SIG_DETAIL" | grep -oP 'rsrp:\s+\K[-0-9.]+')
        lte_rsrq=$(echo "$SIG_DETAIL" | grep -oP 'rsrq:\s+\K[-0-9.]+')
        lte_snr=$(echo  "$SIG_DETAIL" | grep -oP 's/n:\s+\K[-0-9.]+')

        LTE_JSON=$(jq -n \
            --arg  state    "${lte_state}"    \
            --arg  ip       "${lte_ip}"       \
            --arg  operator "${lte_operator}" \
            --arg  tech     "${lte_tech}"     \
            --arg  reg      "${lte_reg}"      \
            --argjson sig_avg "$sig_avg"      \
            --argjson sig_min "$sig_min"      \
            --argjson sig_max "$sig_max"      \
            --arg  rssi     "${lte_rssi}"     \
            --arg  rsrp     "${lte_rsrp}"     \
            --arg  rsrq     "${lte_rsrq}"     \
            --arg  snr      "${lte_snr}"      \
            '{state:$state, ip:$ip, operator:$operator, tech:$tech, reg:$reg,
              sig_avg:$sig_avg, sig_min:$sig_min, sig_max:$sig_max,
              rssi:$rssi, rsrp:$rsrp, rsrq:$rsrq, snr:$snr}')
    fi

    # JSON zusammenbauen – kompaktes Hybrid-Format
    # f:   [gw_fail, inet_fail, dns_fail, srv_fail]  (ok = n - fail)
    # o:   [[layer, start, dur_s], ...]
    # eth: "state|ip|iface"
    # lte: "state|operator|tech|reg|sig_avg|sig_min|sig_max|rssi|rsrp|rsrq|snr"
    local ETH_STR LTE_STR
    ETH_STR="${ETH_STATE}|${ETH_IP}|${DEF_IFACE}"
    if [ "$LTE_JSON" != "null" ]; then
        LTE_STR=$(echo "$LTE_JSON" | jq -r '"\(.state)|\(.operator)|\(.tech)|\(.reg)|\(.sig_avg)|\(.sig_min)|\(.sig_max)|\(.rssi)|\(.rsrp)|\(.rsrq)|\(.snr)"')
    else
        LTE_STR=""
    fi

    local JSON
    JSON=$(echo "$STATE" | jq \
        --arg  ts   "$TS"      \
        --arg  eth  "$ETH_STR" \
        --arg  lte  "$LTE_STR" \
        '{nwHealth: {
            ts: $ts,
            ps: .period_start,
            n:  .checks,
            f:  [.gw_fail, .inet_fail, .dns_fail, .srv_fail],
            o:  [.outages[] | [.layer, .start, .dur_s]],
            eth: $eth,
            lte: $lte
        }}')

    # Upload – kein Token → überspringen
    if [ -z "$TOK" ]; then
        _log "kein Token – Upload übersprungen"
        return
    fi

    local checks outage_count any_fail
    checks=$(echo "$STATE" | jq '.checks')
    outage_count=$(echo "$STATE" | jq '.outages | length')
    any_fail=$(echo "$STATE" | jq '.gw_fail + .inet_fail + .dns_fail + .srv_fail')

    # LTE-Signalqualität prüfen
    # Theoretische Schwellwerte (3GPP TS 36.133 / Teltonika / Rohde & Schwarz):
    #   RSRQ <= -15, SNR < -10, RSRP < -110
    # Angepasst aufgrund Feldversuch Villars-Player (2026-03-13):
    #   RSRQ -18 dB, SNR -16 dB → 45 Mbit/s Durchsatz, Player voll funktionsfähig.
    #   LTE kompensiert via adaptive Modulation + HARQ. Theoretische Schwellwerte
    #   erzeugen False Alarms. SNR als Trigger entfernt; RSRQ auf -19 (nahe
    #   3GPP-Floor -19.5 dB) verschärft; RSRP -110 unverändert.
    local lte_alarm=0
    if [ -n "$lte_rsrp" ] || [ -n "$lte_rsrq" ]; then
        local rsrp_int rsrq_int
        rsrp_int=$(echo "${lte_rsrp:-0}" | awk '{printf "%d", $1}')
        rsrq_int=$(echo "${lte_rsrq:-0}" | awk '{printf "%d", $1}')
        [ "$rsrq_int" -le -19  ] && lte_alarm=1
        [ "$rsrp_int" -lt -110 ] && lte_alarm=1
    fi

    # Nur uploaden wenn Netzwerk-Fehler, Outages oder LTE-Alarm – reine OK-Perioden überspringen
    if [ "${outage_count}" = "0" ] && [ "${any_fail}" = "0" ] && [ "${lte_alarm}" = "0" ]; then
        _log "Alles OK (${checks} Checks) – kein Upload nötig"
        return
    fi

    local HTTP
    HTTP=$(curl -k --connect-timeout 20 --max-time 40 -s -w "%{http_code}" -o /dev/null \
        --data "$JSON" \
        -H "APG-Authorization: ${TOK}" \
        "${DOMAIN}/apg-mp?device=${ID}" \
        --user-agent "apg-mp")

    if [ "$HTTP" = "200" ]; then
        _log "Upload OK – ${checks} Checks, ${outage_count} Outage(s), ${any_fail} Fail(s), LTE-Alarm=${lte_alarm}"
    else
        _log "Upload fehlgeschlagen (HTTP $HTTP)"
    fi
}

# ── Modus: daily (täglich 03:45, kurz vor Reboot) ─────────

_mode_daily() {
    local TOK ID DOMAIN
    TOK="$(cat $BIN/token 2>/dev/null)"
    [ -z "$TOK" ] && { _log "kein Token – Daily-Upload übersprungen"; return; }

    if [ ! -f "$SIG_DAILY_FILE" ]; then
        _log "kein Sig-Daily-State – Daily-Upload übersprungen"
        return
    fi

    local DAILY n_hours
    DAILY=$(cat "$SIG_DAILY_FILE")
    n_hours=$(echo "$DAILY" | jq '.hours | length')
    if [ "${n_hours:-0}" = "0" ]; then
        _log "keine Signaldaten – Daily-Upload übersprungen"
        return
    fi

    ID="$($BIN/getid.sh)"
    DOMAIN="$($BIN/getdomain.sh | tr -d "[']")"

    # Kompaktes JSON:
    # h[HH] = [rsrp_avg, rsrp_min, rsrp_max, rsrq_avg, rsrq_min, rsrq_max, snr_avg, snr_min, snr_max, n]
    # rsrp/rsrq gerundet auf ganze dBm, snr auf eine Nachkommastelle
    local JSON
    JSON=$(echo "$DAILY" | jq \
        --arg ts "$TS" \
        '{sigDaily: {
            ts:   $ts,
            date: .date,
            h: (.hours | to_entries | map({
                key:   .key,
                value: [
                    (.value.rsrp_sum / .value.n | round),
                    .value.rsrp_min,
                    .value.rsrp_max,
                    (.value.rsrq_sum / .value.n | round),
                    .value.rsrq_min,
                    .value.rsrq_max,
                    ((.value.snr_sum / .value.n * 10 | round) / 10),
                    .value.snr_min,
                    .value.snr_max,
                    .value.n
                ]
            }) | from_entries)
        }}')

    local HTTP
    HTTP=$(curl -k --connect-timeout 20 --max-time 40 -s -w "%{http_code}" -o /dev/null \
        --data "$JSON" \
        -H "APG-Authorization: ${TOK}" \
        "${DOMAIN}/apg-mp?device=${ID}" \
        --user-agent "apg-mp")

    if [ "$HTTP" = "200" ]; then
        _log "sigDaily Upload OK – ${n_hours} Stunden"
        rm -f "$SIG_DAILY_FILE"
    else
        _log "sigDaily Upload fehlgeschlagen (HTTP $HTTP) – State bleibt erhalten"
    fi
}

# ── Entry Point ───────────────────────────────────────────

case "$MODE" in
    check)  _mode_check  ;;
    upload) _mode_upload ;;
    daily)  _mode_daily  ;;
    *)      echo "Usage: $0 check|upload|daily"; exit 1 ;;
esac

exit 0
