"""DNS forwarder (dnsmasq) - lifecycle management for WireGuard clients."""

from __future__ import annotations

import os
import signal
import subprocess
from pathlib import Path

from .config import DNS_DIR
from .exceptions import BeaconSystemError


def _pid_file(beacon_id: str) -> Path:
    return DNS_DIR / f"{beacon_id}.pid"


def get_system_dns() -> list[str]:
    """Read nameservers from /etc/resolv.conf."""
    servers = []
    try:
        with open("/etc/resolv.conf") as f:
            for line in f:
                line = line.strip()
                if line.startswith("nameserver"):
                    parts = line.split()
                    if len(parts) >= 2:
                        servers.append(parts[1])
    except OSError:
        pass
    return servers


def is_installed() -> bool:
    """Return True if dnsmasq is available on PATH."""
    try:
        subprocess.check_output(["which", "dnsmasq"], stderr=subprocess.DEVNULL)
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False


def start(beacon_id: str, listen_addr: str, upstream: list[str] | None = None) -> None:
    """Start a dnsmasq forwarder listening on listen_addr:53.

    *upstream* should be pre-captured from /etc/resolv.conf **before** wg-quick
    modifies it (wg-quick injects the WireGuard gateway as the system nameserver,
    which would cause dnsmasq to forward queries to itself).  When not provided,
    the current /etc/resolv.conf is read as a fallback.
    dnsmasq daemonizes itself and writes its PID to the state directory.

    Raises BeaconSystemError if dnsmasq is not installed or fails to start.
    """
    if is_running(beacon_id):
        return  # already running - nothing to do

    DNS_DIR.mkdir(parents=True, exist_ok=True)
    pid_file = _pid_file(beacon_id)
    log_file = DNS_DIR / f"{beacon_id}.log"

    if upstream is None:
        upstream = get_system_dns()
    if not upstream:
        raise BeaconSystemError(
            "No nameservers found in /etc/resolv.conf. Cannot start DNS forwarder."
        )

    cmd = [
        "dnsmasq",
        f"--listen-address={listen_addr}",
        "--bind-interfaces",
        "--port=53",
        "--no-resolv",
        f"--pid-file={pid_file}",
        f"--log-facility={log_file}",
    ]
    for server in upstream:
        cmd.append(f"--server={server}")

    try:
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            raise BeaconSystemError(
                f"dnsmasq failed to start: {result.stderr.strip() or result.stdout.strip()}"
            )
    except FileNotFoundError:
        raise BeaconSystemError(
            "dnsmasq not found. Install it: apt-get install dnsmasq-base"
        )


def stop(beacon_id: str) -> bool:
    """Stop the dnsmasq process for the given beacon. Returns True if stopped."""
    pid_file = _pid_file(beacon_id)
    if not pid_file.exists():
        return False
    try:
        pid = int(pid_file.read_text().strip())
        os.kill(pid, signal.SIGTERM)
        pid_file.unlink(missing_ok=True)
        return True
    except (ValueError, ProcessLookupError, OSError):
        pid_file.unlink(missing_ok=True)
        return False


def is_running(beacon_id: str) -> bool:
    """Return True if the dnsmasq process for this beacon is alive."""
    pid_file = _pid_file(beacon_id)
    if not pid_file.exists():
        return False
    try:
        pid = int(pid_file.read_text().strip())
        os.kill(pid, 0)
        return True
    except (ValueError, ProcessLookupError, PermissionError, OSError):
        return False
