"""Tests for NMEA sentence parser."""

import pytest
from datetime import datetime, timezone

from fishinfo.exceptions import ValidationError
from fishinfo.nmea.parser import NMEAParser
from fishinfo.nmea.sentences import (
    DBTSentence,
    DPTSentence,
    FixQuality,
    GGASentence,
    HDGSentence,
    HDTSentence,
    MTWSentence,
    MWVSentence,
    RMCSentence,
    VTGSentence,
)


class TestNMEAParser:
    """Tests for NMEAParser class."""

    def setup_method(self) -> None:
        """Set up test fixtures."""
        self.parser = NMEAParser()

    def test_parse_gga_basic(self) -> None:
        """Test parsing basic GGA sentence."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        result = self.parser.parse(sentence)

        assert isinstance(result, GGASentence)
        assert result.sentence_type == "GGA"
        assert result.latitude == pytest.approx(48.1173, rel=1e-4)
        assert result.longitude == pytest.approx(11.5167, rel=1e-4)
        assert result.fix_quality == FixQuality.GPS_FIX
        assert result.satellites == 8
        assert result.hdop == 0.9
        assert result.altitude == 545.4
        assert result.geoid_separation == 47.0

    def test_parse_gga_no_fix(self) -> None:
        """Test parsing GGA sentence with no fix."""
        sentence = "$GPGGA,123519,,,,,0,00,,,M,,M,,*4E"
        result = self.parser.parse(sentence)

        assert isinstance(result, GGASentence)
        assert result.latitude is None
        assert result.longitude is None
        assert result.fix_quality == FixQuality.INVALID
        assert result.satellites == 0

    def test_parse_rmc_basic(self) -> None:
        """Test parsing basic RMC sentence."""
        sentence = "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
        result = self.parser.parse(sentence)

        assert isinstance(result, RMCSentence)
        assert result.sentence_type == "RMC"
        assert result.status == "A"
        assert result.latitude == pytest.approx(48.1173, rel=1e-4)
        assert result.longitude == pytest.approx(11.5167, rel=1e-4)
        assert result.speed_knots == 22.4
        assert result.course == 84.4
        assert result.magnetic_variation == -3.1  # West is negative

    def test_parse_rmc_void(self) -> None:
        """Test parsing RMC sentence with void status."""
        sentence = "$GPRMC,123519,V,,,,,,,230394,,*25"
        result = self.parser.parse(sentence)

        assert isinstance(result, RMCSentence)
        assert result.status == "V"
        assert result.latitude is None
        assert result.longitude is None

    def test_parse_vtg_basic(self) -> None:
        """Test parsing basic VTG sentence."""
        sentence = "$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48"
        result = self.parser.parse(sentence)

        assert isinstance(result, VTGSentence)
        assert result.sentence_type == "VTG"
        assert result.course_true == 54.7
        assert result.course_magnetic == 34.4
        assert result.speed_knots == 5.5
        assert result.speed_kmh == 10.2

    def test_parse_hdt_basic(self) -> None:
        """Test parsing basic HDT sentence."""
        sentence = "$HEHDT,274.07,T*03"
        result = self.parser.parse(sentence)

        assert isinstance(result, HDTSentence)
        assert result.sentence_type == "HDT"
        assert result.heading == 274.07

    def test_parse_hdg_basic(self) -> None:
        """Test parsing basic HDG sentence."""
        sentence = "$HEHDG,101.1,,,7.1,W*3C"
        result = self.parser.parse(sentence)

        assert isinstance(result, HDGSentence)
        assert result.sentence_type == "HDG"
        assert result.heading == 101.1
        assert result.deviation is None
        assert result.variation == -7.1  # West is negative

    def test_parse_hdg_with_deviation(self) -> None:
        """Test parsing HDG sentence with deviation."""
        sentence = "$HEHDG,98.3,2.5,E,5.0,W*12"
        result = self.parser.parse(sentence)

        assert isinstance(result, HDGSentence)
        assert result.heading == 98.3
        assert result.deviation == 2.5  # East is positive
        assert result.variation == -5.0  # West is negative

    def test_parse_dbt_basic(self) -> None:
        """Test parsing basic DBT sentence."""
        sentence = "$SDDBT,12.3,f,3.7,M,2.0,F*31"
        result = self.parser.parse(sentence)

        assert isinstance(result, DBTSentence)
        assert result.sentence_type == "DBT"
        assert result.depth_feet == 12.3
        assert result.depth_meters == 3.7
        assert result.depth_fathoms == 2.0

    def test_parse_dpt_basic(self) -> None:
        """Test parsing basic DPT sentence."""
        sentence = "$SDDPT,3.6,0.5,*5B"
        result = self.parser.parse(sentence)

        assert isinstance(result, DPTSentence)
        assert result.sentence_type == "DPT"
        assert result.depth == 3.6
        assert result.offset == 0.5
        assert result.max_range is None

    def test_parse_mwv_relative(self) -> None:
        """Test parsing MWV sentence with relative wind."""
        sentence = "$WIMWV,045.0,R,12.5,N,A*39"
        result = self.parser.parse(sentence)

        assert isinstance(result, MWVSentence)
        assert result.sentence_type == "MWV"
        assert result.wind_angle == 45.0
        assert result.reference == "R"
        assert result.wind_speed == 12.5
        assert result.speed_units == "N"
        assert result.status == "A"

    def test_parse_mwv_true(self) -> None:
        """Test parsing MWV sentence with true wind."""
        sentence = "$WIMWV,220.0,T,15.2,M,A*21"
        result = self.parser.parse(sentence)

        assert isinstance(result, MWVSentence)
        assert result.reference == "T"
        assert result.wind_speed == 15.2
        assert result.speed_units == "M"

    def test_parse_mtw_basic(self) -> None:
        """Test parsing basic MTW sentence."""
        sentence = "$YXMTW,15.5,C*1F"
        result = self.parser.parse(sentence)

        assert isinstance(result, MTWSentence)
        assert result.sentence_type == "MTW"
        assert result.temperature == 15.5
        assert result.units == "C"


class TestNMEAParserValidation:
    """Tests for NMEA parser validation and error handling."""

    def setup_method(self) -> None:
        """Set up test fixtures."""
        self.parser = NMEAParser()

    def test_parse_invalid_prefix(self) -> None:
        """Test parsing sentence with invalid prefix."""
        sentence = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        with pytest.raises(ValidationError, match="must start with"):
            self.parser.parse(sentence)

    def test_parse_invalid_checksum(self) -> None:
        """Test parsing sentence with invalid checksum."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*99"
        with pytest.raises(ValidationError, match="Invalid checksum"):
            self.parser.parse(sentence)

    def test_parse_invalid_checksum_disabled(self) -> None:
        """Test parsing sentence with checksum validation disabled."""
        parser = NMEAParser(validate_checksums=False)
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*99"
        result = parser.parse(sentence)
        assert isinstance(result, GGASentence)

    def test_parse_unsupported_sentence_type(self) -> None:
        """Test parsing unsupported sentence type."""
        sentence = "$GPXYZ,123,456,789*12"
        with pytest.raises(ValidationError, match="Unsupported sentence type"):
            self.parser.parse(sentence)

    def test_parse_empty_sentence(self) -> None:
        """Test parsing empty sentence."""
        sentence = "$*00"
        with pytest.raises(ValidationError, match="Empty NMEA sentence"):
            self.parser.parse(sentence)

    def test_parse_insufficient_fields_gga(self) -> None:
        """Test parsing GGA with insufficient fields."""
        sentence = "$GPGGA,123519*43"
        with pytest.raises(ValidationError, match="insufficient fields"):
            self.parser.parse(sentence)

    def test_parse_insufficient_fields_rmc(self) -> None:
        """Test parsing RMC with insufficient fields."""
        sentence = "$GPRMC,123519,A*1D"
        with pytest.raises(ValidationError, match="insufficient fields"):
            self.parser.parse(sentence)


class TestCoordinateParsing:
    """Tests for latitude/longitude parsing."""

    def setup_method(self) -> None:
        """Set up test fixtures."""
        self.parser = NMEAParser()

    def test_parse_latitude_north(self) -> None:
        """Test parsing northern latitude."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        result = self.parser.parse(sentence)
        assert result.latitude == pytest.approx(48.1173, rel=1e-4)

    def test_parse_latitude_south(self) -> None:
        """Test parsing southern latitude."""
        sentence = "$GPGGA,123519,3345.678,S,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*4C"
        result = self.parser.parse(sentence)
        assert result.latitude == pytest.approx(-33.7613, rel=1e-4)

    def test_parse_longitude_east(self) -> None:
        """Test parsing eastern longitude."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        result = self.parser.parse(sentence)
        assert result.longitude == pytest.approx(11.5167, rel=1e-4)

    def test_parse_longitude_west(self) -> None:
        """Test parsing western longitude."""
        sentence = "$GPGGA,123519,4807.038,N,12245.123,W,1,08,0.9,545.4,M,47.0,M,,*4F"
        result = self.parser.parse(sentence)
        assert result.longitude == pytest.approx(-122.7520, rel=1e-4)

    def test_parse_equator(self) -> None:
        """Test parsing equator position."""
        sentence = "$GPGGA,123519,0000.000,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*48"
        result = self.parser.parse(sentence)
        assert result.latitude == pytest.approx(0.0, abs=1e-6)

    def test_parse_prime_meridian(self) -> None:
        """Test parsing prime meridian position."""
        sentence = "$GPGGA,123519,4807.038,N,00000.000,E,1,08,0.9,545.4,M,47.0,M,,*4B"
        result = self.parser.parse(sentence)
        assert result.longitude == pytest.approx(0.0, abs=1e-6)


class TestTimeParsing:
    """Tests for time/date parsing."""

    def setup_method(self) -> None:
        """Set up test fixtures."""
        self.parser = NMEAParser()

    def test_parse_time_basic(self) -> None:
        """Test parsing basic time."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        result = self.parser.parse(sentence)
        assert result.time is not None
        assert result.time.hour == 12
        assert result.time.minute == 35
        assert result.time.second == 19

    def test_parse_time_with_fractional_seconds(self) -> None:
        """Test parsing time with fractional seconds."""
        sentence = "$GPGGA,123519.123,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*77"
        result = self.parser.parse(sentence)
        assert result.time is not None
        assert result.time.hour == 12
        assert result.time.minute == 35
        assert result.time.second == 19
        assert result.time.microsecond == 123000

    def test_parse_rmc_with_date(self) -> None:
        """Test parsing RMC with date."""
        sentence = "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
        result = self.parser.parse(sentence)
        assert result.time is not None
        assert result.time.year == 1994
        assert result.time.month == 3
        assert result.time.day == 23


class TestEdgeCases:
    """Tests for edge cases and boundary conditions."""

    def setup_method(self) -> None:
        """Set up test fixtures."""
        self.parser = NMEAParser()

    def test_parse_empty_fields(self) -> None:
        """Test parsing sentence with empty fields."""
        sentence = "$GPGGA,123519,,,,,,08,0.9,545.4,M,47.0,M,,*63"
        result = self.parser.parse(sentence)
        assert result.latitude is None
        assert result.longitude is None

    def test_parse_whitespace(self) -> None:
        """Test parsing sentence with leading/trailing whitespace."""
        sentence = "  $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47  \n"
        result = self.parser.parse(sentence)
        assert isinstance(result, GGASentence)

    def test_parse_multiple_talker_ids(self) -> None:
        """Test parsing sentences from different talkers."""
        sentences = [
            "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47",  # GPS
            "$GLGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*50",  # GLONASS
            "$GAGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*44",  # Galileo
        ]
        for sentence in sentences:
            result = self.parser.parse(sentence)
            assert isinstance(result, GGASentence)
