"""Daemon lifecycle helpers — shared by CLI and job_crud."""

import os
import signal
import subprocess
import sys
import time
from pathlib import Path

import httpx
import structlog

log = structlog.get_logger()

DEFAULT_PORT = 7890


def read_pid(project_root: Path) -> int | None:
    """Read daemon PID from .fliiq/daemon.pid."""
    pid_path = project_root / ".fliiq" / "daemon.pid"
    if not pid_path.is_file():
        return None
    try:
        return int(pid_path.read_text().strip())
    except (ValueError, OSError):
        return None


def is_process_alive(pid: int) -> bool:
    """Check if a process with given PID is running."""
    try:
        os.kill(pid, 0)
        return True
    except OSError:
        return False


def is_daemon_running(project_root: Path) -> bool:
    """Check if the daemon is running (PID file exists + process alive)."""
    pid = read_pid(project_root)
    if pid is None:
        return False
    return is_process_alive(pid)


def default_port() -> int:
    """Get daemon port from env or default."""
    return int(os.environ.get("FLIIQ_DAEMON_PORT", DEFAULT_PORT))


def wait_for_health(port: int, timeout: float = 5.0) -> bool:
    """Poll GET /health until 200 or timeout."""
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        try:
            resp = httpx.get(f"http://127.0.0.1:{port}/health", timeout=1.0)
            if resp.status_code == 200:
                return True
        except httpx.ConnectError:
            pass
        time.sleep(0.5)
    return False


def ensure_daemon_running(project_root: Path, port: int | None = None) -> bool:
    """Start daemon if not running. Returns True if daemon is now running."""
    if is_daemon_running(project_root):
        return True

    effective_port = port or default_port()
    fliiq_dir = project_root / ".fliiq"

    cmd = [
        sys.executable, "-m", "uvicorn",
        "fliiq.api.server:app",
        "--host", "127.0.0.1",
        "--port", str(effective_port),
        "--log-level", "info",
    ]

    env = os.environ.copy()
    env["FLIIQ_PROJECT_ROOT"] = str(project_root)
    env["FLIIQ_DAEMON_PORT"] = str(effective_port)

    log_path = fliiq_dir / "daemon.log"
    log_file = open(log_path, "a")  # noqa: SIM115

    subprocess.Popen(
        cmd,
        env=env,
        stdout=log_file,
        stderr=log_file,
        start_new_session=True,
    )

    return wait_for_health(effective_port)


def stop_daemon(project_root: Path) -> bool:
    """Stop the daemon. Returns True if it was running and stopped."""
    pid = read_pid(project_root)
    if pid is None:
        return False

    if not is_process_alive(pid):
        # Stale PID file
        pid_path = project_root / ".fliiq" / "daemon.pid"
        pid_path.unlink(missing_ok=True)
        return False

    os.kill(pid, signal.SIGTERM)

    deadline = time.monotonic() + 5.0
    while time.monotonic() < deadline:
        if not is_process_alive(pid):
            break
        time.sleep(0.5)
    else:
        os.kill(pid, signal.SIGKILL)

    pid_path = project_root / ".fliiq" / "daemon.pid"
    pid_path.unlink(missing_ok=True)
    return True
