"""Pure analysis context for isolation checks (Wave 1 port).

:class:`AnalysisContext` is the read-only bundle every
``@isolation_check`` walks. The original lived in
``mgf.vm.security.isolation.static_analyzer`` alongside the
``analyze_static`` entry point, which parses untrusted XML via
``defusedxml`` and synthesises XML from a spec — both ADAPTER-bound
(defusedxml is forbidden in the pure core).

This module ports ONLY the pure context shape + its two element-walk
methods. It accepts an already-parsed stdlib
:class:`xml.etree.ElementTree.ElementTree`; parsing untrusted strings
stays in the adapter (``analyze_static`` / ``synthesize_xml_from_spec``
are DEFERRED to Wave 2). The ``spec`` attribute from the original is
dropped here because it pinned the legacy ``creator.spec.VMSpec``,
which the new core does not carry.
"""

from __future__ import annotations

import xml.etree.ElementTree as ET  # noqa: TC003
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from mgf.vm.core.domain import IsolationLevel
from mgf.vm.core.exceptions import SpecValidationError

if TYPE_CHECKING:
    from collections.abc import Mapping


__all__ = ["QUARANTINE_NETWORK_NAME", "AnalysisContext"]


# Canonical quarantine-network name. Kept here (rather than in the
# deferred adapter analyzer) so the pure checks/fixes that reference
# it stay importable in the pure core.
QUARANTINE_NETWORK_NAME = "vmanager-quarantine"


@dataclass(frozen=True)
class AnalysisContext:
    """Carries everything an :func:`isolation_check` needs.

    Frozen so checks can't accidentally mutate the inputs.

    The ``runtime_audit`` flag is False for a static analysis (set
    True by the runtime analyzer); it lets a check distinguish "this
    VM hasn't started yet" from "this VM is running right now".

    ``host_facts`` is populated by the runtime analyzer with host-side
    facts (qemu PID, AppArmor mode, libvirt URI, open FDs, network
    state). Empty for static-mode analyses; runtime-only checks read
    keys from this dict via ``ctx.host_facts.get("apparmor.profile_state")``
    etc. Tests inject mock host_facts directly.
    """

    domain_xml: ET.ElementTree
    vm_name: str
    declared_level: IsolationLevel = IsolationLevel.STRICT
    runtime_audit: bool = False
    host_facts: Mapping[str, object] = field(default_factory=dict)

    def root(self) -> ET.Element:
        """Return the ``<domain>`` root element."""
        root = self.domain_xml.getroot()
        if root is None:  # defensive — ET.fromstring always sets this
            msg = "domain XML has no root element"
            raise SpecValidationError([msg])
        return root

    def find_devices(self, tag: str) -> list[ET.Element]:
        """Return every ``<devices>/<{tag}>`` child element."""
        return self.root().findall(f"./devices/{tag}")
