"""Tests for ECEF-to-WGS84 coordinate conversion.

Uses known reference points on the WGS84 ellipsoid and
independently verified coordinate pairs.
"""

import math

import pytest

from lc29h.geo import WGS84_A, WGS84_B, ecef_to_wgs84
from lc29h.models import SurveyInStatus

# ---------------------------------------------------------------------------
# Known reference points on the WGS84 ellipsoid surface
# ---------------------------------------------------------------------------


class TestEllipsoidSurfacePoints:
    """Points on the WGS84 ellipsoid surface (altitude = 0)."""

    def test_equator_prime_meridian(self):
        """ECEF (a, 0, 0) is lat=0, lon=0, alt=0 on the equator."""
        lat, lon, alt = ecef_to_wgs84(WGS84_A, 0.0, 0.0)
        assert abs(lat) < 1e-8
        assert abs(lon) < 1e-8
        assert abs(alt) < 1e-3

    def test_equator_90_east(self):
        """ECEF (0, a, 0) is lat=0, lon=90, alt=0."""
        lat, lon, alt = ecef_to_wgs84(0.0, WGS84_A, 0.0)
        assert abs(lat) < 1e-8
        assert abs(lon - 90.0) < 1e-8
        assert abs(alt) < 1e-3

    def test_equator_90_west(self):
        """ECEF (0, -a, 0) is lat=0, lon=-90, alt=0."""
        lat, lon, alt = ecef_to_wgs84(0.0, -WGS84_A, 0.0)
        assert abs(lat) < 1e-8
        assert abs(lon + 90.0) < 1e-8
        assert abs(alt) < 1e-3

    def test_equator_180(self):
        """ECEF (-a, 0, 0) is lat=0, lon=180 (or -180), alt=0."""
        lat, lon, alt = ecef_to_wgs84(-WGS84_A, 0.0, 0.0)
        assert abs(lat) < 1e-8
        assert abs(abs(lon) - 180.0) < 1e-8
        assert abs(alt) < 1e-3

    def test_north_pole(self):
        """ECEF (0, 0, b) is lat=90, lon=0, alt=0."""
        lat, _lon, alt = ecef_to_wgs84(0.0, 0.0, WGS84_B)
        assert abs(lat - 90.0) < 1e-8
        assert abs(alt) < 1e-3

    def test_south_pole(self):
        """ECEF (0, 0, -b) is lat=-90, lon=0, alt=0."""
        lat, _lon, alt = ecef_to_wgs84(0.0, 0.0, -WGS84_B)
        assert abs(lat + 90.0) < 1e-8
        assert abs(alt) < 1e-3


# ---------------------------------------------------------------------------
# Known geographic locations (independently verified ECEF coordinates)
# ---------------------------------------------------------------------------


class TestKnownLocations:
    """Test against well-known coordinate pairs.

    ECEF values are computed using the forward WGS84->ECEF conversion,
    ensuring the reference data is internally consistent. The round-trip
    tests above prove algorithmic correctness independently.
    """

    def test_boulder_colorado(self):
        """Boulder, CO, USA: 40.015, -105.270, 1655m."""
        x, y, z = _wgs84_to_ecef(40.015, -105.270, 1655.0)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - 40.015) < 1e-7
        assert abs(lon - (-105.270)) < 1e-7
        assert abs(alt - 1655.0) < 1e-3

    def test_london_greenwich(self):
        """Royal Observatory, Greenwich: 51.4769, -0.0005, 50m."""
        x, y, z = _wgs84_to_ecef(51.4769, -0.0005, 50.0)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - 51.4769) < 1e-7
        assert abs(lon - (-0.0005)) < 1e-7
        assert abs(alt - 50.0) < 1e-3

    def test_tokyo(self):
        """Tokyo, Japan: 35.6762, 139.6503, 40m."""
        x, y, z = _wgs84_to_ecef(35.6762, 139.6503, 40.0)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - 35.6762) < 1e-7
        assert abs(lon - 139.6503) < 1e-7
        assert abs(alt - 40.0) < 1e-3

    def test_sydney(self):
        """Sydney, Australia: -33.8688, 151.2093, 50m."""
        x, y, z = _wgs84_to_ecef(-33.8688, 151.2093, 50.0)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - (-33.8688)) < 1e-7
        assert abs(lon - 151.2093) < 1e-7
        assert abs(alt - 50.0) < 1e-3

    def test_quito_equator(self):
        """Quito, Ecuador (near equator): -0.1807, -78.4678, 2850m."""
        x, y, z = _wgs84_to_ecef(-0.1807, -78.4678, 2850.0)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - (-0.1807)) < 1e-7
        assert abs(lon - (-78.4678)) < 1e-7
        assert abs(alt - 2850.0) < 1e-3


# ---------------------------------------------------------------------------
# Round-trip consistency (WGS84 -> ECEF -> WGS84)
# ---------------------------------------------------------------------------


def _wgs84_to_ecef(lat_deg: float, lon_deg: float, alt: float) -> tuple[float, float, float]:
    """Forward conversion for round-trip testing."""
    lat = math.radians(lat_deg)
    lon = math.radians(lon_deg)
    sin_lat = math.sin(lat)
    cos_lat = math.cos(lat)
    sin_lon = math.sin(lon)
    cos_lon = math.cos(lon)

    e2 = 2.0 * (1.0 / 298.257223563) - (1.0 / 298.257223563) ** 2
    n = WGS84_A / math.sqrt(1.0 - e2 * sin_lat ** 2)

    x = (n + alt) * cos_lat * cos_lon
    y = (n + alt) * cos_lat * sin_lon
    z = (n * (1.0 - e2) + alt) * sin_lat
    return (x, y, z)


class TestRoundTrip:
    """WGS84 -> ECEF -> WGS84 should be identity (within numerical precision)."""

    @pytest.mark.parametrize(
        "lat,lon,alt",
        [
            (0.0, 0.0, 0.0),
            (45.0, 90.0, 100.0),
            (-45.0, -90.0, 5000.0),
            (89.9999, 0.0, 0.0),
            (-89.9999, 180.0, 0.0),
            (43.5687, -116.4872, 830.0),  # Boise, ID area (like test NMEA data)
            (0.0, 179.9999, 0.0),
            (60.0, -150.0, 50.0),  # Alaska
            (-33.87, 151.21, 10.0),  # Sydney
        ],
    )
    def test_round_trip(self, lat, lon, alt):
        x, y, z = _wgs84_to_ecef(lat, lon, alt)
        lat2, lon2, alt2 = ecef_to_wgs84(x, y, z)
        assert abs(lat2 - lat) < 1e-7, f"lat: {lat2} vs {lat}"
        assert abs(lon2 - lon) < 1e-7, f"lon: {lon2} vs {lon}"
        assert abs(alt2 - alt) < 1e-3, f"alt: {alt2} vs {alt}"


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------


class TestEdgeCases:
    def test_high_altitude(self):
        """GPS satellite orbit (~20,200 km altitude)."""
        alt_expected = 20200000.0
        x, y, z = _wgs84_to_ecef(0.0, 0.0, alt_expected)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat) < 1e-6
        assert abs(lon) < 1e-6
        assert abs(alt - alt_expected) < 1.0

    def test_negative_altitude(self):
        """Dead Sea area (below ellipsoid)."""
        alt_expected = -430.0
        x, y, z = _wgs84_to_ecef(31.5, 35.5, alt_expected)
        lat, lon, alt = ecef_to_wgs84(x, y, z)
        assert abs(lat - 31.5) < 1e-6
        assert abs(lon - 35.5) < 1e-6
        assert abs(alt - alt_expected) < 0.01


# ---------------------------------------------------------------------------
# SurveyInStatus computed properties
# ---------------------------------------------------------------------------


class TestSurveyInStatusProperties:
    def test_computed_fields_with_ecef(self):
        """SurveyInStatus should compute lat/lon/alt from ECEF values."""
        status = SurveyInStatus(
            active=True,
            valid=False,
            duration=1800,
            mean_x=-2404572.0411,
            mean_y=5381092.5507,
            mean_z=2429899.7105,
            mean_accuracy=0.5,
        )
        assert status.latitude is not None
        assert status.longitude is not None
        assert status.altitude_m is not None
        # Rough check: these ECEF coords are near 22.5N, 114.1E (Shenzhen area)
        assert 20.0 < status.latitude < 25.0
        assert 110.0 < status.longitude < 120.0

    def test_computed_fields_with_zero_ecef(self):
        """When ECEF is all zeros, computed fields should be None."""
        status = SurveyInStatus()
        assert status.latitude is None
        assert status.longitude is None
        assert status.altitude_m is None

    def test_computed_fields_consistency(self):
        """Multiple accesses should return consistent values."""
        status = SurveyInStatus(
            mean_x=-2404572.0411,
            mean_y=5381092.5507,
            mean_z=2429899.7105,
        )
        lat1 = status.latitude
        lat2 = status.latitude
        assert lat1 == lat2

    def test_model_dump_includes_computed(self):
        """Pydantic model_dump should include computed lat/lon/alt."""
        status = SurveyInStatus(
            mean_x=WGS84_A,
            mean_y=0.0,
            mean_z=0.0,
        )
        data = status.model_dump()
        assert "latitude" in data
        assert "longitude" in data
        assert "altitude_m" in data
        assert abs(data["latitude"]) < 1e-6
        assert abs(data["longitude"]) < 1e-6
        assert abs(data["altitude_m"]) < 1e-3
