"""Display-isolation checks (MALWARE_ANALYSIS.md §5.5, 2 checks).

When SPICE / VNC are enabled they MUST be:

- :func:`display_local_only` — bound to ``127.0.0.1`` (no
  network-reachable display server).
- :func:`display_password_set` — protected by a password (the
  fix executor generates a 24-char :func:`secrets.token_urlsafe`
  and stores it; the orchestrator surfaces it via ``vm info``).

A VM with no graphics at all (``<graphics>`` element absent) is
trivially safe — both checks pass.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mgf.vm.core.security.isolation.checks._helpers import finding
from mgf.vm.core.security.isolation.types import (
    FixKind,
    IsolationCategory,
    IsolationFinding,
    isolation_check,
)

if TYPE_CHECKING:
    from xml.etree.ElementTree import Element

    from mgf.vm.core.security.isolation.context import AnalysisContext


__all__ = [
    "display_local_only",
    "display_password_set",
]


def _graphics_elements(ctx: AnalysisContext) -> list[Element]:
    return ctx.find_devices("graphics")


@isolation_check
def display_local_only(ctx: AnalysisContext) -> IsolationFinding:
    """Every ``<graphics>`` listens on 127.0.0.1 only."""
    bad: list[dict[str, str]] = []
    for g in _graphics_elements(ctx):
        listen_attr = g.get("listen")
        listen_child = g.find("listen")
        listen_addr = listen_attr or (
            listen_child.get("address") if listen_child is not None else None
        )
        if listen_addr not in ("127.0.0.1", "::1"):
            bad.append(
                {
                    "type": g.get("type", "?"),
                    "listen": listen_addr or "(unset = libvirt default)",
                }
            )

    return finding(
        fid="display.local_only",
        title="SPICE/VNC bound to loopback only",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not bad,
        summary_pass=(
            "every graphics endpoint listens on 127.0.0.1 (no network reach)"
            if _graphics_elements(ctx)
            else "no graphics endpoints declared"
        ),
        summary_fail=(f"{len(bad)} graphics endpoint(s) reachable from the network: {bad}"),
        details={"misconfigured": bad},
        fix_executor="graphics.localhost_listen",
    )


@isolation_check
def display_password_set(ctx: AnalysisContext) -> IsolationFinding:
    """Every ``<graphics>`` has a non-empty ``passwd`` attribute.

    ADVISORY rather than BLOCKING — a localhost-only SPICE without
    a password is reachable only by root / the qemu user, but a
    shoulder-surf or stray ``virt-viewer`` session can open it.
    The fix generates a fresh 24-char password each invocation.
    """
    missing: list[str] = []
    for g in _graphics_elements(ctx):
        if not g.get("passwd"):
            missing.append(g.get("type", "?"))

    return finding(
        fid="display.password_set",
        title="SPICE/VNC password set",
        category=IsolationCategory.ADVISORY,
        fix_kind=FixKind.AUTO_FIX,
        passing=not missing,
        summary_pass=(
            "every graphics endpoint has a password"
            if _graphics_elements(ctx)
            else "no graphics endpoints declared"
        ),
        summary_fail=(f"{len(missing)} graphics endpoint(s) without a password: {missing}"),
        details={"types_without_password": missing},
        fix_executor="graphics.set_password",
    )
