"""Enrich-stage tests: full-metadata parsing plus run_enrich orchestration
(batched, cache-keyed, idempotent, token-degrading). No network."""

from __future__ import annotations

import json

import httpx

from bggpipe.bgg_client import BGGClient, cache_key
from bggpipe.config import Config
from bggpipe.models import parse_things_full
from bggpipe.resolve import write_matches

FULL_THING_XML = """<items>
  <item type="boardgame" id="266192">
    <thumbnail>https://cf.example/thumb.jpg</thumbnail>
    <image>https://cf.example/full.jpg</image>
    <name type="primary" sortindex="1" value="Wingspan"/>
    <name type="alternate" value="Fl&#252;gelschlag"/>
    <description>A bird-collection engine builder.</description>
    <yearpublished value="2019"/>
    <minplayers value="1"/>
    <maxplayers value="5"/>
    <poll name="suggested_numplayers">
      <results numplayers="1">
        <result value="Best" numvotes="10"/>
        <result value="Recommended" numvotes="50"/>
        <result value="Not Recommended" numvotes="20"/>
      </results>
      <results numplayers="3">
        <result value="Best" numvotes="120"/>
        <result value="Recommended" numvotes="40"/>
        <result value="Not Recommended" numvotes="2"/>
      </results>
      <results numplayers="4">
        <result value="Best" numvotes="90"/>
        <result value="Recommended" numvotes="80"/>
        <result value="Not Recommended" numvotes="5"/>
      </results>
    </poll>
    <playingtime value="70"/>
    <minplaytime value="40"/>
    <maxplaytime value="70"/>
    <minage value="10"/>
    <link type="boardgamecategory" id="1" value="Animals"/>
    <link type="boardgamecategory" id="2" value="Card Game"/>
    <link type="boardgamemechanic" id="3" value="Engine Building"/>
    <link type="boardgamedesigner" id="4" value="Elizabeth Hargrave"/>
    <link type="boardgameartist" id="5" value="Natalia Rojas"/>
    <link type="boardgamepublisher" id="6" value="Stonemaier Games"/>
    <statistics page="1"><ratings>
      <average value="8.05"/>
      <averageweight value="2.45"/>
      <owned value="123456"/>
      <ranks><rank type="subtype" id="1" name="boardgame" value="30"/></ranks>
    </ratings></statistics>
  </item>
</items>"""


def test_parse_things_full_extracts_everything():
    (game,) = parse_things_full(FULL_THING_XML)
    assert game["name"] == "Wingspan"
    assert game["year"] == 2019
    assert game["description"] == "A bird-collection engine builder."
    assert (game["min_players"], game["max_players"]) == (1, 5)
    # 3 is Best-majority; 4 loses to Recommended+NotRec? no — Best(90) >= Rec(80)
    # and > NotRec(5), so 4 qualifies too; 1 does not (Best < Recommended)
    assert game["best_player_counts"] == ["3", "4"]
    assert game["designers"] == ["Elizabeth Hargrave"]
    assert game["artists"] == ["Natalia Rojas"]
    assert game["publishers"] == ["Stonemaier Games"]
    assert game["categories"] == ["Animals", "Card Game"]
    assert game["mechanics"] == ["Engine Building"]
    assert game["rating"] == 8.05
    assert game["weight"] == 2.45
    assert game["rank"] == 30
    assert game["playtime"] == 70
    assert game["min_age"] == 10
    assert game["image"].endswith("full.jpg")


CATAN_MINIMAL = (
    '<items><item type="boardgame" id="13">'
    '<name type="primary" value="CATAN"/><yearpublished value="1995"/>'
    "</item></items>"
)


def _matches_rows():
    base = {
        "year": "",
        "type": "boardgame",
        "candidates_json": "[]",
        "source_photos": "x.jpg",
        "version_id": "",
        "version_name": "",
        "version_status": "version_unknown",
        "version_candidates_json": "[]",
    }
    return [
        {
            **base,
            "title_raw": "Catan",
            "bgg_id": "13",
            "bgg_name": "CATAN",
            "match_status": "auto",
        },
        {
            **base,
            "title_raw": "Wingspan",
            "bgg_id": "266192",
            "bgg_name": "Wingspan",
            "match_status": "auto",
            "version_id": "465063",
            "version_name": "English first edition",
            "version_status": "version_auto",
            "version_candidates_json": json.dumps(
                [
                    {
                        "version_id": 465063,
                        "name": "English first edition",
                        "year": 2019,
                        "publishers": ["Stonemaier Games"],
                        "languages": ["English"],
                        "score": 5,
                    }
                ]
            ),
        },
        {
            **base,
            "title_raw": "Junk",
            "bgg_id": "",
            "bgg_name": "",
            "match_status": "rejected",
        },
    ]


def _no_network(request: httpx.Request) -> httpx.Response:
    raise AssertionError(f"test hit the network: {request.url}")


def _cfg_with_matches(tmp_path):
    cfg = Config(data_dir=tmp_path / "data")
    write_matches(cfg.matches_path, _matches_rows())
    return cfg


def _seed_batch_fixture(cache_dir):
    """The batch cache entry run_enrich will ask for: sorted ids 13,266192."""
    cache_dir.mkdir(parents=True, exist_ok=True)
    combined = FULL_THING_XML.replace("<items>", "<items>" + CATAN_MINIMAL[7:-8], 1)
    key = cache_key("thing", {"id": "13,266192", "stats": "1"})
    (cache_dir / key).write_text(combined)


def test_run_enrich_writes_games_json_with_versions(tmp_path):
    from bggpipe.enrich import run_enrich

    cfg = _cfg_with_matches(tmp_path)
    cache = tmp_path / "cache"
    _seed_batch_fixture(cache)
    client = BGGClient(cache_dir=cache, transport=httpx.MockTransport(_no_network))

    games = run_enrich(cfg, client=client)
    assert set(games) == {"13", "266192:465063"}
    wingspan = games["266192:465063"]
    assert wingspan["designers"] == ["Elizabeth Hargrave"]
    assert wingspan["version"]["name"] == "English first edition"
    assert wingspan["version"]["publishers"] == ["Stonemaier Games"]
    assert games["13"]["version"] is None
    saved = json.loads((cfg.data_dir / "games.json").read_text())
    assert saved == games


def test_run_enrich_skips_already_enriched(tmp_path):
    from bggpipe.enrich import run_enrich

    cfg = _cfg_with_matches(tmp_path)
    cache = tmp_path / "cache"
    _seed_batch_fixture(cache)
    client = BGGClient(cache_dir=cache, transport=httpx.MockTransport(_no_network))
    run_enrich(cfg, client=client)

    # empty cache + network-refusing transport: passes only if enrich
    # doesn't need to fetch anything at all
    bare = BGGClient(
        cache_dir=tmp_path / "empty", transport=httpx.MockTransport(_no_network)
    )
    games = run_enrich(cfg, client=bare)
    assert set(games) == {"13", "266192:465063"}


def test_refresh_bypasses_cache_read(tmp_path):
    from bggpipe.enrich import run_enrich

    cfg = _cfg_with_matches(tmp_path)
    cache = tmp_path / "cache"
    _seed_batch_fixture(cache)
    requests = []

    def handler(request):
        requests.append(str(request.url))
        return httpx.Response(200, text=FULL_THING_XML.replace("8.05", "7.5"))

    client = BGGClient(cache_dir=cache, transport=httpx.MockTransport(handler))
    games = run_enrich(cfg, refresh=True, client=client)
    assert len(requests) == 1  # cache read skipped, live fetch happened
    assert games["266192:465063"]["rating"] == 7.5


def test_enrich_degrades_without_token(tmp_path, capsys):
    from bggpipe.enrich import run_enrich

    cfg = _cfg_with_matches(tmp_path)
    client = BGGClient(
        cache_dir=tmp_path / "empty",
        transport=httpx.MockTransport(
            lambda req: httpx.Response(401, text="Unauthorized")
        ),
    )
    games = run_enrich(cfg, client=client)
    assert games == {}
    assert "waiting on the BGG API" in capsys.readouterr().out
    assert json.loads((cfg.data_dir / "games.json").read_text()) == {}
