"""Daemon lifecycle: auto-install, always-on, sandboxed.

On first `import irohds`, the daemon is:
  1. Detected as already running (socket ping), OR
  2. Started via OS service manager, OR
  3. Installed as an OS service + started, OR
  4. Launched directly with bwrap sandbox (fallback)

The daemon persists across Python process exits and starts at boot.
"""

import os
import platform
import shutil
import subprocess
import time
from pathlib import Path
from typing import Optional

from irohds._client import DaemonClient, default_socket_addr

DAEMON_NAME = "irohds-daemon"


def _find_binary() -> Optional[str]:
    import sysconfig

    candidates = [
        str(Path(sysconfig.get_path("scripts")) / DAEMON_NAME),
        shutil.which(DAEMON_NAME),
        str(Path.home() / ".cargo" / "bin" / DAEMON_NAME),
    ]
    if platform.system() == "Windows":
        exe = f"{DAEMON_NAME}.exe"
        candidates.insert(0, str(Path(sysconfig.get_path("scripts")) / exe))
        candidates.append(str(Path.home() / ".cargo" / "bin" / exe))
    for c in candidates:
        if c and os.path.isfile(c) and os.access(c, os.X_OK):
            return c
    return None


def _is_alive() -> bool:
    try:
        c = DaemonClient()
        ok = c.ping()
        c.close()
        return ok
    except Exception:
        return False


def _wait_alive(timeout: float = 10.0) -> bool:
    deadline = time.time() + timeout
    while time.time() < deadline:
        if _is_alive():
            return True
        time.sleep(0.2)
    return False


def _data_dir() -> Path:
    """Must match Rust config.rs data_dir() (dirs::data_local_dir).

    Linux:   $XDG_DATA_HOME/irohds  (~/.local/share/irohds)
    macOS:   ~/Library/Application Support/irohds
    Windows: %LOCALAPPDATA%/irohds  (AppData/Local/irohds)
    """
    override = os.environ.get("IROHDS_DATA_DIR")
    if override:
        return Path(override)
    system = platform.system()
    if system == "Darwin":
        return Path.home() / "Library" / "Application Support" / "irohds"
    if system == "Windows":
        local = os.environ.get("LOCALAPPDATA")
        if local:
            return Path(local) / "irohds"
        return Path.home() / "AppData" / "Local" / "irohds"
    xdg = os.environ.get("XDG_DATA_HOME", str(Path.home() / ".local" / "share"))
    return Path(xdg) / "irohds"


def _runtime_dir() -> Path:
    """Must match Rust config.rs runtime_dir()."""
    if platform.system() == "Windows":
        # Rust falls back to dirs::cache_dir() on Windows
        local = os.environ.get("LOCALAPPDATA")
        if local:
            return Path(local)
        return Path.home() / "AppData" / "Local"
    xdg = os.environ.get("XDG_RUNTIME_DIR")
    if xdg:
        return Path(xdg)
    return Path("/tmp")


# -- Service manager ---------------------------------------------------------

def _try_start_service() -> bool:
    system = platform.system()
    try:
        if system == "Linux":
            return subprocess.run(
                ["systemctl", "--user", "start", "irohds"],
                capture_output=True, timeout=5).returncode == 0
        elif system == "Darwin":
            plist = Path.home() / "Library/LaunchAgents/computer.iroh.irohds.plist"
            if plist.exists():
                return subprocess.run(
                    ["launchctl", "start", "computer.iroh.irohds"],
                    capture_output=True, timeout=5).returncode == 0
        elif system == "Windows":
            return subprocess.run(
                ["schtasks", "/run", "/tn", "irohds"],
                capture_output=True, timeout=5).returncode == 0
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    return False


def _try_install_service(binary: str) -> bool:
    try:
        return subprocess.run(
            [binary, "install-service"],
            capture_output=True, timeout=15).returncode == 0
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False


# -- Fallback: direct launch -------------------------------------------------

def _bwrap_cmd(binary: str) -> list:
    data = _data_dir()
    data.mkdir(parents=True, exist_ok=True)
    runtime = str(_runtime_dir())
    return [
        "bwrap",
        "--ro-bind", "/", "/",
        "--bind", str(data), str(data),
        "--bind", runtime, runtime,
        "--proc", "/proc",
        "--dev", "/dev",
        "--unshare-ipc", "--unshare-uts",
        "--new-session",
        "--", binary,
    ]


def _find_sandbox_profile() -> Optional[Path]:
    """Find sandbox.sb relative to package install location."""
    pkg = Path(__file__).parent
    for candidate in [
        pkg / "sandbox.sb",                        # installed as package_data
        pkg.parent / "services" / "sandbox.sb",    # dev: python/irohds/../services
        pkg.parent.parent / "services" / "sandbox.sb",  # dev: repo root
    ]:
        if candidate.exists():
            return candidate
    return None


def _start_direct(binary: str) -> None:
    addr = default_socket_addr()
    if platform.system() != "Windows" and os.path.exists(addr):
        os.unlink(addr)

    system = platform.system()
    if system == "Linux" and shutil.which("bwrap"):
        cmd = _bwrap_cmd(binary)
    elif system == "Darwin":
        sb = _find_sandbox_profile()
        if sb:
            cmd = [
                "sandbox-exec", "-f", str(sb),
                "-D", f"DATA_DIR={_data_dir()}",
                "-D", f"RUNTIME_DIR={_runtime_dir()}",
                "-D", f"DAEMON_BIN={binary}",
                binary,
            ]
        else:
            cmd = [binary]
    else:
        cmd = [binary]

    log_path = _data_dir() / "daemon.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    log_file = open(log_path, "a")

    kwargs = {"stdout": log_file, "stderr": log_file}
    if system == "Windows":
        kwargs["creationflags"] = (
            subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)
    else:
        kwargs["start_new_session"] = True

    subprocess.Popen(cmd, **kwargs)
    log_file.close()


# -- Public API --------------------------------------------------------------

def ensure_daemon() -> str:
    """Ensure the daemon is running. Returns socket address.

    Called automatically on first @irohds.memo call.
    """
    addr = default_socket_addr()

    if _is_alive():
        return addr

    if _try_start_service() and _wait_alive(10.0):
        return addr

    binary = _find_binary()
    if binary is None:
        raise RuntimeError(f"{DAEMON_NAME} not found. Install: uv add irohds")

    if _try_install_service(binary) and _wait_alive(10.0):
        return addr

    _start_direct(binary)
    if _wait_alive(10.0):
        return addr

    log = _data_dir() / "daemon.log"
    raise RuntimeError(f"daemon failed to start (log: {log})")


# -- Service management (replaces _service.py) -------------------------------

def install_service():
    """Install, enable, and start the irohds service."""
    binary = _find_binary()
    if binary is None:
        raise RuntimeError(f"{DAEMON_NAME} not found. Install: uv add irohds")
    subprocess.check_call([binary, "install-service"])


def uninstall_service():
    """Stop, disable, and remove the irohds service."""
    binary = _find_binary()
    if binary is None:
        raise RuntimeError(f"{DAEMON_NAME} not found. Install: uv add irohds")
    subprocess.check_call([binary, "uninstall-service"])


def service_status() -> dict:
    """Return service status."""
    return {"daemon_running": _is_alive(), "binary": _find_binary()}
