"""Stage 6 — enrich: full game + version metadata into games.json.

The seed data for the future web frontend, keyed by bgg_id (or
"bgg_id:version_id" when a version is settled). Cheap by design:
- ids are batched (~20 per /thing call) and sorted so batch cache keys
  stay stable across runs;
- already-enriched keys are skipped entirely (no request, no cache read)
  unless --refresh, which bypasses the cache read because ranks and
  ratings drift over time;
- the chosen version's details come from matches.csv's stored version
  candidates — no extra API calls.
Degrades gracefully without BGG_API_TOKEN: whatever is cached enriches,
the rest waits for the token.
"""

from __future__ import annotations

import json

import typer

from bggpipe.bgg_client import (
    BGGAuthError,
    BGGClient,
    BGGQueueTimeout,
    client_for,
)
from bggpipe.config import Config
from bggpipe.fsio import atomic_write_text
from bggpipe.models import is_confident_version, is_recognized
from bggpipe.resolve import read_matches

BATCH_SIZE = 20


def _version_info(row: dict) -> dict | None:
    if not is_confident_version(row):
        return None
    version_id = int(row["version_id"])
    for cand in json.loads(row["version_candidates_json"] or "[]"):
        if cand.get("version_id") == version_id:
            return {
                "version_id": version_id,
                "name": cand.get("name", ""),
                "year": cand.get("year"),
                "publishers": cand.get("publishers") or [],
                "languages": cand.get("languages") or [],
            }
    # candidates were pruned (e.g. manual review) — keep what the row knows
    return {
        "version_id": version_id,
        "name": row["version_name"],
        "year": None,
        "publishers": [],
        "languages": [],
    }


def run_enrich(
    cfg: Config, *, refresh: bool = False, client: BGGClient | None = None
) -> dict:
    rows = read_matches(cfg.matches_path)
    if not rows:
        typer.echo(f"{cfg.matches_path} is empty — run `bggpipe resolve` first.")
        raise typer.Exit(code=1)

    targets: list[tuple[str, int, dict | None]] = []
    for row in rows:
        if not is_recognized(row):
            continue
        version = _version_info(row)
        key = f"{row['bgg_id']}:{version['version_id']}" if version else row["bgg_id"]
        targets.append((key, int(row["bgg_id"]), version))

    games_path = cfg.games_path
    games: dict = json.loads(games_path.read_text()) if games_path.exists() else {}

    need = sorted({bgg_id for key, bgg_id, _ in targets if refresh or key not in games})
    client = client or client_for(cfg)

    fetched: dict[int, dict] = {}
    blocked = False
    for start in range(0, len(need), BATCH_SIZE):
        batch = need[start : start + BATCH_SIZE]
        try:
            for thing in client.things_full(batch, refresh=refresh):
                fetched[thing["bgg_id"]] = thing
        except (BGGAuthError, BGGQueueTimeout):
            blocked = True
            break

    updated = 0
    for key, bgg_id, version in targets:
        if bgg_id in fetched:
            games[key] = {**fetched[bgg_id], "version": version}
            updated += 1

    # prune keys no current target claims: a row whose version was approved
    # after a bare-key run (or was later rejected) must not leave an orphan
    # entry in the frontend seed data. Only safe when nothing was blocked —
    # a token-less run knows too little to declare anything stale.
    if not blocked:
        current = {key for key, _, _ in targets}
        stale = [k for k in games if k not in current]
        for k in stale:
            del games[k]
        if stale:
            typer.echo(
                f"  pruned {len(stale)} stale entr{'y' if len(stale) == 1 else 'ies'}"
            )

    games_path.parent.mkdir(parents=True, exist_ok=True)
    atomic_write_text(
        games_path, json.dumps(games, indent=2, ensure_ascii=False) + "\n"
    )

    typer.echo(
        f"games.json: {len(games)} entr{'y' if len(games) == 1 else 'ies'} "
        f"({updated} added/refreshed this run; "
        f"{len(targets) - updated} already present or waiting)."
    )
    if blocked:
        remaining = [i for i in need if i not in fetched]
        typer.echo(
            f"\n{len(remaining)} game(s) are waiting on the BGG API "
            "(set BGG_API_TOKEN and re-run enrich — everything fetched "
            "so far is saved)."
        )
    return games
