"""Tests for osculating_sphere function.

Functional tests for computing the local osculating sphere at a point on WGS84.
"""

import numpy as np
import pytest

from gri_utils.constants import ECCENTRICITY_SQUARED, ER_M
from gri_utils.conversion import lla_to_xyz
from gri_utils.distance import osculating_sphere


def _expected_radius(lat_deg: float) -> float:
    """Compute expected osculating sphere radius at a latitude."""
    lat_rad = np.radians(lat_deg)
    sin_lat = np.sin(lat_rad)
    w_sq = 1 - ECCENTRICITY_SQUARED * sin_lat**2
    n = ER_M / np.sqrt(w_sq)
    m = ER_M * (1 - ECCENTRICITY_SQUARED) / w_sq ** (3 / 2)
    return float(np.sqrt(m * n))


# -----------------------------------------------------------------------------
# Osculating sphere at various latitudes
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("lat", "description"),
    [
        (0.0, "equator"),
        (45.0, "mid-latitude"),
        (90.0, "north pole"),
        (-45.0, "southern mid-latitude"),
        (80.0, "high latitude"),
    ],
)
def test_osculating_sphere_radius(lat: float, description: str) -> None:
    """Verify osculating sphere radius matches expected formula."""
    lla = [lat, 0.0, 0.0]
    _center, radius = osculating_sphere(lla)

    expected = _expected_radius(lat)
    assert radius == pytest.approx(expected), description


@pytest.mark.parametrize(
    ("lat", "description"),
    [
        (0.0, "equator"),
        (45.0, "mid-latitude"),
        (90.0, "north pole"),
    ],
)
def test_osculating_sphere_center_on_normal(lat: float, description: str) -> None:
    """Verify center lies along surface normal from the surface point."""
    lla = [lat, 0.0, 0.0]
    center, radius = osculating_sphere(lla)
    surface_pt = lla_to_xyz(lla)

    # Vector from center to surface should have length = radius
    to_surface = surface_pt - center
    assert np.linalg.norm(to_surface) == pytest.approx(radius), description


# -----------------------------------------------------------------------------
# Longitude independence
# -----------------------------------------------------------------------------


@pytest.mark.parametrize("lon", [0.0, 90.0, 180.0, -90.0])
def test_radius_independent_of_longitude(lon: float) -> None:
    """Radius depends only on latitude, not longitude."""
    lat = 45.0
    _center, radius = osculating_sphere([lat, lon, 0.0])
    _center_ref, radius_ref = osculating_sphere([lat, 0.0, 0.0])
    assert radius == pytest.approx(radius_ref)


# -----------------------------------------------------------------------------
# Vectorized input
# -----------------------------------------------------------------------------


def test_vectorized_input() -> None:
    """Vectorized input returns consistent results with scalar calls."""
    lla_array = np.array(
        [
            [0.0, 0.0, 0.0],
            [45.0, 90.0, 0.0],
            [90.0, 0.0, 0.0],
        ],
    )
    centers, radii = osculating_sphere(lla_array)

    # Vectorized input returns arrays
    assert isinstance(radii, np.ndarray)
    assert isinstance(centers, np.ndarray)

    # Compare to scalar results
    for i, lla in enumerate(lla_array):
        center_scalar, radius_scalar = osculating_sphere(lla.tolist())
        assert radii[i] == pytest.approx(radius_scalar)
        assert centers[i] == pytest.approx(center_scalar)
