#!/usr/bin/env python3
"""
🌸 Nami Health Alerts — Telegram notifications for service status changes

Monitors all services via API Gateway, tracks state changes, and sends
Telegram alerts when services go down or recover.

Run:   python3 /opt/nami-army/health_alerts.py
Cron:  */5 * * * * /root/.hermes/hermes-agent/venv/bin/python3 /opt/nami-army/health_alerts.py
"""

import json
import os
import sys
import time
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

# ─── Configuration ───────────────────────────────────────────────────
API_URL = "http://localhost:8700/api/status"
STATE_FILE = Path("/opt/nami-army/health_state.json")
ALERT_LOG = Path("/opt/backup/logs/health_alerts.log")
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "7724670451")
DAILY_SUMMARY_HOUR = 8  # Send daily summary at 8 AM UTC

# ─── Emoji & styling ─────────────────────────────────────────────────
EMOJI = {
    "hermes-gateway": "🧠", "nami-bridge": "🌉", "nami-status-api": "📡",
    "laopatana-stat-lab": "📊", "hanoi-stats-analyzer": "🎲", "clipboardbypao": "📋",
    "gold-signal-os": "🥇", "miroshark-backend": "🦈", "miroshark-frontend": "🦈",
    "miroshark-oracle": "🔮", "nginx": "🌐", "postgresql": "🗄️"
}

# ─── Telegram ─────────────────────────────────────────────────────────
def telegram_send(text: str) -> bool:
    """Send message to Telegram. Returns True if successful."""
    if not BOT_TOKEN:
        # Try to load from hermes .env
        env_file = Path("/root/.hermes/.env")
        token = ""
        if env_file.exists():
            for line in env_file.read_text().splitlines():
                line = line.strip()
                if line.startswith("TELEGRAM_BOT_TOKEN=") and not line.startswith("#"):
                    token = line.split("=", 1)[1].strip().strip('"').strip("'")
                    break
        if not token:
            print("[WARN] No TELEGRAM_BOT_TOKEN found", file=sys.stderr)
            return False
    else:
        token = BOT_TOKEN

    # Truncate long messages (Telegram limit ~4096 chars)
    text = text[:4000]
    
    try:
        url = f"https://api.telegram.org/bot{token}/sendMessage"
        data = json.dumps({
            "chat_id": CHAT_ID,
            "text": text,
            "parse_mode": "HTML",
            "disable_web_page_preview": True
        }).encode()
        req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
        with urllib.request.urlopen(req, timeout=10) as resp:
            result = json.loads(resp.read())
            return result.get("ok", False)
    except Exception as e:
        print(f"[ERROR] Telegram send failed: {e}", file=sys.stderr)
        return False


# ─── State Management ─────────────────────────────────────────────────
def load_state():
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {"last_status": {}, "last_summary": "", "alerted_down": {}, "downtime_start": {}}


def save_state(state):
    STATE_FILE.write_text(json.dumps(state, indent=2))


# ─── Fetch Status ─────────────────────────────────────────────────────
def fetch_status():
    try:
        req = urllib.request.Request(API_URL)
        with urllib.request.urlopen(req, timeout=10) as resp:
            return json.loads(resp.read())
    except Exception as e:
        print(f"[ERROR] API Gateway unreachable: {e}", file=sys.stderr)
        return None


# ─── Alert Formatting ─────────────────────────────────────────────────
def format_service_name(key, name):
    emoji = EMOJI.get(key, "📦")
    return f"{emoji} <b>{name}</b>"


def format_down_alert(down_services, data, downtime_info):
    """Format a DOWN alert message"""
    now = datetime.now(timezone.utc)
    lines = [
        "⚠️ <b>Nami Health Alert — Services DOWN</b>",
        f"🕐 <i>{now.strftime('%H:%M UTC · %d %b %Y')}</i>",
        ""
    ]
    
    for key, name in down_services:
        emoji = EMOJI.get(key, "📦")
        lines.append(f"❌ {emoji} <b>{name}</b>")
        if key in downtime_info:
            dt = downtime_info[key]
            lines.append(f"   ⏱ <i>Started: {dt}</i>")
    
    lines.append("")
    lines.append(f"📊 <b>{data['up']}/{data['total']}</b> services UP")
    lines.append(f'🔗 <a href="http://dashboard.178.104.181.132.nip.io">→ Open Dashboard</a>')
    
    return "\n".join(lines)


def format_recovery_alert(recovered, data):
    """Format a RECOVERY alert message"""
    now = datetime.now(timezone.utc)
    lines = [
        "✅ <b>Nami Health — Services RECOVERED</b>",
        f"🕐 <i>{now.strftime('%H:%M UTC · %d %b %Y')}</i>",
        ""
    ]
    
    for key, name in recovered:
        emoji = EMOJI.get(key, "📦")
        lines.append(f"✅ {emoji} <b>{name}</b> — Back online!")
    
    lines.append("")
    total = data.get('total', 12)
    up = data.get('up', total)
    down = total - up
    if down == 0:
        lines.append(f"🟢 <b>All {total}/{total} services UP!</b> ✨")
    else:
        lines.append(f"📊 <b>{up}/{total}</b> services UP ({down} still down)")
    lines.append(f'🔗 <a href="http://dashboard.178.104.181.132.nip.io">→ Open Dashboard</a>')
    
    return "\n".join(lines)


def format_daily_summary(data):
    """Format daily summary message"""
    now = datetime.now(timezone.utc)
    services = data.get("services", {})
    
    lines = [
        f"🌸 <b>Nami Daily Report</b>",
        f"📅 <i>{now.strftime('%d %b %Y')}</i>",
        f"",
        f"🏥 <b>Services: {data['up']}/{data['total']} UP</b>",
        f""
    ]
    
    for key, svc in services.items():
        emoji = EMOJI.get(key, "📦")
        status_icon = "✅" if svc["status"] == "up" else "❌"
        lines.append(f"{status_icon} {emoji} {svc['name']}")
    
    lines.extend([
        "",
        f"💻 CPU: {data.get('cpu','—')}%  🧠 RAM: {data.get('ram','—')}%  💾 Disk: {data.get('disk','—')}%",
        f"⏱ Uptime: {data.get('uptime','—')}",
        f'🔗 <a href="http://dashboard.178.104.181.132.nip.io">→ Open Dashboard</a>'
    ])
    
    return "\n".join(lines)


# ─── Auto-Restart ─────────────────────────────────────────────────────
def try_restart(service_key):
    """Try to restart a service via systemctl. Returns True if restarted."""
    import subprocess
    unit_map = {
        "laopatana-stat-lab": "laopatana-stat-lab",
        "hanoi-stats-analyzer": "hanoi-stats-analyzer",
        "clipboardbypao": "clipboardbypao",
        "gold-signal-os": "gold-signal-os",
        "miroshark-backend": "miroshark-backend",
        "miroshark-frontend": "miroshark-frontend",
        "miroshark-oracle": "miroshark-oracle",
    }
    unit = unit_map.get(service_key)
    if not unit:
        return False
    try:
        r = subprocess.run(["systemctl", "restart", unit], capture_output=True, text=True, timeout=30)
        return r.returncode == 0
    except:
        return False


# ─── Main Logic ───────────────────────────────────────────────────────
def main():
    state = load_state()
    data = fetch_status()
    
    if not data:
        print("[WARN] Could not fetch status, skipping this run")
        return
    
    services = data.get("services", {})
    current_status = {}
    down_now = []
    
    for key, svc in services.items():
        current_status[key] = svc["status"]
        if svc["status"] == "down":
            down_now.append((key, svc["name"]))
    
    prev = state.get("last_status", {})
    alerted = state.get("alerted_down", {})
    downtime = state.get("downtime_start", {})
    now_utc = datetime.now(timezone.utc).strftime("%H:%M UTC · %d %b")
    
    # ── Detect DOWN events ──
    new_downs = []
    for key, name in down_now:
        if key not in alerted:
            new_downs.append((key, name))
            alerted[key] = True
            downtime[key] = now_utc
            # Try auto-restart non-critical services
            result = try_restart(key)
            if result:
                print(f"[AUTO-RESTART] {key} — restart triggered")
    
    # ── Detect RECOVERY events ──
    recovered = []
    for key in list(alerted.keys()):
        if current_status.get(key) == "up":
            recovered.append((key, services[key]["name"]))
            del alerted[key]
            downtime.pop(key, None)
    
    # ── Send alerts ──
    if new_downs:
        msg = format_down_alert(new_downs, data, {k: downtime.get(k, "?") for k, _ in new_downs})
        ok = telegram_send(msg)
        print(f"[ALERT] DOWN — {len(new_downs)} service(s) — {'sent' if ok else 'FAILED'}")
        if not ok:
            print(f"  Message: {msg[:200]}")
    
    if recovered:
        msg = format_recovery_alert(recovered, data)
        ok = telegram_send(msg)
        print(f"[ALERT] RECOVERED — {len(recovered)} service(s) — {'sent' if ok else 'FAILED'}")
    
    # ── Daily summary ──
    now = datetime.now(timezone.utc)
    today_str = now.strftime("%Y-%m-%d")
    last_summary = state.get("last_summary", "")
    
    if today_str != last_summary and now.hour >= DAILY_SUMMARY_HOUR:
        msg = format_daily_summary(data)
        ok = telegram_send(msg)
        if ok:
            state["last_summary"] = today_str
            print(f"[SUMMARY] Daily report sent for {today_str}")
    
    # ── Save state ──
    state["last_status"] = current_status
    state["alerted_down"] = alerted
    state["downtime_start"] = downtime
    save_state(state)


if __name__ == "__main__":
    main()
