"""MacServe-owned guest networking only. No customer workload management."""
import base64
import fcntl
import ipaddress
import json
import os
from pathlib import Path
import re
import shutil
import socket
import subprocess
import sys
import tempfile
import time


def run(args, data=None):
    process = subprocess.run(args, input=data, text=True, capture_output=True, timeout=60)
    if process.returncode:
        # wg-quick can include the private key in debug output on failure.
        # Never expose raw subprocess output from privileged networking commands.
        raise RuntimeError(args[0] + " failed; inspect guest networking locally")
    return process.stdout.strip()


def key(value):
    if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9+/]{43}=", value):
        raise ValueError("Invalid WireGuard key")
    decoded = base64.b64decode(value, validate=True)
    if len(decoded) != 32 or decoded == bytes(32) or base64.b64encode(decoded).decode() != value:
        raise ValueError("Invalid WireGuard key")
    return value


def atomic(path, content):
    path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
    fd, temporary = tempfile.mkstemp(prefix=".macserve-", dir=path.parent)
    try:
        with os.fdopen(fd, "w") as output:
            os.fchmod(output.fileno(), 0o600)
            output.write(content)
            output.flush()
            os.fsync(output.fileno())
        os.replace(temporary, path)
        directory = os.open(path.parent, os.O_DIRECTORY)
        try:
            os.fsync(directory)
        finally:
            os.close(directory)
    finally:
        if os.path.exists(temporary):
            os.unlink(temporary)


def configuration(data, private):
    if data.get("transport") != "wireguard" or data.get("allowedIPs") != ["10.77.0.1/32", "fd77::1/128"]:
        raise ValueError("Refusing a non-split tunnel configuration")
    if data.get("relayIPv4") != "10.77.0.1" or data.get("relayIPv6") != "fd77::1":
        raise ValueError("Unexpected relay tunnel addresses")
    ip4, ip6 = ipaddress.ip_address(data["ipv4"]), ipaddress.ip_address(data["ipv6"])
    if ip4 not in ipaddress.ip_network("10.77.0.0/16") or ip6 not in ipaddress.ip_network("fd77::/64") or str(ip4) == "10.77.0.1" or str(ip6) == "fd77::1":
        raise ValueError("Invalid machine tunnel addresses")
    endpoint = data["endpoint"]
    if not isinstance(endpoint, str) or not re.fullmatch(r"(?:[a-zA-Z0-9.-]+|\[[a-fA-F0-9:]+\]):51820", endpoint):
        raise ValueError("Invalid relay endpoint")
    return (f"[Interface]\nPrivateKey = {key(private)}\nAddress = {ip4}/32, {ip6}/128\nMTU = 1280\n\n"
            f"[Peer]\nPublicKey = {key(data['publicKey'])}\nEndpoint = {endpoint}\n"
            "AllowedIPs = 10.77.0.1/32, fd77::1/128\nPersistentKeepalive = 25\n")


def endpoint_candidates(endpoint):
    host = endpoint.rsplit(":", 1)[0].strip("[]")
    candidates = []
    for family, _, _, _, sockaddr in socket.getaddrinfo(host, 51820, socket.AF_UNSPEC, socket.SOCK_DGRAM):
        address = sockaddr[0]
        # Only consider endpoints for which this guest has an actual route.
        result = subprocess.run(["ip"] + (["-6"] if family == socket.AF_INET6 else []) + ["route", "get", address],
                                capture_output=True, text=True, timeout=5)
        if result.returncode == 0 and "wgms0" not in result.stdout:
            rendered = f"[{address}]:51820" if family == socket.AF_INET6 else f"{address}:51820"
            if rendered not in candidates:
                candidates.append(rendered)
    if not candidates:
        raise RuntimeError("No reachable relay address on this connection")
    return candidates


def status():
    """Inspect public kernel state only; never create keys, files or interfaces."""
    interfaces = run(["wg", "show", "interfaces"]).split()
    if "wgms0" not in interfaces:
        return {"tunnelState": "not-configured", "lastHandshakeAt": None}
    records = run(["wg", "show", "wgms0", "latest-handshakes"]).splitlines()
    if len(records) != 1:
        return {"tunnelState": "unknown", "lastHandshakeAt": None,
                "error": "Expected one relay peer on the MacServe tunnel."}
    fields = records[0].split()
    if len(fields) != 2 or not fields[1].isascii() or not fields[1].isdigit():
        raise ValueError("The tunnel returned an invalid handshake timestamp.")
    latest = int(fields[1])
    now = time.time()
    if latest > now:
        return {"tunnelState": "unknown", "lastHandshakeAt": None,
                "error": "The tunnel handshake is in the future; check the system clock."}
    return {"tunnelState": "connected" if latest > 0 and now - latest <= 180 else
            ("connecting" if latest == 0 else "offline"),
            "lastHandshakeAt": latest or None}


def main():
    if os.geteuid() != 0:
        raise ValueError("Guest networking requires sudo")
    action = sys.argv[1]
    if action == "status":
        print(json.dumps(status()))
        return
    if action not in ("init", "apply"):
        raise ValueError("Unknown guest networking action")
    os.umask(0o077)
    root = Path("/etc/macserve")
    root.mkdir(mode=0o700, exist_ok=True)
    with open(root / "network.lock", "a") as lock:
        fcntl.flock(lock, fcntl.LOCK_EX)
        private_path = root / "network.key"
        if not private_path.exists():
            # Never replace a lost identity while a live config still exists.
            if Path("/etc/wireguard/wgms0.conf").exists():
                raise RuntimeError("Guest key is missing; restore it before reconnecting")
            atomic(private_path, run(["wg", "genkey"]) + "\n")
        private = key(private_path.read_text().strip())
        public = run(["wg", "pubkey"], private + "\n")
        if action == "init":
            print(json.dumps({"publicKey": public}))
            return
        if action != "apply":
            raise ValueError("Unknown guest networking action")
        data = json.loads(sys.argv[2])
        content = configuration(data, private)
        path = Path("/etc/wireguard/wgms0.conf")
        previous = path.read_text() if path.exists() else None
        links = json.loads(run(["ip", "-j", "link", "show"]))
        exists = any(link["ifname"] == "wgms0" for link in links)
        if exists and previous is None:
            raise RuntimeError("Refusing to take over an existing unowned WireGuard interface")
        if previous != content and exists:
            run(["wg-quick", "down", "wgms0"])
            exists = False
        if previous != content:
            atomic(path, content)
        if not exists:
            run(["wg-quick", "up", "wgms0"])
        # Re-resolve after network changes. Rotate through reachable A/AAAA
        # candidates if a handshake is stale; do not change a healthy endpoint.
        handshakes = run(["wg", "show", "wgms0", "latest-handshakes"]).splitlines()
        latest = max((int(line.split()[1]) for line in handshakes), default=0)
        if latest < time.time() - 180:
            choices = endpoint_candidates(data["endpoint"])
            attempt_path = root / "endpoint-attempt"
            attempt = int(attempt_path.read_text()) if attempt_path.exists() else 0
            endpoint = choices[attempt % len(choices)]
            run(["wg", "set", "wgms0", "peer", key(data["publicKey"]), "endpoint", endpoint])
            atomic(attempt_path, str(attempt + 1))
        if shutil.which("systemctl") and Path("/run/systemd/system").exists():
            run(["systemctl", "enable", "wg-quick@wgms0.service"])
        elif shutil.which("rc-update"):
            service = Path("/etc/init.d/wg-quick.wgms0")
            if not service.exists():
                service.symlink_to("/etc/init.d/wg-quick")
            run(["rc-update", "add", "wg-quick.wgms0", "default"])
        print(json.dumps({"publicKey": public, "configured": True, "handshakeAt": latest,
                          "localEgress": True, "interface": "wgms0"}))


if __name__ == "__main__":
    try:
        main()
    except Exception as error:
        print(str(error), file=sys.stderr)
        sys.exit(1)
