"""Tests for arautopilot.core.nmea_data -- Sprint 5."""

from __future__ import annotations

import math
import time
from unittest.mock import patch

import pytest

from arautopilot.core.nmea_data import CogSogData, HeadingData, NmeaNavData, XteData


class TestCogSogData:
    def test_initial_state_invalid(self):
        d = CogSogData()
        assert not d.is_valid
        assert math.isnan(d.cog_deg)
        assert math.isnan(d.sog_kn)

    def test_update_marks_valid(self):
        d = CogSogData()
        d.update(270.0, 8.5)
        assert d.is_valid
        assert d.cog_deg == pytest.approx(270.0)
        assert d.sog_kn == pytest.approx(8.5)

    def test_cog_wraps_360(self):
        d = CogSogData()
        d.update(370.0, 5.0)
        assert d.cog_deg == pytest.approx(10.0)

    def test_cog_zero_stays_zero(self):
        d = CogSogData()
        d.update(0.0, 5.0)
        assert d.cog_deg == pytest.approx(0.0)

    def test_stale_after_max_age(self):
        d = CogSogData(max_age_s=1.0)
        d.update(90.0, 6.0)
        assert d.is_valid
        with patch("arautopilot.core.nmea_data.time") as mock_time:
            mock_time.monotonic.return_value = d.timestamp + 2.0
            assert not d.is_valid

    def test_age_ms(self):
        d = CogSogData()
        d.update(180.0, 10.0)
        assert d.age_ms >= 0


class TestXteData:
    def test_initial_invalid(self):
        d = XteData()
        assert not d.is_valid
        assert math.isnan(d.xte_m)

    def test_update_positive_xte(self):
        """Positive XTE = vessel to starboard of track."""
        d = XteData()
        d.update(xte_m=5.0, dtw_m=200.0, waypoint_name="WP01")
        assert d.is_valid
        assert d.xte_m == pytest.approx(5.0)
        assert d.dtw_m == pytest.approx(200.0)
        assert d.waypoint_name == "WP01"

    def test_update_negative_xte(self):
        d = XteData()
        d.update(xte_m=-3.5)
        assert d.xte_m == pytest.approx(-3.5)

    def test_stale_after_max_age(self):
        d = XteData(max_age_s=2.0)
        d.update(1.0)
        with patch("arautopilot.core.nmea_data.time") as mock_time:
            mock_time.monotonic.return_value = d.timestamp + 3.0
            assert not d.is_valid

    def test_update_without_optional_fields(self):
        d = XteData()
        d.update(xte_m=0.0)
        assert d.is_valid
        assert math.isnan(d.dtw_m)
        assert d.waypoint_name == ""


class TestNmeaNavData:
    def test_aggregate_has_all_fields(self):
        nav = NmeaNavData()
        assert isinstance(nav.heading, HeadingData)
        assert isinstance(nav.cog_sog, CogSogData)
        assert isinstance(nav.xte, XteData)

    def test_independent_updates(self):
        nav = NmeaNavData()
        nav.cog_sog.update(45.0, 7.0)
        nav.xte.update(2.5)
        assert nav.cog_sog.is_valid
        assert nav.xte.is_valid
        assert not nav.heading.is_valid
