"""Render a Markdown report for a case (Phase 17).

Pulled out of the CLI body so the GUI worker can call the same code
path. Mirrors the CLI's previous logic exactly: collect every
analyzer's output, render via :func:`render_markdown_report`, write
atomically to ``<case>/reports/report-<timestamp>.md``, and refresh
the case manifest.
"""

from __future__ import annotations

import logging
import os
import tempfile
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING

from mgf.vm.core.exceptions import VManagerError

from mgf.vm.forensics.analysis import (
    analyze_pcap,
    build_timeline,
)
from mgf.vm.forensics.analysis.filesystem import (
    load_diff,
    render_diff_table,
    summarize_diff,
)
from mgf.vm.forensics.case import ArtifactKind
from mgf.vm.forensics.manifest import build_manifest, save_manifest
from mgf.vm.forensics.reports import ReportInputs, render_markdown_report

if TYPE_CHECKING:
    from mgf.vm.forensics.store import CaseStore


__all__ = ["render_case_report"]


LOG = logging.getLogger("mgf.vm.forensics.render")


def render_case_report(store: CaseStore, case_name: str) -> Path:
    """Render a Markdown report for the case; return the on-disk path.

    The report file lands at
    ``<case_dir>/reports/report-<UTC-timestamp>.md`` (mode 0o600,
    atomic temp+rename). The case manifest is refreshed afterwards so
    ``case show --verify`` stays clean.

    Returns the resolved report path.
    """
    case = store.load(case_name)
    case_dir = store.case_dir(case_name)

    inputs = ReportInputs(case)

    # Network: analyse the latest pcap if any.
    pcaps = [a for a in case.artifacts if a.kind is ArtifactKind.NETWORK_PCAP]
    if pcaps:
        try:
            inputs.network = analyze_pcap(pcaps[-1].path)
        except (OSError, ValueError, VManagerError):
            # Best-effort: a missing/corrupt pcap drops the network
            # section rather than failing the whole report. Narrow to
            # I/O + parse + domain errors (EH-07) so a programming bug
            # still surfaces instead of being silently swallowed.
            LOG.warning(
                "network analysis failed; omitting section from report",
                extra={"case_name": case_name, "pcap": str(pcaps[-1].path)},
                exc_info=True,
            )
            inputs.network = None

    # Filesystem: latest diff (if a baseline + diff have been captured).
    diffs = [a for a in case.artifacts if a.kind is ArtifactKind.FS_DIFF]
    if diffs:
        try:
            loaded = load_diff(diffs[-1].path)
            inputs.fs_summary = summarize_diff(loaded)
            inputs.fs_rows = render_diff_table(loaded)
        except VManagerError:
            pass

    # Timeline always renders — Case state alone produces it.
    inputs.timeline = build_timeline(case)

    body = render_markdown_report(inputs)
    when = datetime.now(UTC).strftime("%Y%m%dT%H%M%S")
    target = case_dir / "reports" / f"report-{when}.md"
    target.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp_str = tempfile.mkstemp(
        prefix=".report-",
        suffix=".md",
        dir=str(target.parent),
    )
    tmp = Path(tmp_str)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(body)
        tmp.chmod(0o600)
        tmp.replace(target)
    except OSError:
        tmp.unlink(missing_ok=True)
        raise

    save_manifest(case_dir, build_manifest(case_dir))

    LOG.info(
        "case report rendered",
        extra={"case_name": case_name, "report_path": str(target)},
    )
    return target
