"""The merged `venue.places` list, its `venue_place_lists` derivation (event_config), and the
organizer's `tourCategories` dial.

The tour consumers are untouched by the merge — this proves the derivation is BYTE-IDENTICAL to the
two hand-authored lists (`landmarks` + `tourSites`), that the category dial routes correctly and
never splits a story route, and that both readers of the lists (the feed overlay and the TTS
preflight) go through the one helper."""
import json
import sys
from pathlib import Path

_F = Path(__file__).resolve()
HERE = _F.parent
for _p in (_F.parents[2] / "live", _F.parents[3] / "ingest", _F.parents[3] / "events"):
    if str(_p) not in sys.path:
        sys.path.insert(0, str(_p))

import trwc_2026  # noqa: E402
from event_config import venue_place_lists  # noqa: E402
from event_meta import overlay_event_config  # noqa: E402
from tour_feed import tour_lines  # noqa: E402

# A synthetic catalogue covering every shape: a landmark, a free excursion site, a two-chapter
# story route (one narrated + one silent shape point), and a second free site in the landmark's
# theme — so a category filter can be shown to keep a route whole and to touch both surfaces.
_PLACES = [
    {"name": "Stadshuset", "lat": 1.0, "lon": 1.0, "line": "close",
     "tier": "landmark", "category": "cityfront"},
    {"name": "Slottet", "lat": 2.0, "lon": 2.0, "line": "palace",
     "tier": "excursion", "category": "royal"},
    {"name": "Vasa-1", "lat": 3.0, "lon": 3.0, "line": "built",
     "tier": "excursion", "category": "maritime", "tour": "vasa"},
    {"name": "Vasa-2", "lat": 4.0, "lon": 4.0,                       # silent shape point (no line)
     "tier": "excursion", "category": "maritime", "tour": "vasa"},
    {"name": "Strandvägen", "lat": 5.0, "lon": 5.0, "line": "quay",
     "tier": "excursion", "category": "cityfront"},
]


def test_merge_is_byte_identical_to_the_two_hand_authored_lists():
    """The whole point of the merge: the derived (landmarks, tourSites) equal the exact lists that
    used to be hand-authored — same entries, same order, same fields — and `tier`/`category` never
    leak onto the wire. Locked against a fixture captured from the pre-merge event."""
    base = json.loads((HERE / "venue_places_baseline.json").read_text(encoding="utf-8"))
    landmarks, tour_sites = venue_place_lists(trwc_2026.EVENT_CONFIG["venue"])
    assert landmarks == base["landmarks"], "the 5 landmarks derive byte-identical"
    assert tour_sites == base["tourSites"], "the 24 tour sites derive byte-identical"
    for entry in landmarks + tour_sites:
        assert "tier" not in entry and "category" not in entry, "authoring metadata stays off the wire"
    # the source really IS merged now — the legacy keys are gone, replaced by one list
    venue = trwc_2026.EVENT_CONFIG["venue"]
    assert "places" in venue and "landmarks" not in venue and "tourSites" not in venue
    assert all("tier" in p and "category" in p for p in venue["places"])


def test_no_filter_derives_by_tier_and_strips_metadata():
    landmarks, sites = venue_place_lists({"places": _PLACES})
    assert [p["name"] for p in landmarks] == ["Stadshuset"]
    assert [p["name"] for p in sites] == ["Slottet", "Vasa-1", "Vasa-2", "Strandvägen"]
    assert landmarks[0] == {"name": "Stadshuset", "lat": 1.0, "lon": 1.0, "line": "close"}
    assert sites[1] == {"name": "Vasa-1", "lat": 3.0, "lon": 3.0, "line": "built", "tour": "vasa"}
    assert sites[2] == {"name": "Vasa-2", "lat": 4.0, "lon": 4.0, "tour": "vasa"}  # stays silent


def test_category_dial_narrows_and_never_splits_a_story_route():
    # one theme, and it touches BOTH surfaces (a cityfront landmark and a cityfront site)
    landmarks, sites = venue_place_lists({"places": _PLACES}, categories=["cityfront"])
    assert [p["name"] for p in landmarks] == ["Stadshuset"]
    assert [p["name"] for p in sites] == ["Strandvägen"]            # royal + maritime dropped
    # a story route survives WHOLE or not at all — both vasa chapters together
    _, sites_m = venue_place_lists({"places": _PLACES}, categories=["maritime"])
    assert [p["name"] for p in sites_m] == ["Vasa-1", "Vasa-2"]
    # an explicit empty list flies nothing; None (the default) flies everything
    assert venue_place_lists({"places": _PLACES}, categories=[]) == ([], [])
    assert venue_place_lists({"places": _PLACES}, categories=None) == venue_place_lists({"places": _PLACES})


def test_legacy_lists_still_resolve_as_a_fallback():
    """An un-migrated event that still carries the old keys keeps working — the existing overlay
    tests depend on this back-compat."""
    lm = [{"name": "X", "lat": 1, "lon": 1, "line": "x"}]
    ts = [{"name": "Y", "lat": 2, "lon": 2, "tour": "t"}]
    assert venue_place_lists({"landmarks": lm, "tourSites": ts}) == (lm, ts)
    assert venue_place_lists({}) == ([], [])


def test_overlay_derives_the_two_lists_and_honours_the_dial():
    out = overlay_event_config({}, {"venue": {"name": "V", "places": _PLACES},
                                    "presentation": {"cruise": {"tourCategories": ["cityfront"]}}})
    assert [p["name"] for p in out["venue"]["landmarks"]] == ["Stadshuset"]
    assert [p["name"] for p in out["venue"]["tourSites"]] == ["Strandvägen"]
    # no dial published = the whole catalogue reaches the meta
    out2 = overlay_event_config({}, {"venue": {"places": _PLACES}})
    assert [p["name"] for p in out2["venue"]["tourSites"]] == ["Slottet", "Vasa-1", "Vasa-2", "Strandvägen"]


def test_tts_preflight_collects_every_line_ignoring_the_dial():
    """The preflight must render every line that CAN fly, whatever a given day narrows the live tour
    to — otherwise a later-enabled theme streams espeak on its first airing."""
    base = json.loads((HERE / "venue_places_baseline.json").read_text(encoding="utf-8"))
    expected = [e["line"] for lst in (base["landmarks"], base["tourSites"]) for e in lst if e.get("line")]
    assert tour_lines(trwc_2026.EVENT_CONFIG) == expected
    assert len(expected) == 25, "5 landmarks + 20 narrated tour sites (4 Lidingö shape points are silent)"


def test_venue_speech_names_derives_the_spoken_name_entries():
    """`venue.speechLang` makes the venue's proper names SPEAK in their own language inside another
    language's narration: (name, bcp47, ipa|None) entries over the venue name, every place name,
    the `speechNames` extras and the `speechIpa` keys — the wire shape a team's `nameLang` rides
    to `to_ssml`. The IPA matters: the pinned Studio voices reject <lang> whole-line and accept
    <phoneme>, so a name without IPA does not actually fire there. Absent `speechLang` = off."""
    from event_config import venue_speech_names

    venue = {"name": "Stockholm", "speechLang": "sv",
             "speechNames": ["Björkö", "Tre Kronor", "Björkö"],     # duplicate on purpose
             "speechIpa": {"Björkö": "ˈbjœrːˌkøː", "Kastellet": "kasˈtɛlːɛt"},
             "places": _PLACES}
    entries = venue_speech_names(venue)
    assert entries[0] == ("Stockholm", "sv", None), "the venue's own name leads"
    names = [n for n, _lg, _ipa in entries]
    assert {"Stadshuset", "Slottet", "Strandvägen", "Björkö", "Tre Kronor"} <= set(names)
    assert all(lg == "sv" for _n, lg, _ipa in entries)
    assert len(names) == len(set(names)), "deduplicated"
    by_name = {n: ipa for n, _lg, ipa in entries}
    assert by_name["Björkö"] == "ˈbjœrːˌkøː", "speechIpa attaches to its name"
    assert by_name["Kastellet"], "a name found only in speechIpa still speaks"
    assert venue_speech_names({"name": "X", "places": _PLACES}) == [], "no speechLang - off"
    assert venue_speech_names(None) == []
    # the real event: place names AND the in-line extras all ride as Swedish, each WITH its IPA
    real = {n: (lg, ipa) for n, lg, ipa in venue_speech_names(trwc_2026.EVENT_CONFIG["venue"])}
    for name in ("Stockholm", "Skansen", "Beckholmen", "Vasa", "Björkö", "Södermalm"):
        lg, ipa = real[name]
        assert lg == "sv" and ipa, f"{name} must speak Swedish by IPA (Studio rejects <lang>)"
