#!/usr/bin/python3
"""Root lifecycle boundary for the disabled-by-default secure DNS stack."""

from __future__ import annotations

import json
import os
import grp
import subprocess
import sys
import tempfile
from pathlib import Path

from octava_secure_dns import compile_plan, load_config


CONFIG_PATH = Path("/etc/octava-connect/secure-dns.json")
RUNTIME_DIR = Path("/run/octava-secure-dns")
NFT_FAMILY = "inet"
NFT_TABLE = "octava_dns_guard"


def atomic_write(path: Path, content: str, mode: int = 0o640) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as handle:
            handle.write(content)
            handle.flush()
            os.fsync(handle.fileno())
        os.chmod(temporary, mode)
        os.chown(temporary, 0, grp.getgrnam("unbound").gr_gid)
        os.replace(temporary, path)
    finally:
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass


def run_checked(argv: list[str], input_text: str | None = None) -> subprocess.CompletedProcess:
    return subprocess.run(
        argv,
        input=input_text,
        text=True,
        capture_output=True,
        timeout=15,
        check=False,
        env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"},
    )


def get_plan() -> dict:
    return compile_plan(load_config(CONFIG_PATH))


def prepare(plan: dict) -> None:
    if not plan["enabled"]:
        raise SystemExit("secure DNS configuration is disabled")
    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
    os.chmod(RUNTIME_DIR, 0o755)
    atomic_write(RUNTIME_DIR / "unbound.conf", plan["unbound"])
    atomic_write(RUNTIME_DIR / "dnsmasq.conf", plan["dnsmasq"])
    atomic_write(RUNTIME_DIR / "guard.nft", plan["guard_nft"], 0o600)
    unbound_check = run_checked(["/usr/sbin/unbound-checkconf", str(RUNTIME_DIR / "unbound.conf")])
    if unbound_check.returncode != 0:
        raise SystemExit("Unbound configuration validation failed")
    dnsmasq_check = run_checked(["/usr/sbin/dnsmasq", "--test", f"--conf-file={RUNTIME_DIR / 'dnsmasq.conf'}"])
    if dnsmasq_check.returncode != 0:
        raise SystemExit("dnsmasq configuration validation failed")


def table_exists() -> bool:
    return run_checked(["/usr/sbin/nft", "list", "table", NFT_FAMILY, NFT_TABLE]).returncode == 0


def apply_guard(plan: dict) -> None:
    prepare(plan)
    candidate = plan["guard_nft"]
    if not candidate:
        return
    script = candidate
    if table_exists():
        script = f"delete table {NFT_FAMILY} {NFT_TABLE}\n" + candidate
    checked = run_checked(["/usr/sbin/nft", "--check", "--file", "-"], script)
    if checked.returncode != 0:
        raise SystemExit("secure DNS nft validation failed")
    applied = run_checked(["/usr/sbin/nft", "--file", "-"], script)
    if applied.returncode != 0:
        raise SystemExit("secure DNS nft apply failed")


def remove_guard() -> None:
    if table_exists():
        deleted = run_checked(["/usr/sbin/nft", "delete", "table", NFT_FAMILY, NFT_TABLE])
        if deleted.returncode != 0:
            raise SystemExit("secure DNS nft cleanup failed")


def main() -> int:
    if os.geteuid() != 0:
        raise SystemExit("octava-secure-dns-control must run as root")
    if len(sys.argv) != 2 or sys.argv[1] not in {"validate", "plan", "prepare", "apply-guard", "remove-guard"}:
        raise SystemExit("usage: octava-secure-dns-control validate|plan|prepare|apply-guard|remove-guard")
    action = sys.argv[1]
    if action == "remove-guard":
        remove_guard()
        return 0
    plan = get_plan()
    if action == "validate":
        print(plan["config_sha256"])
    elif action == "plan":
        print(json.dumps(plan, indent=2, sort_keys=True))
    elif action == "prepare":
        prepare(plan)
    elif action == "apply-guard":
        apply_guard(plan)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
