#!/bin/sh
#
# Copyright (C) 2018-2026 Ycarus (Yannick Chabanois) <ycarus@zugaina.org> for OpenMPTCProuter
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# This script logs a timestamped event every time an interface tracked by
# omr-tracker changes state: up, down, high latency, packet loss or
# disconnection. The tracker itself already flips OMR_TRACKER_STATUS to
# ERROR (with a descriptive OMR_TRACKER_STATUS_MSG) on link loss, gateway
# loss or a latency/packet-loss threshold breach (see omr-tracker's
# OMR_TRACKER_CHECK_QUALITY logic), so this script only has to watch for a
# status transition and classify the reason from that message - it does not
# re-implement any threshold detection.
#
# Events are appended as one JSON object per line to $EVENTS_FILE and kept
# under a configurable age and/or size cap (omr-events.settings.max_age /
# max_size), whichever limit is hit first. The file lives in /tmp (tmpfs) to
# avoid flash wear, same as omr-metrics' /tmp/metrics.

[ -z "$OMR_TRACKER_INTERFACE" ] && exit 0
# Nothing changed: this hook still runs every post-tracking cycle (throttled
# to OMR_TRACKER_POST_INTERVAL), so bail out cheaply before touching uci/fs.
[ "$OMR_TRACKER_PREV_STATUS" = "$OMR_TRACKER_STATUS" ] && exit 0

[ "$(uci -q get omr-events.settings.enabled)" = "0" ] && exit 0

EVENTS_DIR="${OMR_EVENTS_DIR:-/tmp/omr-events}"
EVENTS_FILE="${EVENTS_DIR}/events.log"
[ -d "$EVENTS_DIR" ] || mkdir -p "$EVENTS_DIR" 2>/dev/null

max_age=$(uci -q get omr-events.settings.max_age)
[ -z "$max_age" ] && max_age=172800
max_size=$(uci -q get omr-events.settings.max_size)
[ -z "$max_size" ] && max_size=10485760

now=$(date +%s)

case "$OMR_TRACKER_STATUS" in
	OK)    event="up" ;;
	ERROR) event="down" ;;
	*)     event="$OMR_TRACKER_STATUS" ;;
esac

# Classify the free-form tracker message into a short machine-readable
# reason so a UI/API consumer can filter or color by cause without parsing
# OMR_TRACKER_STATUS_MSG itself. Patterns mirror the exact strings the
# tracker daemon and 002-error emit (bin/omr-tracker, post-tracking.d/002-error).
msg="$OMR_TRACKER_STATUS_MSG"
case "$msg" in
	*"Packet loss is"*)          reason="packet_loss" ;;
	*"Latency is"*)               reason="high_latency" ;;
	*"Congestion score is"*)      reason="congestion" ;;
	*"link down"*)                 reason="link_down" ;;
	*"gateway down"*)              reason="gateway_down" ;;
	*"No answer from server"*)     reason="no_answer" ;;
	*"No access to server API"*)   reason="no_answer" ;;
	*"No IP"*|*"ip issues"*)       reason="no_ip" ;;
	*"Glorytun-UDP path"*)         reason="vpn_path" ;;
	"")
		if [ "$event" = "up" ]; then
			reason="recovered"
		else
			reason="unknown"
		fi
	;;
	*) reason="other" ;;
esac

# JSON-escape the free-form message (backslash and double-quote only, the
# tracker never emits control characters here)
msg_esc=$(printf '%s' "$msg" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')

_jval() {
	if [ -n "$1" ] && [ "$1" != "--" ]; then
		echo "$1"
	else
		echo "null"
	fi
}

# Every tracker runs this hook for its own interface, and a WAN problem
# changes several of them at once: one prune rewrote the file from a copy
# taken before another tracker's append, or two of them shared the same .tmp
# file, and the whole history was lost (1 round in 6 with six trackers).
# Busybox flock has no -w: poll it, and go ahead unlocked rather than never.
[ -w "$EVENTS_DIR" ] || exit 0
exec 7>>"${EVENTS_FILE}.lock"
if command -v flock >/dev/null 2>&1; then
	_tries=0
	until flock -n 7 2>/dev/null; do
		_tries=$((_tries + 1))
		[ "$_tries" -ge 10 ] && break
		sleep 1
	done
fi

# "ts" must stay the first key: the retention pass below extracts it with a
# cheap awk split instead of a full JSON parse.
printf '{"ts":%s,"interface":"%s","device":"%s","event":"%s","reason":"%s","message":"%s","latency":%s,"loss":%s}\n' \
	"$now" "$OMR_TRACKER_INTERFACE" "$OMR_TRACKER_DEVICE" "$event" "$reason" "$msg_esc" \
	"$(_jval "$OMR_TRACKER_LATENCY")" "$(_jval "$OMR_TRACKER_LOSS")" >> "$EVENTS_FILE" 2>/dev/null

logger -t omr-events "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) $event: ${msg:-$reason}"

# Prune by age then by size (whichever limit is hit first) in a single pass:
# drop lines older than the cutoff, then drop from the oldest remaining line
# until the file is back under the size cap. Only when a limit is hit: the
# lines are in time order, so the first one tells whether any is too old.
cutoff=$((now - max_age))
size=$(wc -c < "$EVENTS_FILE" 2>/dev/null)
first_ts=$(head -n 1 "$EVENTS_FILE" 2>/dev/null | sed -n 's/^{"ts":\([0-9]*\),.*/\1/p')
if [ "${size:-0}" -gt "$max_size" ] || [ "${first_ts:-0}" -lt "$cutoff" ]; then
	awk -v cutoff="$cutoff" -v maxsize="$max_size" '
		{
			split($0, a, "\"ts\":")
			split(a[2], b, ",")
			ts = b[1] + 0
			if (ts >= cutoff) {
				n++
				lines[n] = $0
				sz = length($0) + 1
				sizes[n] = sz
				total += sz
			}
		}
		END {
			start = 1
			while (total > maxsize && start <= n) { total -= sizes[start]; start++ }
			for (i = start; i <= n; i++) print lines[i]
		}' "$EVENTS_FILE" > "${EVENTS_FILE}.tmp.$$" 2>/dev/null && mv "${EVENTS_FILE}.tmp.$$" "$EVENTS_FILE" 2>/dev/null
	rm -f "${EVENTS_FILE}.tmp.$$"
fi

exit 0
