"""Lockdown tests for the curated examples kept under `deploy/files/`.

`deploy/` is reference material. The production units are emitted by
ckn-bw's `systemd_units` reactor in `bundles/left4me/metadata.py`;
when reactor output drifts intentionally, update these examples to match.
"""
from pathlib import Path


ROOT = Path(__file__).resolve().parents[2]
DEPLOY = ROOT / "deploy"


WEB_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-web.service"
SERVER_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-server@.service"
GAME_SLICE = DEPLOY / "files/usr/local/lib/systemd/system/l4d2-game.slice"
BUILD_SLICE = DEPLOY / "files/usr/local/lib/systemd/system/l4d2-build.slice"
SYSCTL_CONF = DEPLOY / "files/etc/sysctl.d/99-left4me.conf"
SANDBOX_RESOLV_CONF = DEPLOY / "files/etc/left4me/sandbox-resolv.conf"
HOST_ENV = DEPLOY / "templates/etc/left4me/host.env"
WEB_ENV_TEMPLATE = DEPLOY / "templates/etc/left4me/web.env.template"
WEB_HARDENING_DROPIN = DEPLOY / "files/etc/systemd/system/left4me-web.service.d/10-hardening.conf"
SERVER_HARDENING_DROPIN = DEPLOY / "files/etc/systemd/system/left4me-server@.service.d/10-hardening.conf"


def test_global_unit_files_exist_at_product_level_paths():
    assert WEB_UNIT.is_file()
    assert SERVER_UNIT.is_file()


def test_web_unit_contains_required_runtime_contract():
    unit = WEB_UNIT.read_text()

    assert "User=left4me" in unit
    assert "Group=left4me" in unit
    assert "WorkingDirectory=/opt/left4me" in unit
    assert "PATH=/var/lib/left4me/.venv/bin:" in unit
    assert "EnvironmentFile=/etc/left4me/host.env" in unit
    assert "EnvironmentFile=/etc/left4me/web.env" in unit
    assert "ExecStart=/var/lib/left4me/.venv/bin/gunicorn" in unit
    assert "--workers 1" in unit
    assert "--threads 32" in unit
    # NoNewPrivileges must remain unset because sudo (used by the overlay,
    # systemctl and journalctl helpers) is setuid.
    assert "NoNewPrivileges=true" not in unit
    assert "ReadWritePaths=/var/lib/left4me" in unit
    # Mounts now happen in PID 1's namespace via the left4me-overlay helper,
    # so MountFlags propagation is irrelevant — and the previous assumption
    # that MountFlags=shared made it work was incorrect.
    assert "MountFlags=" not in unit
    # Hardening directives belong in the drop-in; must not appear in the base unit.
    assert "PrivateTmp=" not in unit
    assert "ProtectSystem=" not in unit


def test_server_unit_contains_required_runtime_contract():
    unit = SERVER_UNIT.read_text()

    assert "User=left4me" in unit
    assert "Group=left4me" in unit
    assert "EnvironmentFile=/etc/left4me/host.env" in unit
    assert "EnvironmentFile=/var/lib/left4me/instances/%i/instance.env" in unit
    # `-` prefix: chdir failure is non-fatal so ExecStartPre can run the
    # mount helper before the merged dir exists. ExecStart re-applies and
    # finds the dir once the mount has landed.
    assert "WorkingDirectory=-/var/lib/left4me/runtime/%i/merged/left4dead2" in unit
    # ExecStart must invoke srcds_run from the *merged* overlay tree, not
    # from installation/. srcds_run cds to its own dirname; if we point at
    # installation/, the engine reads gameinfo.txt and addons from the lower
    # layer and never sees overlay plugins (Metamod/SourceMod) or cfgs.
    assert "ExecStart=/var/lib/left4me/runtime/%i/merged/srcds_run" in unit
    assert "$L4D2_ARGS" in unit
    assert "${L4D2_ARGS}" not in unit
    # Hardening directives belong in the drop-in; must not appear in the base unit.
    assert "NoNewPrivileges=" not in unit
    assert "PrivateTmp=" not in unit
    assert "PrivateDevices=" not in unit
    assert "ProtectHome=" not in unit
    assert "ProtectSystem=" not in unit
    assert "RestrictSUIDSGID=" not in unit
    assert "LockPersonality=" not in unit


def test_server_unit_mounts_overlay_via_exec_start_pre():
    """At boot, systemd auto-starts enabled units before the web app gets a
    chance to run start_instance's pre-start mount. The unit itself must
    re-mount the overlay so reboots are transparent. Pairs with the helper's
    idempotency check (test_overlay_helper_mount_is_idempotent_when_mounted).

    The unit-level `nsenter --mount=/proc/1/ns/mnt --` is what makes
    umount fast: without it, the helper Python process would inherit
    the unit's per-service mount namespace and pin it alive, blocking
    PID 1's umount until the helper exited. Wrapping with nsenter at
    the Exec line puts the helper itself in PID 1's namespace.
    """
    unit = SERVER_UNIT.read_text()
    # `+` prefix: runs as PID 1 (root, no sandbox). Required because
    # the unit has NoNewPrivileges=true, which blocks sudo's setuid
    # escalation — and the helper needs root for the mount syscall.
    assert (
        "ExecStartPre=+/usr/bin/nsenter --mount=/proc/1/ns/mnt -- "
        "/usr/local/libexec/left4me/left4me-overlay mount %i"
        in unit
    )
    # Bound the restart loop; without these, a CHDIR-failure (or any other
    # pre-start error) spins indefinitely.
    assert "StartLimitBurst=5" in unit
    assert "StartLimitIntervalSec=60s" in unit


def test_server_unit_unmounts_overlay_via_exec_stop_post():
    """Single source of truth for unmount, mirroring the mount path.
    ExecStopPost (not ExecStop) so it runs after srcds has fully exited
    and the cgroup is cleared.

    Same nsenter-at-Exec-line wrapping as ExecStartPre — without it,
    the helper process would itself hold a reference to the unit's
    per-service mount namespace, and umount in PID 1 would loop on
    EBUSY until the helper gave up. With it, umount succeeds first try.
    """
    unit = SERVER_UNIT.read_text()
    assert (
        "ExecStopPost=+/usr/bin/nsenter --mount=/proc/1/ns/mnt -- "
        "/usr/local/libexec/left4me/left4me-overlay umount %i"
        in unit
    )


def test_server_unit_contains_perf_baseline_directives():
    unit = SERVER_UNIT.read_text()

    # Slice membership.
    assert "Slice=l4d2-game.slice" in unit

    # CFS priority bump (no SCHED_FIFO).
    assert "Nice=-5" in unit
    assert "CPUSchedulingPolicy=" not in unit

    # I/O priority.
    assert "IOSchedulingClass=best-effort" in unit
    assert "IOSchedulingPriority=4" in unit

    # OOM ordering: game servers survive, sandbox dies first.
    assert "OOMScoreAdjust=-200" in unit

    # Memory caps with headroom for map-load spikes.
    assert "MemoryHigh=1.5G" in unit
    assert "MemoryMax=2G" in unit

    # Bounded fork surface.
    assert "TasksMax=256" in unit

    # Plenty of fds for plugin-heavy setups.
    assert "LimitNOFILE=65536" in unit

    # srcds clean shutdown via SIGINT, with time to flush. With the
    # helper running in PID 1's mount namespace (via the unit-level
    # nsenter on ExecStopPost), umount has no race window and the
    # default 15 s is plenty for the whole stop transition.
    assert "KillSignal=SIGINT" in unit
    assert "TimeoutStopSec=15s" in unit

    # Per-unit override of journald rate limiting (default drops srcds output).
    assert "LogRateLimitIntervalSec=0" in unit


def test_l4d2_game_slice_exists_with_high_weights():
    assert GAME_SLICE.is_file()
    text = GAME_SLICE.read_text()
    assert "[Slice]" in text
    assert "CPUWeight=1000" in text
    assert "IOWeight=1000" in text


def test_l4d2_build_slice_exists_with_low_weights():
    assert BUILD_SLICE.is_file()
    text = BUILD_SLICE.read_text()
    assert "[Slice]" in text
    assert "CPUWeight=10" in text
    assert "IOWeight=10" in text


def test_sysctl_conf_present_with_perf_settings():
    assert SYSCTL_CONF.is_file()
    text = SYSCTL_CONF.read_text()
    for line in (
        "net.core.rmem_max = 8388608",
        "net.core.wmem_max = 8388608",
        "net.core.rmem_default = 524288",
        "net.core.wmem_default = 524288",
        "net.core.netdev_max_backlog = 5000",
        "net.core.netdev_budget = 600",
        "vm.swappiness = 10",
        "net.ipv4.udp_rmem_min = 16384",
        "net.ipv4.udp_wmem_min = 16384",
        "net.core.default_qdisc = fq_codel",
        "net.ipv4.tcp_congestion_control = bbr",
        "kernel.yama.ptrace_scope = 2",
    ):
        assert line in text, f"missing {line!r} in 99-left4me.conf"


def test_env_templates_contain_required_defaults():
    host_env = HOST_ENV.read_text()
    assert "Deployment units use fixed /var/lib/left4me paths" in host_env
    assert host_env.endswith("LEFT4ME_ROOT=/var/lib/left4me\n")
    web_env = WEB_ENV_TEMPLATE.read_text()
    assert web_env.startswith(
        "DATABASE_URL=sqlite:////var/lib/left4me/left4me.db\n"
        "SECRET_KEY=replace-with-generated-secret\n"
        "JOB_WORKER_THREADS=4\n"
    )
    assert web_env.rstrip().endswith("STEAM_WEB_API_KEY=")


def test_sandbox_resolv_conf_exists():
    assert SANDBOX_RESOLV_CONF.is_file()
    text = SANDBOX_RESOLV_CONF.read_text()
    nameservers = [
        line.split()[1]
        for line in text.splitlines()
        if line.startswith("nameserver ")
    ]
    assert len(nameservers) >= 2, "expected at least two nameservers for redundancy"
    # Sanity: the resolvers must be public (not RFC1918 / loopback). We don't
    # pin the exact IPs — Cloudflare/Google/Quad9 are all acceptable.
    for ns in nameservers:
        assert not ns.startswith("127."), ns
        assert not ns.startswith("10."), ns
        assert not ns.startswith("192.168."), ns
        first_octet = int(ns.split(".")[0])
        # Reject 172.16.0.0/12.
        if first_octet == 172:
            second_octet = int(ns.split(".")[1])
            assert not (16 <= second_octet <= 31), ns


def test_web_hardening_dropin_present_with_directives():
    assert WEB_HARDENING_DROPIN.is_file()
    text = WEB_HARDENING_DROPIN.read_text()
    assert "[Service]" in text
    # COMMON
    for d in (
        "ProtectProc=invisible",
        "ProtectKernelTunables=true",
        "ProtectKernelModules=true",
        "ProtectKernelLogs=true",
        "ProtectClock=true",
        "ProtectControlGroups=true",
        "ProtectHostname=true",
        "LockPersonality=true",
        "ProtectSystem=strict",
        "ProtectHome=true",
        "PrivateTmp=true",
        "RestrictNamespaces=true",
        "RestrictRealtime=true",
        "RemoveIPC=true",
        "KeyringMode=private",
        "UMask=0027",
        "RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX",
    ):
        assert d in text, f"missing {d!r} in web hardening drop-in"
    # WEB-specific
    # `native x86` (not `native`) because the install job fork-execs
    # steamcmd_linux (32-bit). Plain `native` produces SIGSYS (bash exit 159).
    assert "SystemCallArchitectures=native x86" in text
    assert "SystemCallFilter=@system-service" in text
    assert "SystemCallFilter=~@debug @mount @raw-io @reboot @swap @cpu-emulation @obsolete" in text
    # WEB must NOT include the sudo-incompatible directives.
    assert "NoNewPrivileges=" not in text
    assert "PrivateUsers=" not in text
    assert "RestrictSUIDSGID=" not in text
    assert "CapabilityBoundingSet=" not in text
    assert "~@privileged" not in text


def test_server_hardening_dropin_present_with_directives():
    assert SERVER_HARDENING_DROPIN.is_file()
    text = SERVER_HARDENING_DROPIN.read_text()
    assert "[Service]" in text
    for d in (
        "NoNewPrivileges=true",
        "RestrictSUIDSGID=true",
        "PrivateUsers=true",
        "PrivatePIDs=true",
        "PrivateIPC=true",
        "PrivateDevices=true",
        "CapabilityBoundingSet=",
        "AmbientCapabilities=",
        "SystemCallArchitectures=native x86",
        "TemporaryFileSystem=/var/lib /etc /opt /home /root /srv /mnt /media",
        "BindReadOnlyPaths=/var/lib/left4me/installation",
        "BindReadOnlyPaths=/var/lib/left4me/overlays",
        "BindReadOnlyPaths=/etc/left4me/host.env",
        "BindPaths=/var/lib/left4me/runtime/%i",
        "SocketBindAllow=udp:27000-27999",
        "SocketBindAllow=tcp:27000-27999",
    ):
        assert d in text, f"missing {d!r} in server hardening drop-in"
    assert "SystemCallFilter=~@debug @mount @raw-io @reboot @swap @cpu-emulation @obsolete @privileged" in text
    # MemoryDenyWriteExecute must remain absent (Source engine compat).
    assert "MemoryDenyWriteExecute" not in text
    # ProcSubset=pid must remain absent — hides /proc/cpuinfo and breaks
    # SteamAPI master-server registration (LAN-only fallback). See
    # ckn-bw 4339289 and the comment block in the drop-in itself.
    for line in text.splitlines():
        bare = line.split("#", 1)[0].strip()
        assert bare != "ProcSubset=pid", "ProcSubset=pid must not be active in the server drop-in"


def test_hardening_dropins_agree_on_syscall_architectures():
    # Both units fork-exec a 32-bit binary on critical paths: the web
    # service runs the install job (steamcmd_linux), the server unit runs
    # srcds_linux. Either drop-in without `x86` in SystemCallArchitectures
    # SIGSYS-kills its child on first syscall (bash exit 159). They must
    # agree, and both must include x86 — caught the hard way on
    # 2026-05-15 when web had `native` only and the install job died.
    import re

    pat = re.compile(r"^SystemCallArchitectures=(.+)$", re.MULTILINE)
    web_arch = pat.search(WEB_HARDENING_DROPIN.read_text()).group(1).strip()
    srv_arch = pat.search(SERVER_HARDENING_DROPIN.read_text()).group(1).strip()
    assert web_arch == srv_arch, (
        f"hardening drop-ins disagree on SystemCallArchitectures: "
        f"web={web_arch!r} server={srv_arch!r}. Both must include `x86`."
    )
    assert "x86" in web_arch.split(), (
        f"SystemCallArchitectures missing x86: {web_arch!r}. Required for "
        "steamcmd_linux (install job) and srcds_linux."
    )
