"""
Node Health Reporter
=====================
Pro nodes report their health status to BizNode admin via:
  1. VPS relay endpoint (pull model, same as webhook_sync)
  2. Telegram group message (periodic heartbeat)
  3. Email digest (daily summary)

Admin node polls the VPS to aggregate all node health data.
If a Pro node goes silent (no heartbeat for >15 min), admin alerts.
"""

from __future__ import annotations

import json
import logging
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

import requests

logger = logging.getLogger("watchdog.reporter")

_PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(_PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(_PROJECT_ROOT))

try:
    from dotenv import load_dotenv
    load_dotenv(_PROJECT_ROOT / ".env")
except ImportError:
    pass

# Config
VPS_BASE_URL = os.getenv("VPS_BASE_URL", "https://biznode.1bz.biz")
VPS_HEALTH_ENDPOINT = f"{VPS_BASE_URL}/api_node_health.php"
VPS_UPDATES_ENDPOINT = f"{VPS_BASE_URL}/api_node_updates.php"
ADMIN_KEY = os.getenv("ADMIN_KEY", "biznode-admin-2026")
BIZNODE_GROUP_ID = os.getenv("BIZNODE_GROUP_ID", "-1003710628018")
BOT_TOKEN = os.getenv("BOT_TOKEN", "") or os.getenv("TELEGRAM_BOT_TOKEN", "")
NODE_NAME = os.getenv("BOT_NAME", "BizNode")
NODE_TIER = os.getenv("NODE_TIER", "pro")


def _get_node_id() -> str:
    """Get this node's unique ID."""
    try:
        from services.mcp_service import get_node_id
        return get_node_id()
    except ImportError:
        # Fallback: use hash of bot token
        import hashlib
        token = BOT_TOKEN or "unknown"
        return hashlib.sha256(token.encode()).hexdigest()[:12]


# ---------------------------------------------------------------------------
# Report to VPS (pull model)
# ---------------------------------------------------------------------------

def report_to_vps(health_data: dict) -> bool:
    """
    POST health snapshot to VPS relay endpoint.
    Admin node will poll this to see all node statuses.
    """
    try:
        payload = {
            "node_id": _get_node_id(),
            "node_name": NODE_NAME,
            "node_tier": NODE_TIER,
            "health": health_data,
            "reported_at": datetime.now(timezone.utc).isoformat(),
        }

        r = requests.post(
            VPS_HEALTH_ENDPOINT,
            json=payload,
            headers={"X-Admin-Key": ADMIN_KEY},
            timeout=15,
        )

        if r.status_code == 200:
            logger.info("Health reported to VPS")
            return True

        logger.warning("VPS health report failed: %s %s", r.status_code, r.text[:200])
        return False

    except Exception as exc:
        logger.error("VPS health report error: %s", exc)
        return False


# ---------------------------------------------------------------------------
# Heartbeat to Telegram group
# ---------------------------------------------------------------------------

def send_heartbeat_to_group(health_data: dict) -> bool:
    """
    Send a periodic heartbeat message to the BizNode Telegram group.
    This lets the admin bot detect silent nodes.
    """
    if not BOT_TOKEN or not BIZNODE_GROUP_ID:
        return False

    services = health_data.get("services", {})
    statuses = []
    for name, info in services.items():
        icon = "🟢" if info.get("status") == "ok" else "🔴"
        statuses.append(f"{icon} {name}")

    all_ok = health_data.get("all_healthy", False)
    overall = "✅ All services healthy" if all_ok else "⚠️ Issues detected"

    msg = (
        f"💓 <b>Node Heartbeat</b>\n"
        f"<b>{NODE_NAME}</b> ({NODE_TIER})\n"
        f"ID: <code>{_get_node_id()[:8]}...</code>\n\n"
        f"{chr(10).join(statuses)}\n\n"
        f"{overall}\n"
        f"<i>{datetime.now(timezone.utc).strftime('%H:%M UTC')}</i>"
    )

    try:
        r = requests.post(
            f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
            json={
                "chat_id": BIZNODE_GROUP_ID,
                "text": msg,
                "parse_mode": "HTML",
            },
            timeout=10,
        )
        return r.status_code == 200 and r.json().get("ok", False)
    except Exception as exc:
        logger.error("Heartbeat send failed: %s", exc)
        return False


# ---------------------------------------------------------------------------
# Check for pending admin instructions/updates
# ---------------------------------------------------------------------------

def check_for_updates() -> list[dict]:
    """
    Poll VPS for pending update packets or instructions for this node.
    Same pull model as webhook_sync_service.
    """
    try:
        r = requests.get(
            VPS_UPDATES_ENDPOINT,
            params={"node_id": _get_node_id()},
            headers={"X-Admin-Key": ADMIN_KEY},
            timeout=15,
        )

        if r.status_code == 200:
            data = r.json()
            updates = data.get("updates", [])
            if updates:
                logger.info("Received %d pending updates from admin", len(updates))
            return updates

        return []

    except Exception as exc:
        logger.error("Update check failed: %s", exc)
        return []


def ack_update(update_id: str) -> bool:
    """Acknowledge that an update has been applied."""
    try:
        r = requests.post(
            VPS_UPDATES_ENDPOINT,
            json={"action": "ack", "update_id": update_id, "node_id": _get_node_id()},
            headers={"X-Admin-Key": ADMIN_KEY},
            timeout=10,
        )
        return r.status_code == 200
    except Exception:
        return False


# ---------------------------------------------------------------------------
# Admin-side: poll all node health from VPS
# ---------------------------------------------------------------------------

def poll_all_node_health() -> list[dict]:
    """
    (Admin node only) Poll VPS for health reports from all registered nodes.
    """
    try:
        r = requests.get(
            VPS_HEALTH_ENDPOINT,
            params={"action": "list"},
            headers={"X-Admin-Key": ADMIN_KEY},
            timeout=15,
        )

        if r.status_code == 200:
            return r.json().get("nodes", [])
        return []

    except Exception as exc:
        logger.error("Failed to poll node health: %s", exc)
        return []


def get_silent_nodes(max_silence_minutes: int = 15) -> list[dict]:
    """
    (Admin node only) Find nodes that haven't reported in too long.
    """
    nodes = poll_all_node_health()
    silent = []

    for node in nodes:
        reported_at = node.get("reported_at", "")
        if not reported_at:
            silent.append(node)
            continue

        try:
            dt = datetime.fromisoformat(reported_at.replace("Z", "+00:00"))
            age_minutes = (datetime.now(timezone.utc) - dt).total_seconds() / 60
            if age_minutes > max_silence_minutes:
                node["silent_minutes"] = round(age_minutes)
                silent.append(node)
        except (ValueError, TypeError):
            silent.append(node)

    return silent
