"""THE CONDITIONS ARE ON AIR WHETHER OR NOT A RACE IS.

The wind is one of the four things the streamed picture always carries, and it was the only one that
came and went. The waiting presentation built its meta from scratch with no wind in it, published
positions only, and the producer switched the wind poller OFF whenever nothing was racing — so the
whole upper-right block vanished for the first minutes of a race day and every gap between heats,
which is most of it.

It also credited "Weather: Open-Meteo.com" the entire time, hard-coded in the page, whatever the wind
actually was. That is the licence obligation discharged when the wind IS Open-Meteo's, and a false
attribution on an SMHI deployment, behind a MarkSetBot anemometer, or in a simulator run.
"""
import sys
from pathlib import Path

import pytest

_LIVE = Path(__file__).resolve().parents[2] / "live"
_INGEST = Path(__file__).resolve().parents[3] / "ingest"
sys.path.insert(0, str(_LIVE))
sys.path.insert(0, str(_INGEST))

import serve_v3_live as S  # noqa: E402
from live_feed_server import LiveFeedServer  # noqa: E402

SRC = Path(S.__file__).read_text(encoding="utf-8")


# --- the reading names its supplier ---------------------------------------------------------------

def test_a_station_reading_records_which_provider_supplied_it():
    """`source` used to collapse every model provider to the generic `"station"`, so a stored reading
    could not say whether it was Open-Meteo's or SMHI's — while the picture credited Open-Meteo
    regardless."""
    from wind_feed import WindFeed

    written = []
    feed = WindFeed(lambda: (59.32, 18.04), written.append, course_id="c", provider="smhi",
                    fetch=lambda _u: {"timeSeries": [
                        {"data": {"wind_from_direction": 210, "wind_speed": 5.0}}]})
    feed.poll_once(now_ms=1)
    assert written and written[0].source == "smhi", \
        "the picture credits whoever the reading names, so a reading that names nobody credits nobody"


def test_the_provider_can_still_be_overridden_explicitly():
    """`source=` remains the caller's to set — a rig replaying a provider's data under another name."""
    from wind_feed import WindFeed
    assert WindFeed(lambda: None, lambda _r: None, course_id="c", source="manual").source == "manual"


def test_the_feed_meta_carries_the_source():
    """…all the way to the renderer, which is where the credit is written."""
    from attribution import WindReading
    from broadcast_feed import resolve_static

    class _Store:
        def wind_readings_for(self, _c):
            return [WindReading("c", 0, 200.0, 9.0, "marksetbot")]

    out = resolve_static(_Store(), "i", [], meta={"trackStartUtcMs": 0, "gunUtcMs": 1000},
                         course={}, team_config={}, course_id="c")
    assert out["meta"]["windSource"] == "marksetbot"
    assert out["meta"]["windFromDeg"] == 200.0


# --- the waiting picture carries the wind ---------------------------------------------------------

def test_the_waiting_picture_resolves_its_own_wind():
    """`_waiting_picture` is a closure, so this reads the module source — what matters is that the
    wind is resolved for the course ON SCREEN and merged into the meta, rather than left out (which
    took the block off the picture) or carried over from the race that has just finished (which would
    show a wind that is no longer being measured anywhere)."""
    assert "def _waiting_picture" in SRC, "the waiting picture moved — this guard needs rewriting"
    _at = SRC.index("def _waiting_picture")
    body = SRC[_at:SRC.index("    from course import resolve_course as _resolve_course", _at)]
    assert "wind_for(instance)" in body, "the waiting meta must carry the wind for its own course"
    assert '"wind_for"' in body, "…and keep it live per tick while the picture is up"


def test_the_wind_poller_follows_the_waiting_picture():
    """Switching the feed off between races is what made the conditions go dark. It must not go back
    to the FINISHED race's course either — that files real readings against a race that is over."""
    begin = SRC[SRC.index("    def _begin_waiting()"):SRC.index("    waiting, waiting_key = _begin_waiting()")]
    assert 'on_air_course["id"] = w.meta.get("courseId")' in begin, \
        "the live wind must follow the course the waiting picture is showing"


def test_the_waiting_presentation_publishes_the_wind_as_it_changes():
    """On the DELTA, not a fresh `init`: an init per wind change re-headers the feed and rebinds the
    renderer, and the wind is exactly the sort of thing that changes on its own all day."""
    published = []

    class _Srv:
        def publish_init(self, *a, **kw):
            published.append(("init", kw))

        def publish_delta(self, sec, standings, events, positions, ais=None, **kw):  # noqa: ANN001
            published.append(("delta", kw.get("wind")))

    winds = [{"windFromDeg": 200.0, "windKts": 9.0},
             {"windFromDeg": 200.0, "windKts": 9.0},
             {"windFromDeg": 215.0, "windKts": 11.0}]
    w = S._Waiting(_Srv(), meta={"trackStartUtcMs": 0}, course={}, teams=[],
                   fetch_positions=lambda: {}, wind_for=lambda: winds.pop(0),
                   on_log=lambda _m: None)
    for _ in range(3):
        w.tick()
    deltas = [p[1] for p in published if p[0] == "delta"]
    assert deltas[0] == {"fromDeg": 200.0, "kts": 9.0}, "the first reading goes out"
    assert deltas[1] is None, "an unchanged wind costs nothing on the wire"
    assert deltas[2] == {"fromDeg": 215.0, "kts": 11.0}, "…and a veer is published when it happens"
    assert sum(1 for p in published if p[0] == "init") == 1, "one init: the renderer is never rebound"


# --- a bad reading is never pointed at ------------------------------------------------------------

def test_an_unusable_reading_yields_no_wind_rather_than_a_wrong_one():
    """A wind that is wrong is worse than a wind that is missing: the block says "—", the arrow holds.
    A `None` cast to 0 would be a north-easterly the renderer points at with full confidence."""
    assert S._wind_meta(None, 9.0, "smhi") == {}
    assert S._wind_meta(200.0, None, "smhi") == {}
    assert S._wind_meta(float("nan"), 9.0, "smhi") == {}
    assert S._wind_meta(200.0, -1.0, "smhi") == {}, "a negative wind speed is not a wind"
    assert S._wind_meta(200.0, 9.0, None) == {"windFromDeg": 200.0, "windKts": 9.0}, \
        "…but an unknown SOURCE only costs the credit, not the reading"
    assert S._wind_meta(200.0, 9.0, "smhi", gust_kts=14.0) == {
        "windFromDeg": 200.0, "windKts": 9.0, "windSource": "smhi",
        "windDirectionSource": "smhi", "windSpeedSource": "smhi", "windGustKts": 14.0}
    assert S._wind_wire_frame(S._wind_meta(200.0, 9.0, "smhi", gust_kts=14.0)) == {
        "fromDeg": 200.0, "kts": 9.0, "gustKts": 14.0, "source": "smhi",
        "directionSource": "smhi", "speedSource": "smhi"}


def test_live_meteo_uses_course_direction_and_retains_speed_provider():
    course = {"startLine": {"port": {"lat": 59.30, "lon": 17.9999},
                            "starboard": {"lat": 59.30, "lon": 18.0001}},
              "waypoints": [{"name": "M1", "lat": 59.31, "lon": 18.0}]}
    corrected = S._course_corrected_wind(
        S._wind_meta(225.0, 13.0, "open-meteo"), course)
    assert corrected["windFromDeg"] == pytest.approx(0.0, abs=0.1)
    assert corrected["windKts"] == 13.0
    assert corrected["windDirectionSource"] == "course-geometry"
    assert corrected["windSpeedSource"] == "open-meteo"


def test_the_waiting_picture_still_goes_on_air_without_a_wind():
    """No wind anywhere is not a reason to publish nothing — the venue and the fleet still are."""
    class _Idle:
        def poll(self):
            return []

        def closed(self, _i):
            return False

        def name_of(self, i):
            return i

        def priorities(self):
            return {}                      # no race is flagged "follow me whole" in this test

    fleet = {"meta": {"title": "Waiting", "instanceId": "r1", "groupId": "g"},
             "course": {"name": "c"},
             "teams": [{"teamId": "hull-1", "name": "1", "boats": []}],
             "fetch_positions": lambda: {}}
    srv = LiveFeedServer(host="127.0.0.1", port=0)
    try:
        S.broadcast_following(None, srv, races=_Idle(),
                              presentation_for=lambda *_: ({}, {}, [], None),
                              waiting_for=lambda: fleet, max_polls=2, interval_s=0,
                              sleep=lambda _s: None, on_log=lambda _m: None)
        assert any(h.startswith(b"event: init") for h in srv._history)
    finally:
        srv.server_close()
