"""Service to automatically monitor, start, and keep alive Cloudflare Tunnel."""

from __future__ import annotations

import asyncio
import logging
import os
import shutil
import subprocess
from pathlib import Path

import httpx

from app.core.settings import get_settings

logger = logging.getLogger("dscons.cloudflared_manager")


class CloudflaredManager:
    """Manages the lifecycle of the local Cloudflare Tunnel daemon."""

    def __init__(self) -> None:
        self.settings = get_settings()
        self._process: asyncio.subprocess.Process | None = None
        self.config_path = self._resolve_config_path()

    def _resolve_config_path(self) -> Path:
        user_profile = os.environ.get("USERPROFILE") or os.path.expanduser("~")
        return Path(user_profile) / ".cloudflared" / "config.yml"

    def _find_binary(self) -> str | None:
        bin_path = shutil.which("cloudflared")
        if bin_path:
            return bin_path

        common_paths = [
            Path("C:/Program Files (x86)/cloudflared/cloudflared.exe"),
            Path("C:/Program Files/cloudflared/cloudflared.exe"),
            Path(os.environ.get("USERPROFILE", "")) / "bin" / "cloudflared.exe",
        ]
        for p in common_paths:
            if p.exists():
                return str(p)
        return None

    def _is_system_process_running(self) -> bool:
        """Checks whether cloudflared is already running as a system service or background process."""
        try:
            res = subprocess.run(
                ["tasklist", "/FI", "IMAGENAME eq cloudflared.exe"],
                capture_output=True,
                text=True,
                timeout=2,
            )
            return "cloudflared.exe" in res.stdout
        except Exception:
            return False

    async def is_tunnel_healthy(self) -> bool:
        """Verifies if the local tunnel daemon and edge connection are active."""
        # 1. Check local cloudflared metrics / ready endpoint (instant, zero hairpin SSL issues)
        for port in (20241, 20242, 20243):
            try:
                async with httpx.AsyncClient(timeout=1.0) as client:
                    res = await client.get(f"http://127.0.0.1:{port}/ready")
                    if res.status_code == 200:
                        return True
            except Exception:
                continue

        # 2. Check public endpoints as fallback
        test_urls = [
            "https://dinhsonconstruction.com/api/health",
            "https://erp.dinhsonconstruction.com/api/health",
            "https://dinhsonconstruction.com/health",
            "https://erp.dinhsonconstruction.com/health",
        ]
        for url in test_urls:
            try:
                async with httpx.AsyncClient(timeout=3.0, verify=False) as client:
                    res = await client.get(url)
                    if res.status_code == 200:
                        return True
            except Exception:
                continue

        return False

    async def start_tunnel(self) -> bool:
        """Starts the cloudflared tunnel process."""
        binary = self._find_binary()
        if not binary:
            logger.error("cloudflared binary not found on system PATH.")
            return False

        if not self.config_path.exists():
            logger.error("cloudflared config file not found at %s", self.config_path)
            return False

        logger.info("Starting Cloudflare Tunnel using config %s...", self.config_path)
        try:
            import sys

            creationflags = 0
            if sys.platform == "win32":
                DETACHED_PROCESS = 0x00000008
                CREATE_NEW_PROCESS_GROUP = 0x00000200
                creationflags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
            self._process = subprocess.Popen(
                [
                    binary,
                    "tunnel",
                    "--config",
                    str(self.config_path),
                    "run",
                ],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                stdin=subprocess.DEVNULL,
                creationflags=creationflags,
                close_fds=True if sys.platform != "win32" else False,
            )
            logger.info(
                "Cloudflare Tunnel process started with PID %s.", self._process.pid
            )
            return True
        except Exception as e:
            logger.error("Error starting Cloudflare Tunnel process: %s", e)
            return False

    async def check_and_recover(self) -> None:
        """Checks if tunnel is healthy, and restarts it if not."""
        if not getattr(self.settings, "cloudflared_enabled", True):
            return

        is_healthy = await self.is_tunnel_healthy()
        if not is_healthy:
            # Only attempt restart if no process is currently running
            if not self._is_system_process_running():
                logger.warning(
                    "Cloudflare Tunnel is not running. Initiating auto-start..."
                )
                started = await self.start_tunnel()
                if started:
                    # Wait up to 10 seconds for edge connection to register
                    for _ in range(10):
                        await asyncio.sleep(1)
                        if await self.is_tunnel_healthy():
                            logger.info(
                                "Cloudflare Tunnel successfully recovered and online."
                            )
                            return
                    logger.warning(
                        "Cloudflare Tunnel process spawned, waiting for edge sync..."
                    )
                else:
                    logger.error("Failed to start Cloudflare Tunnel.")
            else:
                logger.debug(
                    "Cloudflare Tunnel process is running, awaiting edge connection."
                )
        else:
            logger.debug("Cloudflare Tunnel is online and healthy.")

    async def stop(self) -> None:
        """Stops the managed tunnel process gracefully."""
        if self._process:
            is_running = False
            if hasattr(self._process, "poll"):
                is_running = self._process.poll() is None
            elif getattr(self._process, "returncode", None) is None:
                is_running = True

            if is_running:
                logger.info(
                    "Stopping Cloudflare Tunnel (PID %s)...",
                    getattr(self._process, "pid", "N/A"),
                )
                try:
                    self._process.terminate()
                    if hasattr(self._process, "wait"):
                        self._process.wait(timeout=3.0)
                except Exception:
                    try:
                        self._process.kill()
                    except Exception:
                        pass
                self._process = None


_global_cf_manager: CloudflaredManager | None = None


def get_cloudflared_manager() -> CloudflaredManager:
    """Returns the singleton CloudflaredManager instance."""
    global _global_cf_manager
    if _global_cf_manager is None:
        _global_cf_manager = CloudflaredManager()
    return _global_cf_manager


async def start_cloudflared_monitor_loop() -> None:
    """Background monitor loop that runs indefinitely to keep Cloudflare Tunnel alive."""
    manager = get_cloudflared_manager()
    while True:
        try:
            await manager.check_and_recover()
        except Exception as e:
            logger.error("Error in Cloudflare Tunnel monitor loop: %s", e)
        await asyncio.sleep(20)
