"""Bootstrap-side runtime-snapshot writer (private).

This is the *write* half of the ``doctor --live`` feature: :func:`bootstrap`
calls :func:`emit_runtime_snapshot` after successful wiring when
``MGF_DOCTOR_RUNTIME_PATH`` is set. It lives at the package root (not under
``cli/``) so the runtime library never imports the CLI command surface — the
``cli`` package holds only the *reader* half (``doctor --live`` /
``--redaction``), which ships with the federation tooling, not the library.
"""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any

#: Env var that, when set, makes :func:`mgf.common.bootstrap` write a
#: runtime-state snapshot to disk after successful wiring.
RUNTIME_SNAPSHOT_ENV_VAR = "MGF_DOCTOR_RUNTIME_PATH"

# Settings fields known to carry secret values. Each entry is a
# ``(section, field)`` pair under the top-level ``settings`` key in
# the snapshot. Add new entries when ``MgfSettings`` grows a new
# secret-bearing leaf — the SH-02 fail-safe is "explicit exclusion
# of known fields," not "redact-everything," because the snapshot's
# legitimate content (app names, paths, log levels) shouldn't be
# scrubbed by accident.
_SNAPSHOT_SECRET_FIELDS: tuple[tuple[str, str], ...] = (
    ("crash", "sentry_dsn"),
    ("crash", "http_url"),
    ("crash", "otlp_endpoint"),
)


def emit_runtime_snapshot(snapshot: dict[str, Any]) -> None:
    """Write ``snapshot`` to the path in ``MGF_DOCTOR_RUNTIME_PATH``.

    Called by :func:`mgf.common.bootstrap` after successful wiring
    when the env var is set. No-op when unset (off by default).

    SH-02: the snapshot is a diagnostic, not a backup — its
    settings dump can carry secret-bearing fields (``crash.sentry_dsn``,
    ``crash.http_url``, ``crash.otlp_endpoint``) that an operator
    must NOT see in plain text. Two hardenings:

    1. Known secret-bearing settings fields are replaced with
       ``"<set>"`` / ``"<unset>"`` presence indicators before
       serialisation. The diagnostic value ("is the Sentry DSN
       configured?") is preserved; the value itself is dropped.
    2. The file is written via
       :func:`mgf.common._io.atomic_write` at mode ``0o600`` (was:
       a plain ``Path.write_text`` at the process umask, typically
       world-readable on a shared host).

    Failures are logged but never raised — snapshot emission must
    not block bootstrap.
    """
    path_str = os.environ.get(RUNTIME_SNAPSHOT_ENV_VAR)
    if not path_str:
        return
    path = Path(path_str)
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        # SH-02: scrub known secret-bearing settings fields BEFORE
        # serialisation. Operates on a shallow copy so we don't
        # mutate the caller's dict.
        scrubbed = _scrub_snapshot_secrets(snapshot)
        # SH-02: atomic_write at mode 0o600 — the snapshot is
        # diagnostic-only; restrict to owner read/write. fsync_dir
        # is False because a lost rename on power failure is
        # acceptable for a transient diagnostic file.
        from mgf.common._io import atomic_write

        atomic_write(
            path,
            json.dumps(scrubbed, indent=2, sort_keys=True, default=str),
            mode=0o600,
            fsync_dir=False,
        )
    except OSError as exc:
        # Log but don't block bootstrap.
        import logging

        logging.getLogger("mgf.common").warning(
            "doctor: cannot write runtime snapshot to %s: %s",
            path,
            exc,
        )


def _scrub_snapshot_secrets(snapshot: dict[str, Any]) -> dict[str, Any]:
    """Return a copy of ``snapshot`` with known secret-bearing
    settings fields replaced by presence indicators (``"<set>"`` /
    ``"<unset>"``). SH-02.

    Defensive against malformed snapshots — every ``isinstance``
    check protects against a future ``MgfSettings`` refactor that
    changes a nested dict to a string or vice versa; if the shape
    is unexpected, we leave the field untouched rather than crash
    the diagnostic write.
    """
    # Shallow-copy the top level; deep-copy only the nodes we
    # mutate. The caller's dict is left untouched.
    scrubbed: dict[str, Any] = dict(snapshot)
    settings_dump = scrubbed.get("settings")
    if not isinstance(settings_dump, dict):
        return scrubbed
    new_settings = dict(settings_dump)
    for section_name, field_name in _SNAPSHOT_SECRET_FIELDS:
        section = new_settings.get(section_name)
        if not isinstance(section, dict):
            continue
        if field_name not in section:
            continue
        new_section = dict(section)
        new_section[field_name] = "<set>" if section[field_name] else "<unset>"
        new_settings[section_name] = new_section
    scrubbed["settings"] = new_settings
    return scrubbed


__all__ = [
    "RUNTIME_SNAPSHOT_ENV_VAR",
    "emit_runtime_snapshot",
]
