#!/usr/bin/env python3
"""Replay every committed fixture and reproduce its recorded rewards.

A fixture is a claim: "this model, on this seed, scored this." The claim is
only worth anything if it can be re-derived from the moves it records. This
does that in Python; `verify.ts` does the same thing in the browser, live, in
front of the visitor.

Exits non-zero if any fixture's rewards cannot be reproduced from its own turns.
"""

from __future__ import annotations

import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent / "wordle_five"))

from wordle_five import solver as S  # noqa: E402
from wordle_five.engine import Game  # noqa: E402
from wordle_five.protocol import parse_guess  # noqa: E402
from wordle_five.reward import Episode, score  # noqa: E402

TRACES = Path(__file__).parent.parent / "public" / "traces" / "wordle"
TOLERANCE = 1e-9


def replay(fixture: dict) -> dict[str, float]:
    game = Game(seed=fixture["seed"])
    if game.answer != fixture["answer"]:
        raise AssertionError(
            f"seed {fixture['seed']} gives {game.answer!r}, fixture claims {fixture['answer']!r}"
        )
    for turn in fixture["turns"]:
        guess = parse_guess(turn["reply"])
        if guess is None:
            game.rejected += 1
            continue
        game.play(guess)

    episode = Episode(
        answer=game.answer,
        guesses=[g for g, _ in game.history],
        patterns=[p for _, p in game.history],
        rejected=game.rejected,
        reference_depth=S.reference_depth(game.answer),
    )
    return score(episode)


def main() -> int:
    fixtures = sorted(TRACES.glob("*.json"))
    fixtures = [f for f in fixtures if f.name != "manifest.json"]
    if not fixtures:
        print("no fixtures found — nothing to verify")
        return 0

    failures = 0
    for path in fixtures:
        data = json.loads(path.read_text())
        recomputed = replay(data)
        recorded = data["rewards"]
        deltas = {k: abs(recomputed[k] - recorded[k]) for k in recorded}
        worst = max(deltas.values())
        status = "ok" if worst <= TOLERANCE else "MISMATCH"
        if worst > TOLERANCE:
            failures += 1
            culprit = max(deltas, key=deltas.get)
            print(f"{path.name:>20}  {status}  worst delta {worst:.3g} on '{culprit}'")
        else:
            print(f"{path.name:>20}  {status}  delta {worst:.1e}")

    print(f"\n{len(fixtures)} fixtures, {failures} mismatched")
    return 1 if failures else 0


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