"""Tests for AIS message decoder."""

import pytest

from fishinfo.exceptions import ValidationError
from fishinfo.nmea.ais import (
    AISDecoder,
    AISPositionReport,
    AISStaticData,
    NavigationStatus,
)


class TestAISDecoder:
    """Tests for AISDecoder class."""

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

    def test_decode_position_report_type1(self) -> None:
        """Test decoding AIS position report type 1."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert result.message_type == 1
        assert result.mmsi == 265513580
        assert result.latitude is not None
        assert result.longitude is not None
        assert result.speed_over_ground is not None
        assert result.course_over_ground is not None

    def test_decode_position_report_type2(self) -> None:
        """Test decoding AIS position report type 2."""
        sentence = "!AIVDM,1,1,,A,23aEOK0P00PlT>HMGN4?vhR00<06,0*7C"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert result.message_type == 2

    def test_decode_position_report_type3(self) -> None:
        """Test decoding AIS position report type 3."""
        sentence = "!AIVDM,1,1,,B,33L=LN0P00PlT>HMGN4?vhR00<09,0*4E"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert result.message_type == 3

    def test_decode_navigation_status(self) -> None:
        """Test decoding navigation status from position report."""
        # Type 1 with navigation status = 0 (under way using engine)
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert isinstance(result.navigation_status, NavigationStatus)

    def test_decode_static_data_type5(self) -> None:
        """Test decoding AIS static and voyage data type 5."""
        # Multi-part message
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        result = self.decoder.decode(sentence1)
        assert result is None  # First fragment

        result = self.decoder.decode(sentence2)
        assert isinstance(result, AISStaticData)
        assert result.message_type == 5
        assert result.mmsi > 0
        assert result.vessel_name != ""
        assert result.call_sign != ""

    def test_decode_single_part_message(self) -> None:
        """Test decoding single-part AIS message."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert result is not None
        assert isinstance(result, AISPositionReport)

    def test_decode_invalid_prefix(self) -> None:
        """Test decoding message 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.decoder.decode(sentence)

    def test_decode_invalid_checksum(self) -> None:
        """Test decoding message with invalid checksum."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*99"
        with pytest.raises(ValidationError, match="Invalid checksum"):
            self.decoder.decode(sentence)

    def test_decode_checksum_validation_disabled(self) -> None:
        """Test decoding with checksum validation disabled."""
        decoder = AISDecoder(validate_checksums=False)
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*99"
        result = decoder.decode(sentence)
        assert result is not None

    def test_decode_unsupported_message_type(self) -> None:
        """Test decoding unsupported message type."""
        # Type 6 message (not supported)
        sentence = "!AIVDM,1,1,,A,6B?n;be:cbapalgc;i6?Ow4,2*4A"
        with pytest.raises(ValidationError, match="Unsupported AIS message type"):
            self.decoder.decode(sentence)

    def test_decode_payload_too_short(self) -> None:
        """Test decoding message with payload too short."""
        sentence = "!AIVDM,1,1,,B,1,0*5D"
        with pytest.raises(ValidationError, match="too short"):
            self.decoder.decode(sentence)

    def test_decode_aivdo_sentence(self) -> None:
        """Test decoding AIVDO sentence (own vessel)."""
        sentence = "!AIVDO,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*7B"
        result = self.decoder.decode(sentence)

        assert result is not None
        assert isinstance(result, AISPositionReport)


class TestAISPositionReport:
    """Tests for AIS position report decoding details."""

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

    def test_position_accuracy_flag(self) -> None:
        """Test position accuracy flag extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert isinstance(result.position_accuracy, bool)

    def test_speed_over_ground_valid(self) -> None:
        """Test valid speed over ground extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        if result.speed_over_ground is not None:
            assert result.speed_over_ground >= 0
            assert result.speed_over_ground <= 102.3  # Max valid speed

    def test_speed_over_ground_not_available(self) -> None:
        """Test speed over ground when not available."""
        # Message with SOG = 1023 (not available)
        # We'd need a specific test message for this
        pass

    def test_course_over_ground_valid(self) -> None:
        """Test valid course over ground extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        if result.course_over_ground is not None:
            assert 0 <= result.course_over_ground <= 360

    def test_true_heading_valid(self) -> None:
        """Test valid true heading extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        if result.true_heading is not None:
            assert 0 <= result.true_heading <= 359

    def test_timestamp_field(self) -> None:
        """Test UTC timestamp extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        assert 0 <= result.timestamp <= 59  # UTC seconds

    def test_rate_of_turn(self) -> None:
        """Test rate of turn extraction."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        result = self.decoder.decode(sentence)

        assert isinstance(result, AISPositionReport)
        if result.rate_of_turn is not None:
            # ROT can be positive or negative
            assert -708 <= result.rate_of_turn <= 708


class TestAISStaticData:
    """Tests for AIS static data decoding details."""

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

    def test_static_data_vessel_name(self) -> None:
        """Test vessel name extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        assert isinstance(result.vessel_name, str)
        assert len(result.vessel_name) > 0

    def test_static_data_call_sign(self) -> None:
        """Test call sign extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        assert isinstance(result.call_sign, str)

    def test_static_data_dimensions(self) -> None:
        """Test vessel dimension extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        assert result.dimension_to_bow >= 0
        assert result.dimension_to_stern >= 0
        assert result.dimension_to_port >= 0
        assert result.dimension_to_starboard >= 0

    def test_static_data_ship_type(self) -> None:
        """Test ship type extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        assert 0 <= result.ship_type <= 255

    def test_static_data_imo_number(self) -> None:
        """Test IMO number extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        # IMO number can be None if not available
        if result.imo_number is not None:
            assert result.imo_number > 0

    def test_static_data_draught(self) -> None:
        """Test draught extraction from static data."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        self.decoder.decode(sentence1)
        result = self.decoder.decode(sentence2)

        assert isinstance(result, AISStaticData)
        # Draught can be None if not available
        if result.draught is not None:
            assert result.draught >= 0
            assert result.draught <= 25.5  # Max valid draught


class TestMultiPartMessages:
    """Tests for multi-part AIS message handling."""

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

    def test_multipart_incomplete_returns_none(self) -> None:
        """Test that incomplete multi-part message returns None."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        result = self.decoder.decode(sentence1)
        assert result is None

    def test_multipart_complete_returns_message(self) -> None:
        """Test that complete multi-part message returns decoded message."""
        sentence1 = "!AIVDM,2,1,3,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7C"
        sentence2 = "!AIVDM,2,2,3,B,00000000000,2*20"

        result1 = self.decoder.decode(sentence1)
        assert result1 is None

        result2 = self.decoder.decode(sentence2)
        assert result2 is not None
        assert isinstance(result2, AISStaticData)

    def test_multipart_different_message_ids(self) -> None:
        """Test handling multiple multi-part messages with different IDs."""
        # Message ID 1
        msg1_part1 = "!AIVDM,2,1,1,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7E"
        # Message ID 2
        msg2_part1 = "!AIVDM,2,1,2,B,55?MbV02>H97PH97mR000<>0@T4@D0000000000015@T@10000000000000,0*7D"
        # Complete message ID 1
        msg1_part2 = "!AIVDM,2,2,1,B,00000000000,2*23"

        result1 = self.decoder.decode(msg1_part1)
        assert result1 is None

        result2 = self.decoder.decode(msg2_part1)
        assert result2 is None

        result3 = self.decoder.decode(msg1_part2)
        assert result3 is not None


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

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

    def test_decode_with_whitespace(self) -> None:
        """Test decoding message with leading/trailing whitespace."""
        sentence = "  !AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66  \n"
        result = self.decoder.decode(sentence)
        assert result is not None

    def test_decode_invalid_format(self) -> None:
        """Test decoding message with invalid format."""
        sentence = "!AIVDM,1*66"
        with pytest.raises(ValidationError, match="Invalid AIS sentence format"):
            self.decoder.decode(sentence)

    def test_position_at_invalid_coordinates(self) -> None:
        """Test position report with invalid coordinate markers."""
        # A message where coordinates are marked as not available
        # Coordinates not available: lon = 0x6791AC0, lat = 0x3412140
        # This requires a specially crafted message
        pass
