"""
Windows Service — xxm Bot Health Monitor
=========================================
Replaces Task Scheduler-based health_check_xxm.py.

Design:
  - HTTP health check (GET :5802/health), NO subprocess polling
  - Zero console windows (WMIC/tasklist-free during normal operation)
  - Manageable via services.msc or `net start/stop xxm-health`

Belongs to: projects/AgentsMeeting/gateway/scripts/

Usage:
  python health_service.py install      # register service (run as Admin)
  python health_service.py start        # start (or net start xxm-health)
  python health_service.py stop         # stop  (or net stop  xxm-health)
  python health_service.py remove       # unregister

  After install: manage via services.msc → "AgentsMeeting xxm Health Service"
"""
import os, sys, time, json, urllib.request, logging

import win32serviceutil
import win32service
import win32event
import servicemanager

# ── Paths (absolute, service-safe) ──────────────────────────────────
_PROJECT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
_LOG_DIR = os.path.join(_PROJECT, "gateway", "logs")
_LOG_FILE = os.path.join(_LOG_DIR, "health_service.log")
_PYTHON = r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\python.exe"
_BOT_SCRIPT = os.path.join(_PROJECT, "xmpp_agent_core.py")

# Bridge API key — needed because /health endpoint requires auth
_BRIDGE_API_KEY = "xxm_bridge_8f3a2c"

_CHECK_INTERVAL = 30        # seconds between health checks
_HEALTH_TIMEOUT = 10         # HTTP request timeout
_RESTART_WAIT = 3            # seconds to wait after kill before start
_STARTUP_WAIT = 5            # seconds to wait after start before first check


class HealthService(win32serviceutil.ServiceFramework):
    """Windows Service: polls :5802/health, restarts bot on failure."""

    _svc_name_ = "xxm-health"
    _svc_display_name_ = "AgentsMeeting xxm Health Service"
    _svc_description_ = "Monitors xxm bot via HTTP :5802/health, auto-restarts on failure. No console windows."

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        self._bot_pid: int | None = None
        self._setup_logging()

    # ── Logging ─────────────────────────────────────────────────────

    def _setup_logging(self):
        os.makedirs(_LOG_DIR, exist_ok=True)
        logging.basicConfig(
            filename=_LOG_FILE, level=logging.INFO,
            format="%(asctime)s [SVC] %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )

    def _log(self, msg: str):
        logging.info(msg)
        # Also write to Windows Event Log
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_, f"[SVC] {msg}"),
        )

    # ── Service lifecycle ───────────────────────────────────────────

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)
        self._log("Service stopping")

    def SvcDoRun(self):
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_, ""),
        )
        self._log(f"Service started (check every {_CHECK_INTERVAL}s)")

        # Acquire singleton lock via proc_guard
        sys.path.insert(0, os.path.join(_PROJECT, "gateway", "scripts"))
        try:
            from proc_guard import guard
            lock = guard("health_service_xxm")
            if not lock.ok:
                self._log(f"Another instance running: {lock.message} — exiting")
                return
        except Exception as e:
            self._log(f"proc_guard init skipped: {e}")

        # Ensure bot is healthy on startup
        if not self._health_check():
            self._log("Bot not responding on startup — restarting")
            self._restart_bot()

        # Main loop: wait(interval) → health check → loop
        while True:
            rc = win32event.WaitForSingleObject(self.hWaitStop, _CHECK_INTERVAL * 1000)
            if rc == win32event.WAIT_OBJECT_0:
                break  # Stop signal

            if not self._health_check():
                self._log("Health check FAILED — restarting bot")
                self._restart_bot()

        self._log("Service stopped")

    # ── Health check (HTTP only, zero subprocess) ───────────────────

    def _health_check(self) -> bool:
        """GET :5807/health. Returns True if XMPP connected.

        Port is 5807 (not 5802) — 5802 is occupied by the wechat-hermes-gateway
        xmpp_bot.py (independent service). xxm bot moved to 5807 to avoid
        dual-listener conflict; this health service must follow.
        """
        try:
            req = urllib.request.Request(
                f"http://127.0.0.1:5807/health?key={_BRIDGE_API_KEY}",
                headers={"User-Agent": "health-service/1.0"},
            )
            resp = json.loads(
                urllib.request.urlopen(req, timeout=_HEALTH_TIMEOUT).read()
            )
            ok = resp.get("xmpp_connected", False)
            if not ok:
                self._log(f"/health returned xmpp_connected=false: {resp}")
            return ok
        except Exception as e:
            self._log(f"/health unreachable: {e}")
            return False

    # ── Bot restart (kill by stored PID → Popen with CREATE_NO_WINDOW) ──

    def _restart_bot(self):
        import subprocess

        # 1. Kill old bot by stored PID (no process enumeration needed)
        if self._bot_pid:
            try:
                subprocess.run(
                    ["taskkill", "/f", "/pid", str(self._bot_pid)],
                    capture_output=True, timeout=10,
                    creationflags=subprocess.CREATE_NO_WINDOW,
                )
                self._log(f"Killed old bot (PID {self._bot_pid})")
            except Exception as e:
                self._log(f"Kill PID {self._bot_pid} failed: {e}")
            self._bot_pid = None

        time.sleep(_RESTART_WAIT)

        # 2. Start new bot
        try:
            p = subprocess.Popen(
                [_PYTHON, _BOT_SCRIPT, "--agent", "xxm"],
                cwd=os.path.dirname(_BOT_SCRIPT),
                creationflags=subprocess.CREATE_NO_WINDOW,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            self._bot_pid = p.pid
            self._log(f"Bot started (PID {p.pid})")

            # 3. Wait a moment then verify
            time.sleep(_STARTUP_WAIT)
            if self._health_check():
                self._log("Bot healthy after restart")
            else:
                self._log("Bot started but /health not yet responding")
        except Exception as e:
            self._log(f"Bot start FAILED: {e}")


if __name__ == "__main__":
    win32serviceutil.HandleCommandLine(HealthService)
