"""Tests for NMEA checksum validation."""

import pytest

from fishinfo.nmea.checksum import (
    add_checksum,
    calculate_checksum,
    extract_checksum,
    validate_checksum,
)


class TestCalculateChecksum:
    """Tests for calculate_checksum function."""

    def test_calculate_checksum_basic(self) -> None:
        """Test basic checksum calculation."""
        sentence = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,"
        result = calculate_checksum(sentence)
        assert result == "47"

    def test_calculate_checksum_rmc(self) -> None:
        """Test checksum calculation for RMC sentence."""
        sentence = "GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W"
        result = calculate_checksum(sentence)
        assert result == "6A"

    def test_calculate_checksum_empty(self) -> None:
        """Test checksum calculation for empty string."""
        result = calculate_checksum("")
        assert result == "00"

    def test_calculate_checksum_single_char(self) -> None:
        """Test checksum calculation for single character."""
        result = calculate_checksum("A")
        assert result == "41"  # ASCII value of 'A'


class TestValidateChecksum:
    """Tests for validate_checksum function."""

    def test_validate_checksum_valid_gga(self) -> None:
        """Test validation of valid GGA sentence."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        assert validate_checksum(sentence) is True

    def test_validate_checksum_valid_rmc(self) -> None:
        """Test validation of valid RMC sentence."""
        sentence = "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
        assert validate_checksum(sentence) is True

    def test_validate_checksum_invalid(self) -> None:
        """Test validation of invalid checksum."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*46"
        assert validate_checksum(sentence) is False

    def test_validate_checksum_no_prefix(self) -> None:
        """Test validation fails without $ prefix."""
        sentence = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        assert validate_checksum(sentence) is False

    def test_validate_checksum_no_asterisk(self) -> None:
        """Test validation fails without * separator."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,"
        assert validate_checksum(sentence) is False

    def test_validate_checksum_ais_aivdm(self) -> None:
        """Test validation of AIS AIVDM sentence."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"
        assert validate_checksum(sentence) is True

    def test_validate_checksum_lowercase(self) -> None:
        """Test validation handles lowercase checksums."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        # Convert checksum to lowercase
        sentence_lower = sentence[:-2] + sentence[-2:].lower()
        assert validate_checksum(sentence_lower) is True


class TestExtractChecksum:
    """Tests for extract_checksum function."""

    def test_extract_checksum_basic(self) -> None:
        """Test extracting checksum from sentence."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
        result = extract_checksum(sentence)
        assert result == "47"

    def test_extract_checksum_no_asterisk(self) -> None:
        """Test extracting checksum when no asterisk present."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,"
        result = extract_checksum(sentence)
        assert result is None

    def test_extract_checksum_lowercase(self) -> None:
        """Test extracting lowercase checksum (converts to uppercase)."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*4a"
        result = extract_checksum(sentence)
        assert result == "4A"

    def test_extract_checksum_with_trailing_data(self) -> None:
        """Test extracting checksum with trailing data after checksum."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47\r\n"
        result = extract_checksum(sentence)
        assert result == "47"


class TestAddChecksum:
    """Tests for add_checksum function."""

    def test_add_checksum_basic(self) -> None:
        """Test adding checksum to sentence."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,"
        result = add_checksum(sentence)
        assert result == "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"

    def test_add_checksum_replaces_existing(self) -> None:
        """Test adding checksum replaces existing checksum."""
        sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*99"
        result = add_checksum(sentence)
        assert result == "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"

    def test_add_checksum_ais(self) -> None:
        """Test adding checksum to AIS sentence."""
        sentence = "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0"
        result = add_checksum(sentence)
        assert result == "!AIVDM,1,1,,B,13u@Dp0P00PlT>HMGN4?vhR00<02,0*66"

    def test_add_checksum_no_prefix_raises(self) -> None:
        """Test adding checksum raises error without prefix."""
        sentence = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,"
        with pytest.raises(ValueError, match="must start with"):
            add_checksum(sentence)


class TestChecksumEdgeCases:
    """Tests for checksum edge cases and error conditions."""

    def test_validate_multiple_asterisks(self) -> None:
        """Test validation with multiple asterisks (uses first)."""
        sentence = "$GPGGA,123*47*99"
        # Should fail because it splits on first * and content doesn't match
        assert validate_checksum(sentence) is False

    def test_validate_checksum_too_short(self) -> None:
        """Test validation with checksum too short."""
        sentence = "$GPGGA,123*4"
        # Should still work - takes first 2 chars (only "4" available, compare fails)
        assert validate_checksum(sentence) is False

    def test_calculate_checksum_special_chars(self) -> None:
        """Test checksum calculation with special characters."""
        sentence = "ABC!@#$%^&*()"
        result = calculate_checksum(sentence)
        # Should not raise, just calculate XOR
        assert isinstance(result, str)
        assert len(result) == 2
