"""Cloudflare tunnel setup handler for Proxmox infrastructure nodes."""

from __future__ import annotations

import asyncio
import json
import logging
import os
import re
import shutil
import subprocess
import sys
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional, Iterator

from .base import SyncHandler
from portacode.tunneling.cloudflared_login import run_login
from portacode.tunneling.cloudflare_setup import ensure_tunnel_with_credentials
from portacode.tunneling.ensure_cloudflared import ensure_cloudflared_installed
from portacode.tunneling.get_domain import get_authenticated_domain
from portacode.tunneling.service_install import TunnelInfo
from portacode.tunneling.state import (
    SYSTEM_CONFIG_PATH,
    clear_state,
    credentials_path_for_tunnel,
    default_cert_path,
    default_config_path,
    default_cloudflared_dir,
    load_state,
    update_state,
)

logger = logging.getLogger(__name__)


def _emit_cloudflare_event(handler: SyncHandler, payload: Dict[str, Any]) -> None:
    loop = handler.context.get("event_loop")
    if not loop or loop.is_closed():
        logger.debug("cloudflare event skipped (no event loop) event=%s", payload.get("event"))
        return
    future = asyncio.run_coroutine_threadsafe(handler.send_response(payload), loop)
    future.add_done_callback(
        lambda fut: logger.warning(
            "Failed to emit cloudflare event %s: %s", payload.get("event"), fut.exception()
        )
        if fut.exception()
        else None
    )


def _ensure_pyyaml() -> None:
    try:
        import yaml  # noqa: F401
    except ModuleNotFoundError as exc:
        logger.info("PyYAML missing; installing via pip")
        try:
            subprocess.run(
                [sys.executable, "-m", "pip", "install", "PyYAML"],
                check=True,
                text=True,
                capture_output=True,
            )
        except subprocess.CalledProcessError as pip_exc:
            msg = pip_exc.stderr or pip_exc.stdout or str(pip_exc)
            raise RuntimeError(f"Failed to install PyYAML: {msg}") from pip_exc
        try:
            import yaml  # noqa: F401
        except ModuleNotFoundError as post_exc:
            raise RuntimeError("PyYAML installation did not resolve the import.") from post_exc


def _is_root() -> bool:
    return hasattr(os, "geteuid") and os.geteuid() == 0


def _safe_remove(path: Path) -> None:
    try:
        if path.exists():
            path.unlink()
    except Exception as exc:  # pragma: no cover - best effort
        logger.debug("Failed to remove %s: %s", path, exc)


def _reset_previous_installation() -> None:
    previous = load_state()
    _safe_remove(default_cert_path())
    _safe_remove(default_cloudflared_dir() / "config.yml")
    if previous.get("tunnel_id") and previous.get("credentials_file"):
        previous_path = Path(previous["credentials_file"])
        if previous_path.exists():
            logger.debug("Preserving existing credentials file %s", previous_path)
        else:
            logger.debug("Credentials file %s missing, will regenerate if needed", previous_path)
    clear_state()


def _build_tunnel_name(device_id: str) -> str:
    normalized = re.sub(r"[^a-zA-Z0-9-]+", "-", device_id.strip()).strip("-")
    normalized = normalized.lower() or "device"
    return f"portacode-proxmox-{normalized}"


@dataclass(frozen=True)
class TunnelSetupResult:
    tunnel_info: TunnelInfo
    credentials_path: Path
    config_path: Path
    cloudflared_version: str


def _run_cloudflare_root_runner(
    *,
    mode: str,
    tunnel_name: Optional[str] = None,
    config_path: Optional[Path] = None,
    origincert_path: Optional[Path] = None,
) -> Dict[str, Any]:
    runner_cmd = [
        sys.executable,
        "-m",
        "portacode.tunneling.cloudflare_root_runner",
        "--mode",
        mode,
    ]
    if mode == "setup":
        if not tunnel_name or not config_path:
            raise ValueError("tunnel_name and config_path are required when mode=setup")
        runner_cmd += [
            "--tunnel-name",
            tunnel_name,
            "--config-path",
            str(config_path),
        ]
    if origincert_path:
        runner_cmd += ["--origincert-path", str(origincert_path)]
    if not _is_root():
        if shutil.which("sudo") is None:
            raise RuntimeError("Root privileges are required to configure cloudflared.")
        runner_cmd = ["sudo", "-n"] + runner_cmd

    result = subprocess.run(runner_cmd, check=False, text=True, capture_output=True)
    if result.returncode != 0:
        message = (result.stderr or result.stdout or "").strip()
        raise RuntimeError(
            "Cloudflare tunnel setup failed while invoking sudo helper."
            f" {message or 'Ensure sudo can be run without a password.'}"
        )
    stdout = result.stdout or ""
    logger.debug("Cloudflare helper output:\n%s", stdout.strip())
    json_line = None
    for line in reversed([line.strip() for line in stdout.splitlines()]):
        if not line:
            continue
        json_line = line
        break

    if not json_line:
        raise RuntimeError("Cloudflare root helper did not return setup information.")

    try:
        return json.loads(json_line)
    except json.JSONDecodeError as exc:
        raise RuntimeError("Cloudflare root helper returned invalid JSON.") from exc


@contextmanager
def _with_origin_cert_env(cert_path: Path) -> Iterator[None]:
    previous = os.environ.get("TUNNEL_ORIGIN_CERT")
    os.environ["TUNNEL_ORIGIN_CERT"] = str(cert_path)
    try:
        yield
    finally:
        if previous is None:
            os.environ.pop("TUNNEL_ORIGIN_CERT", None)
        else:
            os.environ["TUNNEL_ORIGIN_CERT"] = previous


def _ensure_cloudflared_available() -> str:
    if shutil.which("cloudflared"):
        return subprocess.check_output(["cloudflared", "--version"], text=True).strip()
    payload = _run_cloudflare_root_runner(mode="install-only")
    version = payload.get("cloudflared_version")
    if not version:
        raise RuntimeError("Cloudflare install helper did not report a version.")
    return version


def _setup_tunnel(tunnel_name: str, config_path: Path, cert_path: Path) -> TunnelSetupResult:
    if _is_root():
        with _with_origin_cert_env(cert_path):
            version = ensure_cloudflared_installed()
            tunnel_info = ensure_tunnel_with_credentials(tunnel_name, config_path=config_path)
        credentials_path = Path(credentials_path_for_tunnel(tunnel_info.tunnel_id))
        return TunnelSetupResult(
            tunnel_info=tunnel_info,
            credentials_path=credentials_path,
            config_path=config_path,
            cloudflared_version=version,
        )

    payload = _run_cloudflare_root_runner(
        mode="setup",
        tunnel_name=tunnel_name,
        config_path=config_path,
        origincert_path=cert_path,
    )
    tunnel_info = TunnelInfo(
        name=tunnel_name,
        tunnel_id=payload["tunnel_id"],
        existed=bool(payload.get("tunnel_existed")),
    )
    credentials_path = Path(payload["credentials_file"])
    version = payload["cloudflared_version"]
    return TunnelSetupResult(
        tunnel_info=tunnel_info,
        credentials_path=credentials_path,
        config_path=config_path,
        cloudflared_version=version,
    )


class CloudflareTunnelSetupHandler(SyncHandler):
    @property
    def command_name(self) -> str:
        return "setup_cloudflare_tunnel"

    def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
        device_id = str(message.get("device_id") or "").strip()
        if not device_id:
            raise ValueError("device_id is required to configure a tunnel")

        timeout = message.get("timeout")
        timeout_value: Optional[int] = int(timeout) if timeout else 600

        _ensure_pyyaml()
        _reset_previous_installation()
        _ensure_cloudflared_available()

        cert_path = default_cert_path()

        def _handle_url(url: str) -> None:
            _emit_cloudflare_event(
                self,
                {
                    "event": "cloudflare_tunnel_login",
                    "status": "pending",
                    "login_url": url,
                    "message": "Open the login URL to authorize Cloudflare for this domain.",
                },
            )

        login_result = run_login(str(cert_path), timeout_value, on_url=_handle_url)
        if not login_result.cert_detected:
            if login_result.timed_out:
                raise RuntimeError("Cloudflare login timed out; try again.")
            raise RuntimeError("Cloudflare login did not complete.")

        domain = get_authenticated_domain(str(cert_path))
        tunnel_name = _build_tunnel_name(device_id)
        config_target = default_config_path() if _is_root() else SYSTEM_CONFIG_PATH
        setup_result = _setup_tunnel(tunnel_name, config_path=config_target, cert_path=cert_path)

        state = update_state(
            {
                "connected": True,
                "domain": domain,
                "tunnel_name": tunnel_name,
                "tunnel_id": setup_result.tunnel_info.tunnel_id,
                "tunnel_existed": setup_result.tunnel_info.existed,
                "credentials_file": str(setup_result.credentials_path),
                "config_path": str(setup_result.config_path),
                "cert_path": str(cert_path),
                "cloudflared_version": setup_result.cloudflare_version,
                "service_installed": True,
            }
        )

        return {
            "event": "cloudflare_tunnel_configured",
            "success": True,
            "message": f"Cloudflare tunnel ready for {domain}.",
            "cloudflare_tunnel": state,
        }
