"""``mgf-fed bundle`` — a single redacted diagnostic tarball (OP-07).

One command assembles everything a triager needs into one file, so an operator
or a consumer filing a bug ships *one* artifact instead of hand-collecting
version + config + health + logs + crashes. Composes the existing diagnostic
surfaces — ``version``, ``explain`` (active config, already secret-redacted),
``doctor``, ``logs``, and the on-disk crash reports — and runs every captured
text section through the :class:`Redactor` as defense-in-depth before it lands
in the tarball.

Side-effect-free + fail-open: each section is collected independently; a
section that errors becomes a "(failed to collect: …)" note rather than aborting
the bundle.
"""

from __future__ import annotations

import argparse
import contextlib
import io
import json
import shutil
import tarfile
import tempfile
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING

from mgf.common.cli import EXIT_OPERATION_ERROR, EXIT_SUCCESS

if TYPE_CHECKING:
    from collections.abc import Callable

# Cap the crash reports copied in so the bundle stays small.
_MAX_CRASHES = 20


def _ns(add_args: Callable[[argparse.ArgumentParser], None]) -> argparse.Namespace:
    """A default-valued Namespace for a subcommand (all its args are optional)."""
    p = argparse.ArgumentParser()
    add_args(p)
    return p.parse_args([])


def _capture(fn: Callable[[argparse.Namespace], int], ns: argparse.Namespace) -> str:
    """Run a subcommand handler with stdout captured. Fail-open."""
    buf = io.StringIO()
    with contextlib.redirect_stdout(buf), contextlib.suppress(Exception):
        fn(ns)
    return buf.getvalue() or "(no output)"


def add_bundle_args(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        "--output",
        "-o",
        default=None,
        help="Output tarball path (default: ./mgf-bundle-<timestamp>.tar.gz).",
    )


def run_bundle(args: argparse.Namespace) -> int:
    from mgf.common.observability import Redactor, default_crash_dir

    from mgf.fed.cli._doctor import add_doctor_args, run_doctor
    from mgf.fed.cli._explain import add_explain_args, run_explain
    from mgf.fed.cli._logs import add_logs_args, run_logs
    from mgf.fed.cli._version import version_facts

    redactor = Redactor.default()

    def _redact(text: str) -> str:
        return redactor.redact_string(text)

    sections: dict[str, Callable[[], str]] = {
        "version.json": lambda: json.dumps(version_facts(), indent=2),
        "explain.txt": lambda: _capture(run_explain, _ns(add_explain_args)),
        "doctor.txt": lambda: _capture(run_doctor, _ns(add_doctor_args)),
        "logs.txt": lambda: _capture(run_logs, _ns(add_logs_args)),
    }

    stamp = datetime.now(tz=UTC).strftime("%Y%m%dT%H%M%SZ")
    out = (
        Path(args.output)
        if getattr(args, "output", None)
        else Path.cwd() / f"mgf-bundle-{stamp}.tar.gz"
    )

    try:
        with tempfile.TemporaryDirectory() as d:
            stage = Path(d) / "mgf-bundle"
            stage.mkdir()
            (stage / "README.txt").write_text(
                "mgf-common diagnostic bundle (OP-07).\n"
                f"Generated {stamp}. All text sections are passed through the "
                "Redactor; crash reports are redacted at write time. Still, "
                "review before sharing externally.\n",
                encoding="utf-8",
            )
            for name, producer in sections.items():
                try:
                    text = producer()
                except Exception as e:  # per-section fail-open.
                    text = f"(failed to collect: {e})"
                (stage / name).write_text(_redact(text), encoding="utf-8")

            crash_dir = default_crash_dir()
            if crash_dir.is_dir():
                dest = stage / "crashes"
                dest.mkdir()
                for p in sorted(crash_dir.glob("*.json"))[-_MAX_CRASHES:]:
                    with contextlib.suppress(OSError):
                        shutil.copy2(p, dest / p.name)

            out.parent.mkdir(parents=True, exist_ok=True)
            with tarfile.open(out, "w:gz") as tar:
                tar.add(stage, arcname="mgf-bundle")
    except OSError as e:
        print(f"mgf-fed bundle: failed to write {out}: {e}")
        return EXIT_OPERATION_ERROR

    print(f"wrote diagnostic bundle: {out}")
    return EXIT_SUCCESS
