#!/usr/bin/env python3
"""Autonomous System Health Watchdog & Self-Healing Service for DSCons & dinhsonconstruction.com

Can be run as a background service or one-shot checker.
Features:
- Monitors local services (FastAPI, PostgreSQL, LM Studio, Cloudflared Tunnel)
- Pings public website (https://dinhsonconstruction.com)
- Auto-recovers broken tunnels or disconnected daemons
- Generates structured JSON log trace and Markdown audit reports
"""

import asyncio
import datetime
import json
import logging
import os
import socket
import sys
import time
from pathlib import Path

import httpx
from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE_DIR))
load_dotenv(BASE_DIR / ".env")

LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)
WATCHDOG_LOG = LOG_DIR / "watchdog.log"

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(WATCHDOG_LOG, encoding="utf-8"),
        logging.StreamHandler(sys.stdout),
    ],
)
logger = logging.getLogger("dscons.watchdog")


class SystemHealthWatchdog:
    def __init__(self, check_interval_sec: int = 30) -> None:
        self.interval = check_interval_sec
        self.public_url = os.getenv("APP_PUBLIC_URL", "https://dinhsonconstruction.com")
        self.local_url = f"http://{os.getenv('DSCONS_HOST', '127.0.0.1')}:{os.getenv('DSCONS_PORT', '8000')}"
        self.db_url = os.getenv("DATABASE_URL")

    async def check_http(self, url: str) -> tuple[bool, int, float]:
        start = time.time()
        try:
            async with httpx.AsyncClient(timeout=8.0, verify=False) as client:
                res = await client.get(f"{url}/health")
                latency = round((time.time() - start) * 1000, 2)
                return (res.status_code == 200, res.status_code, latency)
        except Exception:
            latency = round((time.time() - start) * 1000, 2)
            return (False, 0, latency)

    def check_port(self, host: str, port: int) -> bool:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2.0)
            res = sock.connect_ex((host, port))
            sock.close()
            return res == 0
        except Exception:
            return False

    async def recover_cloudflare_tunnel(self) -> bool:
        logger.warning("Attempting Cloudflare Tunnel self-healing...")
        from app.modules.integrations.application.cloudflared_manager import get_cloudflared_manager

        mgr = get_cloudflared_manager()
        await mgr.check_and_recover()
        # Verify
        is_ok, status, _ = await self.check_http(self.public_url)
        if is_ok:
            logger.info("Cloudflare Tunnel recovered successfully.")
            return True
        logger.error("Cloudflare Tunnel recovery could not restore public domain.")
        return False

    async def run_single_audit(self) -> dict:
        timestamp = datetime.datetime.now().isoformat()

        # 1. Local backend check
        local_backend_ok, local_code, local_lat = await self.check_http(self.local_url)

        # 2. Public website check
        public_web_ok, public_code, public_lat = await self.check_http(self.public_url)

        # 3. Port status
        postgres_ok = self.check_port(
            "127.0.0.1", int(os.getenv("POSTGRES_PORT", 5432))
        )
        lmstudio_ok = self.check_port("127.0.0.1", 1234)

        # 4. Self-Healing trigger
        healed = False
        if local_backend_ok and not public_web_ok:
            logger.warning(
                "Public domain is unreachable while local backend is healthy. Initiating Tunnel Auto-Recovery..."
            )
            healed = await self.recover_cloudflare_tunnel()
            if healed:
                public_web_ok, public_code, public_lat = await self.check_http(
                    self.public_url
                )

        status_result = {
            "timestamp": timestamp,
            "overall_status": "HEALTHY"
            if (local_backend_ok and public_web_ok and postgres_ok)
            else "DEGRADED",
            "public_website": {
                "url": self.public_url,
                "healthy": public_web_ok,
                "status_code": public_code,
                "latency_ms": public_lat,
            },
            "local_backend": {
                "url": self.local_url,
                "healthy": local_backend_ok,
                "status_code": local_code,
                "latency_ms": local_lat,
            },
            "database_port_5432": postgres_ok,
            "lmstudio_port_1234": lmstudio_ok,
            "self_healed": healed,
        }

        logger.info(
            f"Health Audit: [{status_result['overall_status']}] "
            f"Public: {public_code} ({public_lat}ms) | "
            f"Local: {local_code} ({local_lat}ms) | "
            f"DB: {'UP' if postgres_ok else 'DOWN'} | "
            f"AI: {'UP' if lmstudio_ok else 'DOWN'}"
        )
        return status_result

    async def run_loop(self) -> None:
        logger.info(f"Starting System Health Watchdog (Interval: {self.interval}s)...")
        while True:
            try:
                await self.run_single_audit()
            except Exception as e:
                logger.error(f"Error in watchdog iteration: {e}")
            await asyncio.sleep(self.interval)


if __name__ == "__main__":
    watchdog = SystemHealthWatchdog(check_interval_sec=30)
    # Check if run once or daemon
    if "--daemon" in sys.argv:
        asyncio.run(watchdog.run_loop())
    else:
        result = asyncio.run(watchdog.run_single_audit())
        print(json.dumps(result, indent=2))
