#!/usr/bin/env python3
"""Render the gateway-scenario scorecard from ``gateway_latest.json`` (#270).

The renderer reads the JSON dropped by ``benchmarks/gateway_benchmark.py``
and emits ``benchmarks/gateway_scorecard.md`` — a public, committed,
deterministic document. Sibling of ``scripts/render_scorecard.py`` (the
existing routing/context scorecard); kept separate so the gating
``make scorecard-check`` CI step is not coupled to gateway-scenario churn.

Determinism contract: given the same input JSON, the rendered markdown is
byte-identical on every run. CI can verify with::

    make benchmark-gateway && make gateway-scorecard && \\
        git diff --quiet benchmarks/gateway_scorecard.md

Usage::

    python scripts/render_gateway_scorecard.py
    python scripts/render_gateway_scorecard.py --check  # exits 1 on drift

Stdlib-only by design, matching ``render_scorecard.py``.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any

_REPO_ROOT = Path(__file__).resolve().parent.parent
_DEFAULT_INPUT = _REPO_ROOT / "benchmarks" / "results" / "gateway_latest.json"
_DEFAULT_OUTPUT = _REPO_ROOT / "benchmarks" / "gateway_scorecard.md"


def _render(payload: dict[str, Any]) -> str:
    """Render the markdown scorecard from the benchmark JSON payload.

    Args:
        payload: The dict produced by ``gateway_benchmark.run_all()``.

    Returns:
        The rendered markdown as a single string.
    """
    aggregate = payload["aggregate"]
    scenarios = payload["scenarios"]
    lines: list[str] = []
    lines.append("# MCP Context Gateway — scenario scorecard")
    lines.append("")
    lines.append(
        "> Renders ``benchmarks/results/gateway_latest.json``. Generated by "
        "``scripts/render_gateway_scorecard.py``. **Do not edit by hand** — "
        "re-run ``make gateway-scorecard`` after the benchmark."
    )
    lines.append("")
    lines.append(
        f"`benchmark_version = {payload['benchmark_version']}` · "
        f"`catalog = {payload['catalog_path']}` · "
        f"`scenarios = {aggregate['n_scenarios']}`"
    )
    lines.append("")
    lines.append("## Headline range")
    lines.append("")
    lines.append(
        f"Across **{aggregate['n_scenarios']} gateway scenarios** the firewall reduction lands "
        f"between **{aggregate['firewall_reduction_min_pct']:.1f}%** "
        f"(small payload — firewall correctly no-ops) and "
        f"**{aggregate['firewall_reduction_max_pct']:.1f}%** "
        f"(largest payload). Raw upstream payload sizes span "
        f"**{aggregate['raw_chars_min']:,}–{aggregate['raw_chars_max']:,} chars**; "
        f"injected summaries land at "
        f"**{aggregate['injected_summary_chars_min']:,}–"
        f"{aggregate['injected_summary_chars_max']:,} chars**."
    )
    lines.append("")
    lines.append(
        "The shape of the range — including the explicit ``0.0%`` floor — is the load-bearing "
        "guarantee that the firewall does not pay any tax on small responses. Anything that "
        "fits under the configured threshold (`firewall_threshold=2000` here) flows through "
        "the prompt verbatim."
    )
    lines.append("")
    lines.append("## Per-scenario table")
    lines.append("")
    lines.append(
        "| scenario | raw chars | summary chars | reduction | artifact | "
        "answer tokens | answer chars |"
    )
    lines.append("|---|---:|---:|---:|:-:|---:|---:|")
    # Sort by raw_result_chars so the table tells a story (tiny → large).
    sorted_rows = sorted(scenarios, key=lambda r: r["raw_result_chars"])
    for row in sorted_rows:
        artifact_glyph = "✓" if row["artifact_created"] else "—"
        lines.append(
            f"| `{row['scenario']}` | {row['raw_result_chars']:,} | "
            f"{row['injected_summary_chars']:,} | "
            f"{row['firewall_reduction_pct']:.1f}% | {artifact_glyph} | "
            f"{row['final_prompt_tokens']:,} | {row['final_prompt_chars']:,} |"
        )
    lines.append("")
    lines.append("## Per-scenario detail")
    lines.append("")
    for row in sorted_rows:
        lines.append(f"### `{row['scenario']}`")
        lines.append("")
        lines.append(f"- **user query**: `{row['user_query']}`")
        lines.append(f"- **routing query**: `{row['routing_query']}`")
        lines.append(f"- **chosen tool**: `{row['chosen_tool']}`")
        lines.append(
            f"- **catalog size**: {row['catalog_tools']} tools; "
            f"**exposed cards**: {row['exposed_choice_cards']}"
        )
        lines.append(
            f"- **firewall**: {row['raw_result_chars']:,} chars → "
            f"{row['injected_summary_chars']:,} chars "
            f"({row['firewall_reduction_pct']:.1f}%); "
            f"artifact persisted: {'yes' if row['artifact_created'] else 'no'}"
        )
        lines.append(
            f"- **answer phase**: {row['final_prompt_tokens']:,} tokens, "
            f"{row['final_prompt_chars']:,} chars"
        )
        lines.append("")
    lines.append("## Reading this honestly")
    lines.append("")
    lines.append(
        "These are deterministic single-call scenarios over the same 60-tool catalog. The "
        "range is wider than the previous “single-call gateway scenario” anecdote in "
        "``docs/benchmarks.md`` (one number, one payload) — *because* the harness now covers "
        "small payloads where the firewall correctly does nothing. Picking the high end "
        "(``bigquery_rowset``, 98.8 %) as a headline is dishonest in isolation; this "
        "table is the honest framing."
    )
    lines.append("")
    return "\n".join(lines) + "\n"


def _load(input_path: Path) -> dict[str, Any]:
    if not input_path.exists():
        sys.stderr.write(
            f"error: gateway benchmark JSON not found at {input_path}. "
            "Run `make benchmark-gateway` first.\n"
        )
        raise SystemExit(2)
    return json.loads(input_path.read_text(encoding="utf-8"))


def main() -> int:
    """Command-line entry point."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--input", type=Path, default=_DEFAULT_INPUT)
    parser.add_argument("--output", type=Path, default=_DEFAULT_OUTPUT)
    parser.add_argument(
        "--check",
        action="store_true",
        help="Exit non-zero if the rendered markdown differs from the committed file.",
    )
    args = parser.parse_args()
    payload = _load(args.input)
    rendered = _render(payload)
    if args.check:
        if not args.output.exists():
            sys.stderr.write(
                f"error: {args.output} missing — regenerate with `make gateway-scorecard`\n"
            )
            return 1
        existing = args.output.read_text(encoding="utf-8")
        if existing != rendered:
            sys.stderr.write(
                f"error: {args.output} is stale relative to {args.input}. "
                "Run `make gateway-scorecard` to regenerate.\n"
            )
            return 1
        return 0
    args.output.write_text(rendered, encoding="utf-8")
    print(f"Wrote {args.output}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
