"""PC-05: memory footprint baseline for a representative consumer
session — bootstrap + 10 k log records + 10 crash reports.

A silent +5 MB regression on a long-running consumer daemon
compounds over days and surfaces as OOM in production with no
obvious code change to point at. The per-record allocation
budget (LG-12) doesn't catch this: it gates one call, not the
steady-state working set.

Strategy: spawn a fresh Python subprocess so the parent test
runner's RSS doesn't pollute the measurement. The subprocess
runs the canonical consumer shape end-to-end and prints
``RUSAGE_SELF.ru_maxrss``. The parent test asserts the value is
below the budget.

Measured 2026-05-17 on Linux 6.17 / Python 3.12 (the maintainer's
laptop): ~38 MB after bootstrap + 10 000 log records + 10
crash reports. Budget 80 MB ≈ 2.1x the local measurement —
generous-budget philosophy per RA-11 + LG-12 catches the
order-of-magnitude regression (a new 10 MB lookup table, a
sliding-window "recent records" cache) without false-positive
on CI hardware variance.

Marker: ``perf`` — runs in the nightly sweep, not on every PR.

References:
- ``docs/inprogress/PERFORMANCE_AND_CAPACITY.md`` PC-05 work unit
- ``docs/standards/TESTING.md`` §8.4 (perf marker convention)
- ``docs/standards/TESTING.md`` §8.5 (generous-budget rationale)
"""

from __future__ import annotations

import subprocess
import sys
import textwrap

import pytest

pytestmark = pytest.mark.perf

# 80 MB. Local: ~38 MB. Cushion ≈ 2.1x for CI hardware noise,
# Python-version drift (3.11 vs 3.12 RSS differs ~5 %), and
# the eventual landing of an OTel-enabled extra. A breach
# means a new compile-time-resident object hit the baseline —
# likely a redactor pattern table, a settings cache, or a
# logging-handler-side buffer. Investigate the diff with
# `python -X importtime` + `tracemalloc.snapshot`.
_RSS_BUDGET_MB = 80.0


_BASELINE_SCRIPT = textwrap.dedent(
    """
    import gc
    import logging
    import resource

    import mgf.common  # noqa: F401 — load the import tree

    from mgf.common._lifecycle import bootstrap
    from mgf.common.observability.crash_reporter import report_now

    with bootstrap(app_name='mem-baseline', app_version='0.0.0'):
        log = logging.getLogger('mgf.test')
        for _ in range(10_000):
            # Match the per-record shape consumers actually emit:
            # one message + one small `extra` dict.
            log.info('baseline record', extra={'iter': 'mem-baseline'})

        for _ in range(10):
            try:
                raise RuntimeError('synthetic crash for PC-05 baseline')
            except RuntimeError as exc:
                report_now(exc, source='pc-05-baseline')

    # Force a GC sweep before sampling so short-lived allocations
    # don't inflate the reading. ru_maxrss is the PEAK during the
    # process lifetime — gc.collect() before the print sets a
    # quieter wall against a future check; the peak itself doesn't
    # go down.
    gc.collect()
    print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
    """
).strip()


def _measure_peak_rss_mb() -> float:
    """Spawn the baseline script in a fresh subprocess; return peak
    RSS in MB. On Linux, ``ru_maxrss`` is in KB."""
    result = subprocess.run(
        [sys.executable, "-c", _BASELINE_SCRIPT],
        check=True,
        capture_output=True,
        text=True,
        timeout=60,
    )
    # The script prints other log records to stderr (JSON-formatted);
    # the last stdout line is the RSS number. Take the last whitespace-
    # separated token to be robust against any spurious stdout output.
    lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
    assert lines, f"No RSS line in subprocess stdout. stderr={result.stderr!r}"
    rss_kb = int(lines[-1])
    return rss_kb / 1024.0


def test_baseline_rss_under_budget() -> None:
    """Peak RSS after bootstrap + 10k logs + 10 crash reports stays
    under :data:`_RSS_BUDGET_MB`."""
    rss_mb = _measure_peak_rss_mb()
    assert rss_mb < _RSS_BUDGET_MB, (
        f"PC-05 budget breach: peak RSS {rss_mb:.1f} MB exceeds the "
        f"{_RSS_BUDGET_MB:.0f} MB budget. The most common cause is a "
        f"new compile-time-resident object (a redactor pattern "
        f"table, a settings cache, a logging-handler buffer that "
        f"keeps records). Diagnose with `python -X importtime "
        f"-c 'import mgf.common'` to see what loads, then "
        f"`tracemalloc.snapshot()` around the bootstrap call."
    )
