#!/usr/bin/env python3
"""Deterministic FIFA 17 base-card seed generator.

Reads the committed, evidenced FIFA 17 data and produces:

  1. the FIFA 17 card-definition *identity catalog*
     (openfut-adapter-fifa17 data): CardDefinitionId -> {asset_id, version=0}.
     `resource_id == asset_id` for version 0.
  2. (optional) semantic base CardDefinitions for OpenFUT Core content.

Base cards only (version 0). No specials — pool.json proves only base identities.
CardDefinitionId is opaque + deterministic: `fifa17_<asset_id>` (per-game
namespace). Core treats it as opaque; the FIFA meaning stays in the catalog.

Sources (all committed, non-sensitive):
  fifa17-recon/data/pool.json     real player assets (id, rating, pos, nation,
                                   league, team, attrs[pace,sho,pas,dri,def,phy], gk)
  fifa17-recon/data/roster.json   names (id -> first/last/common)
  fifa17-recon/data/tables/{leagues,nations,teams}.json   entity id -> name

Determinism: output is sorted by asset_id and JSON-stable; running twice on the
same source yields identical bytes. `--check` regenerates and diffs against the
committed files, exiting non-zero on drift.
"""
import argparse
import hashlib
import json
import os
import sys

GENERATOR_VERSION = 1
SCHEMA_VERSION = 1
GAME = "fifa17"
RECON = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "fifa17-recon")


def _load(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def _table_map(path, id_key, name_key):
    doc = _load(path)
    return {row[id_key]: row[name_key] for row in doc["rows"]}


def tier(overall):
    return "gold" if overall >= 75 else "silver" if overall >= 65 else "bronze"


def build(recon):
    pool = _load(os.path.join(recon, "data", "pool.json"))
    roster = {r["id"]: r for r in _load(os.path.join(recon, "data", "roster.json"))}
    leagues = _table_map(os.path.join(recon, "data", "tables", "leagues.json"), "leagueid", "leaguename")
    nations = _table_map(os.path.join(recon, "data", "tables", "nations.json"), "nationid", "nationname")
    teams = _table_map(os.path.join(recon, "data", "tables", "teams.json"), "teamid", "teamname")

    catalog_cards = {}
    definitions = []
    skipped = []
    seen_assets = set()

    for p in sorted(pool, key=lambda x: x["id"]):
        asset = p["id"]
        if asset in seen_assets:
            skipped.append((asset, "duplicate_asset_id"))
            continue
        seen_assets.add(asset)
        if asset > 0x00FFFFFF:
            skipped.append((asset, "asset_id_exceeds_24_bits"))
            continue
        card_id = "fifa17_%d" % asset
        # Catalog entry needs only the asset id (identity); always emitted.
        catalog_cards[card_id] = {"asset_id": asset, "version": 0}

        # Semantic definition needs a name + resolvable entities; skip+report if not.
        r = roster.get(asset)
        name = None
        if r:
            common = (r.get("common") or "").strip()
            name = common or (" ".join(x for x in [r.get("first", ""), r.get("last", "")] if x)).strip()
        if not name:
            skipped.append((asset, "no_roster_name"))
            continue
        nation = nations.get(p["nation"])
        league = leagues.get(p["league"])
        club = teams.get(p["team"])
        if not nation or not league or not club:
            missing = [k for k, v in (("nation", nation), ("league", league), ("club", club)) if not v]
            skipped.append((asset, "unresolved_entity:" + ",".join(missing)))
            continue
        a = p["attrs"]  # [pace, shooting, passing, dribbling, defending, physical]
        definitions.append({
            "id": card_id,
            "name": name,
            "overall": p["rating"],
            "position": p["pos"],
            "nation": nation,
            "league": league,
            "club": club,
            "pace": a[0], "shooting": a[1], "passing": a[2],
            "dribbling": a[3], "defending": a[4], "physical": a[5],
            "rarity": tier(p["rating"]),
            "image_path": None,
        })

    catalog = {
        "schema_version": SCHEMA_VERSION,
        "game": GAME,
        "_provenance": {
            "generator": "scripts/seed_fifa17_cards.py",
            "generator_version": GENERATOR_VERSION,
            "sources": ["fifa17-recon/data/pool.json"],
            "base_cards_only": True,
        },
        "cards": {cid: catalog_cards[cid] for cid in sorted(catalog_cards)},
    }
    definitions.sort(key=lambda d: d["id"])
    return catalog, definitions, skipped, len(pool)

# ── Curated development subset ───────────────────────────────────────────────
# The dev pack is a small, deterministic slice of the full base-card set, chosen
# to exercise the retail /club UI (quality/position/nation/league/team filters +
# pagination) without loading all ~17.5K definitions. Selection is reproducible
# from the sorted source; no asset is hand-picked.
MY_SQUAD_PAGE = 11  # evidence: the client requests count=11 per page
DEV_PRIMARY_GOLD_CAP = 18  # > one page → quality=gold and league=<primary> span 2+ pages
DEV_SECONDARY_GOLD = 4
DEV_SILVER = 6
DEV_BRONZE = 4


def select_dev(definitions):
    """Deterministically curate a dev slice from the full definition set.

    Criteria (all reproducible): the two leagues with the most Gold cards are the
    primary/secondary leagues (tie-break by name); take the primary league's top
    Gold cards (capped), guaranteeing >1 page for both `quality=gold` and
    `league=<primary>`; ensure a GK is present; add the secondary league's top
    Golds (multiple leagues); add the top Silvers and Bronzes (quality spread).
    """
    by_league = {}
    for d in definitions:
        by_league.setdefault(d["league"], []).append(d)
    golds = lambda ds: [d for d in ds if d["overall"] >= 75]
    ranked = sorted(by_league, key=lambda L: (-len(golds(by_league[L])), L))
    primary, secondary = ranked[0], ranked[1]
    key = lambda d: (-d["overall"], d["id"])
    chosen = {}
    for d in sorted(golds(by_league[primary]), key=key)[:DEV_PRIMARY_GOLD_CAP]:
        chosen[d["id"]] = d
    if not any(d["position"] == "GK" for d in chosen.values()):
        gks = sorted((d for d in definitions if d["position"] == "GK" and d["overall"] >= 75), key=key)
        if gks:
            chosen[gks[0]["id"]] = gks[0]
    for d in sorted(golds(by_league[secondary]), key=key)[:DEV_SECONDARY_GOLD]:
        chosen[d["id"]] = d
    for d in sorted((d for d in definitions if 65 <= d["overall"] < 75), key=key)[:DEV_SILVER]:
        chosen[d["id"]] = d
    for d in sorted((d for d in definitions if d["overall"] < 65), key=key)[:DEV_BRONZE]:
        chosen[d["id"]] = d
    return sorted(chosen.values(), key=lambda d: d["id"]), primary


def dev_coverage(dev):
    """Coverage stats + the self-check gate the dev pack must pass."""
    q = lambda lo, hi=256: sum(1 for d in dev if lo <= d["overall"] < hi)
    positions = sorted({d["position"] for d in dev})
    teams = {}
    for d in dev:
        teams[d["club"]] = teams.get(d["club"], 0) + 1
    cov = {
        "count": len(dev),
        "gold": q(75),
        "silver": q(65, 75),
        "bronze": q(0, 65),
        "positions": positions,
        "has_gk": "GK" in positions,
        "gold_st": sum(1 for d in dev if d["position"] == "ST" and d["overall"] >= 75),
        "leagues": sorted({d["league"] for d in dev}),
        "nations": sorted({d["nation"] for d in dev}),
        "max_same_team": max(teams.values()) if teams else 0,
        "pages_gold": (q(75) + MY_SQUAD_PAGE - 1) // MY_SQUAD_PAGE,
    }
    # The pack is only useful if it can exercise the filters + pagination.
    checks = {
        "gold_over_one_page": cov["gold"] > MY_SQUAD_PAGE,
        "has_gk": cov["has_gk"],
        "has_st": any(d["position"] == "ST" for d in dev),
        "multi_league": len(cov["leagues"]) >= 2,
        "multi_nation": len(cov["nations"]) >= 2,
        "has_silver": cov["silver"] >= 1,
        "has_bronze": cov["bronze"] >= 1,
        "same_team_group": cov["max_same_team"] >= 2,
    }
    cov["checks"] = checks
    cov["ok"] = all(checks.values())
    return cov


def dumps(obj):
    return json.dumps(obj, indent=2, ensure_ascii=False, sort_keys=False) + "\n"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--recon", default=os.path.normpath(RECON))
    ap.add_argument("--catalog-out", required=True)
    ap.add_argument("--defs-out", help="optional semantic CardDefinition JSON (full Core content)")
    ap.add_argument("--dev-out", help="curated game-scoped dev CardDefinition pack")
    ap.add_argument("--check", action="store_true", help="fail if committed output would change")
    args = ap.parse_args()

    catalog, definitions, skipped, pool_rows = build(args.recon)
    catalog_bytes = dumps(catalog)
    defs_bytes = dumps(definitions)
    dev, primary = select_dev(definitions)
    dev_bytes = dumps(dev)
    cov = dev_coverage(dev)
    cov["primary_league"] = primary

    reasons = {}
    for _, why in skipped:
        reasons[why.split(":")[0]] = reasons.get(why.split(":")[0], 0) + 1
    report = {
        "pool_rows": pool_rows,
        "catalog_entries": len(catalog["cards"]),
        "definitions_seeded": len(definitions),
        "skipped_definitions": len(skipped),
        "skip_reasons": reasons,
        "catalog_sha256": hashlib.sha256(catalog_bytes.encode()).hexdigest()[:16],
        "dev_coverage": cov,
    }
    print(json.dumps(report, indent=2), file=sys.stderr)

    # The dev pack is worthless if it cannot exercise the filters + pagination.
    if args.dev_out and not cov["ok"]:
        print("DEV PACK FAILED COVERAGE GATE: %s" % cov["checks"], file=sys.stderr)
        sys.exit(2)
    # Every dev definition must have a catalog (FIFA render) identity.
    missing = [d["id"] for d in dev if d["id"] not in catalog["cards"]]
    if missing:
        print("DEV DEFS WITHOUT CATALOG IDENTITY: %s" % missing, file=sys.stderr)
        sys.exit(2)

    outputs = [(args.catalog_out, catalog_bytes)]
    if args.defs_out:
        outputs.append((args.defs_out, defs_bytes))
    if args.dev_out:
        outputs.append((args.dev_out, dev_bytes))

    if args.check:
        drift = False
        for path, want in outputs:
            have = open(path, encoding="utf-8").read() if os.path.exists(path) else None
            if have != want:
                print("DRIFT: %s is stale (regenerate)" % path, file=sys.stderr)
                drift = True
        sys.exit(1 if drift else 0)

    for path, data in outputs:
        os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            f.write(data)
    print("wrote catalog=%d dev=%d cards" % (len(catalog["cards"]), len(dev)), file=sys.stderr)


if __name__ == "__main__":
    main()
