"""games_city: the city kit in OhMyGames builds."""

import base64
import hashlib
import io
import tarfile
from types import SimpleNamespace as N

import pytest

import games_city as gc


def _card(title, pitch="", stage="none", view="third", lane="custom", setting=""):
    return N(title=title, pitch=pitch, genre="", core_loop="", first_version="", mood=[],
             world=N(view=view, setting=setting, look=""), lane=lane, stage=stage)


def _tar(files: dict[str, bytes]) -> bytes:
    buf = io.BytesIO()
    with tarfile.open(fileobj=buf, mode="w:gz") as tf:
        for name, data in files.items():
            info = tarfile.TarInfo(name)
            info.size = len(data)
            tf.addfile(info, io.BytesIO(data))
    return buf.getvalue()


@pytest.fixture
def assets(tmp_path):
    (tmp_path / "citykit.tar.gz").write_bytes(_tar({"CITY.md": b"city", "game.mjs": b"export {}"}))
    return tmp_path


def _members(att):
    data = base64.b64decode(att["url"].split(",", 1)[1])
    with tarfile.open(fileobj=io.BytesIO(data)) as tf:
        return {m.name: tf.extractfile(m).read() for m in tf.getmembers() if m.isfile()}


@pytest.mark.parametrize("title,pitch", [
    ("Grand Heat", "An open world crime game in the style of GTA"),
    ("Downtown Hustle", "Run deals through the downtown blocks"),
    ("Gangland", "Gangsters fight over the city streets"),
    ("Mafia Mile", "A mafia family rules the streets of a big city"),
    ("Taxi Night", "Drive a taxi driver's route through the metropolis"),
    ("Skyline", "Chase rivals between the skyscrapers of New York"),
    ("Harbour Lights", "Hong Kong nights, neon signs and rain"),
    ("Heist Town", "A heist across the city blocks"),
    ("Cop Chase", "A police chase through the city streets"),
])
def test_city_words_name_a_city(title, pitch):
    assert gc.city_kind(_card(title, pitch)) == "city"


@pytest.mark.parametrize("title,pitch", [
    ("Medieval Market", "A medieval town with a tavern and a blacksmith"),
    ("Fishing Village", "A fishing village in Brittany, GTA style"),
    ("Racing Week", "Drift a rally car around a racing circuit in a downtown"),
    ("Grand Prix", "A grand prix racing track through the city"),
    ("City Builder", "Lay out roads and zone a city-builder tycoon"),
    ("Chess City", "A board game of city chess"),
    ("Space Gangs", "Gangsters in space, with aliens and asteroids"),
    ("Side Heist", "A 2d side-scroll heist in the city"),
    ("Driver", "Drive a car across the countryside"),
    ("Cop Quiz", "A police quiz with questions and answers"),
    ("Kingdom Hearts Fan", "a kingdom hearts style adventure"),
])
def test_false_positives_are_not_cities(title, pitch):
    assert gc.city_kind(_card(title, pitch)) is None


def test_medieval_or_breton_is_never_a_city():
    # the word city alone does not decide: town_kind wins (a walled medieval city stays a town)
    assert gc.city_kind(_card("Walled", "a walled medieval city with knights and gangsters")) is None


def test_prompt_counts_as_words():
    assert gc.city_kind(_card("Quiet Game", "a walk"), "a gta style open city") == "city"


@pytest.mark.parametrize("stage,view,lane,fits", [
    ("none", "third", "custom", True),
    ("room", "first", "custom", False),
    ("none", "top", "custom", True),
    ("board", "third", "custom", False),
    ("none", "side", "custom", False),
    ("none", "third", "quick", False),
])
def test_fits_follows_the_town_rule(stage, view, lane, fits):
    assert gc.city_fits(_card("GTA", "an open city", stage=stage, view=view, lane=lane)) is fits


def test_board_and_side_view_get_no_city(assets):
    assert gc.city_attachments(_card("GTA", "an open city", view="side"), 0, "", assets) is None
    assert gc.city_attachments(_card("GTA", "an open city", stage="board"), 0, "", assets) is None


def test_city_attachment_is_code_only_and_numbered(assets):
    atts, contract, meta = gc.city_attachments(_card("GTA", "an open city"), 2, "", assets)
    assert [a["filename"] for a in atts] == ["citykit.tar.gz"]
    assert "att3" in contract and "att4" not in contract
    assert _members(atts[0])["CITY.md"] == b"city"
    assert meta["kind"] == "city" and meta["sha"] == hashlib.sha256((assets / "citykit.tar.gz").read_bytes()).hexdigest()


def test_contract_names_the_files_and_the_laws(assets):
    _, contract, _ = gc.city_attachments(_card("GTA", "an open city"), 0, "", assets)
    for needle in ("src/lib/city", "CITY.md", "createCity", "src/lib/city/game.mjs", "spawn", "colliders", "carPoses()",
                   "Never draw buildings or roads"):
        assert needle in contract, needle
    assert "Player vehicles come from the Garage" not in contract


def test_garage_attached_brings_the_cars_from_the_garage(assets):
    _, contract, _ = gc.city_attachments(_card("GTA", "an open city"), 0, "", assets, garage=True)
    assert "vehicles come from the Garage kit" in contract and "createVehicle" in contract


def test_missing_tar_means_no_city(tmp_path):
    assert gc.city_attachments(_card("GTA", "an open city"), 0, "", tmp_path) is None
