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

One command assembles everything a triager needs into one file, so an operator
filing a bug ships *one* artifact instead of hand-collecting version + config +
health + crashes. Composes the existing diagnostic surfaces — ``version``,
``explain`` (resolved config, already SecretStr-redacted), ``doctor``, the
rotating log tail, 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 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

if TYPE_CHECKING:
    import argparse
    from collections.abc import Callable

# Caps so the bundle stays small.
_MAX_CRASHES = 20
_MAX_LOG_LINES = 500


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


def _log_tail() -> str:
    """Last N lines of the rotating mgf-cloud log (set up by bootstrap())."""
    from mgf.common.observability import default_log_path

    path = default_log_path()
    if not path.is_file():
        return f"(no log file at {path})"
    lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
    return "\n".join(lines[-_MAX_LOG_LINES:]) or "(empty log)"


def run_bundle(args: argparse.Namespace) -> int:
    """Write a redacted diagnostic tarball; return 0 on success, 1 on I/O error."""
    from mgf.common.observability import Redactor, default_crash_dir

    from mgf.cloud.cli._doctor import run_doctor
    from mgf.cloud.cli._explain import run_explain
    from mgf.cloud.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, sort_keys=True),
        "explain.txt": lambda: _capture(run_explain, args),
        "doctor.txt": lambda: _capture(run_doctor, args),
        "logs.txt": _log_tail,
    }

    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-cloud-bundle-{stamp}.tar.gz"
    )

    try:
        with tempfile.TemporaryDirectory() as d:
            stage = Path(d) / "mgf-cloud-bundle"
            stage.mkdir()
            (stage / "README.txt").write_text(
                "mgf-cloud diagnostic bundle (OP-07).\n"
                f"Generated {stamp}. All text sections are passed through the "
                "Redactor and credentials are SecretStr-redacted at the source; "
                "still, review before sharing externally.\n",
                encoding="utf-8",
            )
            for name, producer in sections.items():
                try:
                    text = producer()
                except Exception as exc:  # per-section fail-open.
                    text = f"(failed to collect: {exc})"
                (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-cloud-bundle")
    except OSError as exc:
        print(f"mgf-cloud bundle: failed to write {out}: {exc}")
        return 1

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


__all__ = ["run_bundle"]
