#!/bin/bash
# Monit self-heartbeat: detailed Telegram health report with UUID.
# Deployed to /usr/local/bin/obdpy-monit-heartbeat.sh
# Called by Monit's "check program" as uid "tca" every 10 cycles (~20 min).
# Exit 0 = heartbeat sent successfully.
#
# Queries monit HTTP API (localhost:2812) for per-service status, memory,
# uptime, response times. Includes package version, disk, system metrics.
# Each report has a unique UUID for traceability.

set -euo pipefail

ENV_FILE="/home/tca/opendeviationbar-py/deploy/opendeviationbar.local.env"
GIT_ENV="/home/tca/opendeviationbar-py/deploy/opendeviationbar.env"
VENV="/home/tca/opendeviationbar-py/.venv"

[ -f "$ENV_FILE" ] && source "$ENV_FILE"
[ -f "$GIT_ENV" ] && source "$GIT_ENV"

if [ -z "${OPENDEVIATIONBAR_TELEGRAM_TOKEN:-}" ] || [ -z "${OPENDEVIATIONBAR_TELEGRAM_CHAT_ID:-}" ]; then
    echo "ERROR: Telegram credentials not set" >&2
    exit 1
fi

UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')

# Get package version
PKG_VERSION=$("$VENV/bin/python3" -c 'from opendeviationbar import __version__; print(__version__)' 2>/dev/null || echo "unknown")

# Parse monit XML via Python for reliable extraction
REPORT=$("$VENV/bin/python3" - "$UUID" "$PKG_VERSION" << 'PYEOF'
import sys
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timezone

uuid = sys.argv[1]
pkg_version = sys.argv[2]

try:
    with urllib.request.urlopen("http://localhost:2812/_status?format=xml", timeout=10) as resp:
        xml_data = resp.read()
    root = ET.fromstring(xml_data)
except Exception as e:
    print(f"\u274c <b>MONIT HEARTBEAT</b> (API error: {e})\n<code>{uuid}</code>")
    sys.exit(0)

# Server info
server = root.find("server")
monit_uptime = int(server.findtext("uptime", "0"))
days, rem = divmod(monit_uptime, 86400)
hours, rem = divmod(rem, 3600)
mins = rem // 60
monit_up_str = f"{days}d {hours}h {mins}m" if days else f"{hours}h {mins}m"

# Collect per-service status
lines = []
all_ok = True
failed_services = []

for svc in root.findall("service"):
    name = svc.findtext("name", "?")
    status = int(svc.findtext("status", "-1"))
    svc_type = svc.get("type", "?")
    icon = "\u2705" if status == 0 else "\u274c"
    if status != 0:
        all_ok = False
        failed_services.append(name)

    detail = ""

    # Process type (3) — has PID, memory, CPU, uptime
    if svc_type == "3":
        pid = svc.findtext("pid", "?")
        mem_kb = int(svc.findtext("memory/kilobyte", "0"))
        mem_mb = mem_kb / 1024
        mem_pct = float(svc.findtext("memory/percent", "0"))
        cpu_pct = float(svc.findtext("cpu/percent", "0"))
        threads = svc.findtext("threads", "?")
        fds = svc.findtext("filedescriptors/open", "?")
        uptime_s = int(svc.findtext("uptime", "0"))
        ud, ur = divmod(uptime_s, 86400)
        uh, ur = divmod(ur, 3600)
        um = ur // 60
        up_str = f"{ud}d {uh}h {um}m" if ud else f"{uh}h {um}m"
        detail = f"\n   PID {pid} | {mem_mb:.0f}MB ({mem_pct:.1f}%) | CPU {cpu_pct:.1f}% | {threads} threads | {fds} fds | up {up_str}"

    # Host type (4) — has response time
    elif svc_type == "4":
        port_el = svc.find("port")
        if port_el is not None:
            rt = float(port_el.findtext("responsetime", "0"))
            port = port_el.findtext("portnumber", "?")
            proto = port_el.findtext("protocol", "?")
            detail = f"\n   :{port} {proto} | response {rt*1000:.1f}ms"

    # System type (5) — load, memory, swap
    elif svc_type == "5":
        sys_el = svc.find("system")
        if sys_el is not None:
            load1 = sys_el.findtext("load/avg01", "?")
            load5 = sys_el.findtext("load/avg05", "?")
            load15 = sys_el.findtext("load/avg15", "?")
            mem_pct = sys_el.findtext("memory/percent", "?")
            mem_kb = int(sys_el.findtext("memory/kilobyte", "0"))
            mem_gb = mem_kb / 1024 / 1024
            swap_pct = sys_el.findtext("swap/percent", "?")
            cpu_usr = sys_el.findtext("cpu/user", "?")
            cpu_sys = sys_el.findtext("cpu/system", "?")
            detail = (
                f"\n   load [{load1} {load5} {load15}] | "
                f"RAM {mem_gb:.1f}GB ({mem_pct}%) | swap {swap_pct}% | "
                f"CPU {cpu_usr}%usr {cpu_sys}%sys"
            )

    # Filesystem type (0) — disk usage
    elif svc_type == "0":
        blk = svc.find("block")
        ino = svc.find("inode")
        if blk is not None:
            used_pct = float(blk.findtext("percent", "0"))
            total_mb = float(blk.findtext("total", "0"))
            total_gb = total_mb / 1024
            free_gb = total_gb * (1 - used_pct / 100)
            inode_pct = float(ino.findtext("percent", "0")) if ino is not None else 0
            detail = f"\n   {used_pct:.1f}% used | {free_gb:.0f}GB free / {total_gb:.0f}GB | inodes {inode_pct:.1f}%"

    # Program type (7) — exit status + output
    elif svc_type == "7":
        prog = svc.find("program")
        if prog is not None:
            exit_code = prog.findtext("status", "?")
            output = prog.findtext("output", "").strip()[:80]
            detail = f"\n   exit={exit_code} | {output}" if output else f"\n   exit={exit_code}"

    lines.append(f"{icon} <b>{name}</b>{detail}")

now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")

if all_ok:
    header = "\u2705 <b>MONIT HEALTH REPORT</b> \u2014 ALL OK"
else:
    header = f"\u274c <b>MONIT HEALTH REPORT</b> \u2014 DEGRADED ({', '.join(failed_services)})"

svc_count = len(root.findall("service"))
ok_count = svc_count - len(failed_services)

msg = f"""{header}
<code>{uuid}</code>

<b>opendeviationbar</b> v{pkg_version} | <b>monit</b> up {monit_up_str}
<b>Services:</b> {ok_count}/{svc_count} healthy

{chr(10).join(lines)}

<i>{now}</i>"""

print(msg)
PYEOF
)

if [ -z "$REPORT" ]; then
    REPORT="$(printf '\u274c') <b>MONIT HEARTBEAT</b> (report generation failed)\n<code>$UUID</code>"
fi

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
    "https://api.telegram.org/bot${OPENDEVIATIONBAR_TELEGRAM_TOKEN}/sendMessage" \
    -d chat_id="${OPENDEVIATIONBAR_TELEGRAM_CHAT_ID}" \
    -d parse_mode="HTML" \
    -d disable_notification="true" \
    --data-urlencode "text=${REPORT}")

if [ "$HTTP_CODE" = "200" ]; then
    echo "Heartbeat sent ($UUID)"
    exit 0
else
    echo "Telegram API returned HTTP ${HTTP_CODE}" >&2
    exit 1
fi
