"""Shared-channel-isolation checks (MALWARE_ANALYSIS.md §5.3, 5 checks).

Every shared-channel device is a guest↔host bidirectional pipe and
is forbidden in malware-analysis VMs:

- :func:`share_no_filesystem` — no ``<filesystem>`` (virtio-fs / 9p).
- :func:`share_no_qemu_agent` — no ``org.qemu.guest_agent.0``
  channel.
- :func:`share_no_spice_clipboard` — no ``com.redhat.spice.0``
  channel (clipboard).
- :func:`share_no_redirdev` — no USB redirection device.
- :func:`share_no_smartcard` — no smartcard passthrough.

All five are BLOCKING + AUTO_FIX (the fix is "strip the element").
"""

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__ = [
    "share_no_filesystem",
    "share_no_qemu_agent",
    "share_no_redirdev",
    "share_no_smartcard",
    "share_no_spice_clipboard",
]


def _channel_targets(elements: list[Element]) -> list[str]:
    """Return the ``target.name`` of every ``<channel>`` element passed in."""
    out: list[str] = []
    for ch in elements:
        target = ch.find("target")
        if target is not None:
            out.append(target.get("name", ""))
    return out


@isolation_check
def share_no_filesystem(ctx: AnalysisContext) -> IsolationFinding:
    """No ``<filesystem>`` block (virtio-fs / 9p)."""
    fs = ctx.find_devices("filesystem")
    sources: list[str] = []
    for f in fs:
        source = f.find("source")
        if source is None:
            sources.append("(no source)")
            continue
        sources.append(
            source.get("dir") or source.get("socket") or source.get("file") or "(unknown)",
        )

    return finding(
        fid="share.no_filesystem",
        title="No host filesystem shared with guest",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not fs,
        summary_pass="no <filesystem> blocks declared",
        summary_fail=(
            f"{len(fs)} <filesystem> block(s) — virtio-fs / 9p shares with the host: {sources}"
        ),
        details={"sources": sources},
        fix_executor="strip.filesystem",
    )


@isolation_check
def share_no_qemu_agent(ctx: AnalysisContext) -> IsolationFinding:
    """No qemu-guest-agent channel (``org.qemu.guest_agent.0``)."""
    targets = _channel_targets(ctx.find_devices("channel"))
    bad = [t for t in targets if t.startswith("org.qemu.guest_agent")]
    return finding(
        fid="share.no_qemu_agent",
        title="No qemu-guest-agent channel",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not bad,
        summary_pass="no <channel> with org.qemu.guest_agent.* target",
        summary_fail=(
            f"{len(bad)} qemu-guest-agent channel(s) — bidirectional command bus to host: {bad}"
        ),
        details={"targets": bad},
        fix_executor="strip.channel.guest_agent",
    )


@isolation_check
def share_no_spice_clipboard(ctx: AnalysisContext) -> IsolationFinding:
    """No SPICE clipboard channel (``com.redhat.spice.0``)."""
    targets = _channel_targets(ctx.find_devices("channel"))
    bad = [t for t in targets if t.startswith("com.redhat.spice")]
    return finding(
        fid="share.no_spice_clipboard",
        title="No SPICE clipboard channel",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not bad,
        summary_pass="no <channel> with com.redhat.spice.* target",
        summary_fail=(
            f"{len(bad)} SPICE clipboard channel(s) — guest can read "
            f"and write the host clipboard: {bad}"
        ),
        details={"targets": bad},
        fix_executor="strip.channel.spice_clipboard",
    )


@isolation_check
def share_no_redirdev(ctx: AnalysisContext) -> IsolationFinding:
    """No USB redirection device (``<redirdev>``)."""
    redir = ctx.find_devices("redirdev")
    return finding(
        fid="share.no_redirdev",
        title="No USB redirection",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not redir,
        summary_pass="no <redirdev> entries",
        summary_fail=(
            f"{len(redir)} <redirdev> entr(y/ies) — guest can pull "
            f"USB devices from the host's SPICE channel"
        ),
        details={"types": [r.get("bus", "") for r in redir]},
        fix_executor="strip.redirdev",
    )


@isolation_check
def share_no_smartcard(ctx: AnalysisContext) -> IsolationFinding:
    """No smartcard passthrough (``<smartcard>``)."""
    sc = ctx.find_devices("smartcard")
    return finding(
        fid="share.no_smartcard",
        title="No smartcard passthrough",
        category=IsolationCategory.BLOCKING,
        fix_kind=FixKind.AUTO_FIX,
        passing=not sc,
        summary_pass="no <smartcard> entries",
        summary_fail=(
            f"{len(sc)} <smartcard> entr(y/ies) — guest can use host-side smartcard credentials"
        ),
        details={"modes": [s.get("mode", "") for s in sc]},
        fix_executor="strip.smartcard",
    )
