"""
Unit Tests for GPS Validator

Tests GPS coordinate validation, Null Island detection, accuracy thresholds
"""

import pytest
from src.gps.validator import (
    validate_gps_coordinates,
    is_null_island,
    is_valid_accuracy,
    anonymize_gps,
    calculate_distance,
    GPSCoordinate,
    GPSValidator
)


@pytest.mark.unit
class TestGPSValidation:
    """Test GPS coordinate validation"""

    def test_valid_coordinates(self, sample_gps_coordinates):
        """Test valid GPS coordinates"""
        for lat, lon, accuracy, location in sample_gps_coordinates:
            assert validate_gps_coordinates(lat, lon, accuracy), \
                f"Valid coordinates rejected: {location}"

    def test_invalid_coordinates(self, invalid_gps_coordinates):
        """Test invalid GPS coordinates"""
        for lat, lon, accuracy, reason in invalid_gps_coordinates:
            assert not validate_gps_coordinates(lat, lon, accuracy), \
                f"Invalid coordinates accepted: {reason}"

    def test_null_island_detection(self):
        """Test Null Island (0, 0) detection"""
        assert is_null_island(0.0, 0.0)
        assert is_null_island(0.0001, 0.0001)  # Close to null island
        assert not is_null_island(40.7128, -74.0060)  # NYC

    def test_accuracy_validation(self):
        """Test GPS accuracy threshold validation"""
        assert is_valid_accuracy(5.0)  # High quality
        assert is_valid_accuracy(25.0)  # Medium quality
        assert is_valid_accuracy(50.0)  # Threshold
        assert not is_valid_accuracy(51.0)  # Over threshold
        assert not is_valid_accuracy(100.0)  # Poor accuracy
        assert not is_valid_accuracy(0.0)  # Invalid
        assert not is_valid_accuracy(-5.0)  # Invalid

    def test_latitude_bounds(self):
        """Test latitude boundary validation"""
        assert validate_gps_coordinates(90.0, 0.0, 5.0)  # Max valid
        assert validate_gps_coordinates(-90.0, 0.0, 5.0)  # Min valid
        assert not validate_gps_coordinates(90.1, 0.0, 5.0)  # Over max
        assert not validate_gps_coordinates(-90.1, 0.0, 5.0)  # Under min

    def test_longitude_bounds(self):
        """Test longitude boundary validation"""
        assert validate_gps_coordinates(0.0, 180.0, 5.0)  # Max valid
        assert validate_gps_coordinates(0.0, -180.0, 5.0)  # Min valid
        assert not validate_gps_coordinates(0.0, 180.1, 5.0)  # Over max
        assert not validate_gps_coordinates(0.0, -180.1, 5.0)  # Under min


@pytest.mark.unit
class TestGPSAnonymization:
    """Test GPS coordinate anonymization"""

    def test_anonymize_10m_precision(self):
        """Test 10m precision anonymization"""
        lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=10)
        assert lat == pytest.approx(40.71285, abs=0.00001)
        assert lon == pytest.approx(-74.00597, abs=0.00001)

    def test_anonymize_100m_precision(self):
        """Test 100m precision anonymization"""
        lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=100)
        assert lat == pytest.approx(40.713, abs=0.001)
        assert lon == pytest.approx(-74.006, abs=0.001)

    def test_anonymize_1km_precision(self):
        """Test 1km precision anonymization"""
        lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=1000)
        assert lat == pytest.approx(40.71, abs=0.01)
        assert lon == pytest.approx(-74.01, abs=0.01)


@pytest.mark.unit
class TestDistanceCalculation:
    """Test Haversine distance calculation"""

    def test_distance_same_point(self):
        """Test distance between same point"""
        distance = calculate_distance(40.7128, -74.0060, 40.7128, -74.0060)
        assert distance == pytest.approx(0.0, abs=0.001)

    def test_distance_nyc_to_london(self):
        """Test distance NYC to London"""
        # NYC: 40.7128, -74.0060
        # London: 51.5074, -0.1278
        distance = calculate_distance(40.7128, -74.0060, 51.5074, -0.1278)
        assert distance == pytest.approx(5570, abs=20)  # ~5570 km (geopy calculation)

    def test_distance_symmetry(self):
        """Test distance calculation is symmetric"""
        d1 = calculate_distance(40.7128, -74.0060, 51.5074, -0.1278)
        d2 = calculate_distance(51.5074, -0.1278, 40.7128, -74.0060)
        assert d1 == pytest.approx(d2)


@pytest.mark.unit
class TestGPSCoordinate:
    """Test GPSCoordinate dataclass"""

    def test_create_valid_coordinate(self):
        """Test creating valid GPS coordinate"""
        coord = GPSCoordinate(
            latitude=40.7128,
            longitude=-74.0060,
            accuracy=5.0
        )
        assert coord.latitude == 40.7128
        assert coord.longitude == -74.0060
        assert coord.accuracy == 5.0

    def test_create_invalid_coordinate(self):
        """Test creating invalid GPS coordinate raises ValueError"""
        with pytest.raises(ValueError):
            GPSCoordinate(
                latitude=91.0,  # Invalid
                longitude=0.0,
                accuracy=5.0
            )

    def test_is_high_quality(self):
        """Test high-quality detection"""
        high_quality = GPSCoordinate(40.7128, -74.0060, accuracy=8.0)
        assert high_quality.is_high_quality()

        medium_quality = GPSCoordinate(40.7128, -74.0060, accuracy=25.0)
        assert not medium_quality.is_high_quality()

    def test_to_dict(self):
        """Test serialization to dictionary"""
        coord = GPSCoordinate(40.7128, -74.0060, accuracy=5.0)
        data = coord.to_dict()

        assert data['latitude'] == 40.7128
        assert data['longitude'] == -74.0060
        assert data['accuracy'] == 5.0


@pytest.mark.unit
class TestGPSValidatorClass:
    """Test GPSValidator class with configurable thresholds"""

    def test_default_thresholds(self):
        """Test validator with default thresholds"""
        validator = GPSValidator()

        is_valid, reason = validator.validate(40.7128, -74.0060, 5.0)
        assert is_valid
        assert reason is None

    def test_custom_max_accuracy(self):
        """Test custom maximum accuracy threshold"""
        validator = GPSValidator(max_accuracy=30.0)

        # Should accept 25m
        is_valid, reason = validator.validate(40.7128, -74.0060, 25.0)
        assert is_valid

        # Should reject 35m
        is_valid, reason = validator.validate(40.7128, -74.0060, 35.0)
        assert not is_valid
        assert "accuracy" in reason.lower()

    def test_strict_mode(self):
        """Test strict mode (requires accuracy data)"""
        validator = GPSValidator(strict_mode=True)

        # Should reject without accuracy
        is_valid, reason = validator.validate(40.7128, -74.0060, None)
        assert not is_valid
        assert "accuracy" in reason.lower()

    def test_allow_null_island(self):
        """Test allowing Null Island coordinates"""
        validator = GPSValidator(allow_null_island=True)

        is_valid, reason = validator.validate(0.0, 0.0, 5.0)
        assert is_valid

    def test_validation_statistics(self):
        """Test validation statistics tracking"""
        validator = GPSValidator()

        # Validate several coordinates
        validator.validate(40.7128, -74.0060, 5.0)  # Valid
        validator.validate(91.0, 0.0, 5.0)  # Invalid latitude
        validator.validate(0.0, 0.0, 5.0)  # Null Island

        stats = validator.get_statistics()

        assert stats['total_validated'] == 3
        assert stats['total_accepted'] == 1
        assert stats['total_rejected'] == 2
        assert stats['acceptance_rate'] == pytest.approx(1/3, abs=0.01)
        assert 'out_of_bounds' in stats['rejection_reasons']
        assert 'null_island' in stats['rejection_reasons']

    def test_reset_statistics(self):
        """Test statistics reset"""
        validator = GPSValidator()

        validator.validate(40.7128, -74.0060, 5.0)
        validator.reset_statistics()

        stats = validator.get_statistics()
        assert stats['total_validated'] == 0
        assert stats['total_accepted'] == 0
