"""Tests for slant_range function.

Functional tests for computing range from an observer along az/el to a target altitude.
"""

import pytest

from gri_utils.conversion import lla_to_xyz
from gri_utils.conversion.xyz_aer import translate_aer
from gri_utils.conversion.xyz_lla import xyz_to_lla
from gri_utils.distance import slant_range

# -----------------------------------------------------------------------------
# Ground observer looking UP at satellites - primary use case
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("observer_lla", "az", "el", "target_alt", "description"),
    [
        # Overhead cases - range should equal altitude difference
        ([0.0, 0.0, 0.0], 0.0, 90.0, 300_000.0, "LEO overhead from equator"),
        ([40.0, -75.0, 0.0], 0.0, 90.0, 300_000.0, "LEO overhead from mid-lat"),
        ([0.0, 0.0, 2500.0], 0.0, 90.0, 500_000.0, "LEO overhead from mountain"),
        # Angled observations - typical tracking scenarios
        ([40.0, -75.0, 0.0], 30.0, 45.0, 300_000.0, "LEO at 45 deg elevation"),
        ([0.0, 0.0, 0.0], 90.0, 10.0, 400_000.0, "LEO near horizon"),
        ([40.0, -75.0, 0.0], 180.0, 45.0, 35_786_000.0, "GEO satellite"),
        # Aircraft tracking
        ([42.0, -71.0, 50.0], 45.0, 15.0, 10_000.0, "Aircraft at cruise altitude"),
        # Various global locations
        ([-33.0, 151.0, 0.0], 180.0, 60.0, 400_000.0, "Sydney looking south"),
        ([78.0, 15.0, 0.0], 45.0, 15.0, 400_000.0, "Arctic low elevation"),
    ],
)
def test_ground_to_target_altitude(
    observer_lla: list[float],
    az: float,
    el: float,
    target_alt: float,
    description: str,
) -> None:
    """Verify slant range correctly finds intersection with target altitude."""
    observer_xyz = lla_to_xyz(observer_lla)
    result = slant_range(
        observer_xyz,
        azimuth_deg=az,
        elevation_deg=el,
        target_altitude_m=target_alt,
    )

    assert result is not None, f"Failed: {description}"
    assert result > 0

    # Verify by computing target position and checking altitude
    target_xyz = translate_aer(observer_xyz, [az, el, result])
    target_lla = xyz_to_lla(target_xyz)

    # Tolerance scales with altitude (0.01% or 100m, whichever is larger)
    tolerance = max(target_alt * 1e-4, 100.0)
    assert target_lla[2] == pytest.approx(target_alt, abs=tolerance), description


# -----------------------------------------------------------------------------
# Satellite looking DOWN at ground/targets - secondary use case
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("observer_lla", "az", "el", "target_alt", "description"),
    [
        # Nadir (straight down)
        ([0.0, 0.0, 500_000.0], 0.0, -90.0, 0.0, "Nadir to ground"),
        ([0.0, 0.0, 500_000.0], 0.0, -90.0, 1000.0, "Nadir to 1km altitude"),
        # Angled observations from space
        ([0.0, 0.0, 400_000.0], 0.0, -45.0, 0.0, "45 deg depression to ground"),
        ([45.0, -90.0, 300_000.0], 180.0, -20.0, 5000.0, "Shallow angle to 5km"),
    ],
)
def test_satellite_to_target_altitude(
    observer_lla: list[float],
    az: float,
    el: float,
    target_alt: float,
    description: str,
) -> None:
    """Verify slant range from elevated platform looking down."""
    observer_xyz = lla_to_xyz(observer_lla)
    result = slant_range(
        observer_xyz,
        azimuth_deg=az,
        elevation_deg=el,
        target_altitude_m=target_alt,
    )

    assert result is not None, f"Failed: {description}"

    # Verify target altitude
    target_xyz = translate_aer(observer_xyz, [az, el, result])
    target_lla = xyz_to_lla(target_xyz)

    assert target_lla[2] == pytest.approx(target_alt, abs=10.0), description


# -----------------------------------------------------------------------------
# Edge cases - no intersection
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("observer_lla", "az", "el", "target_alt", "description"),
    [
        # Looking up from ground toward ground altitude - misses
        ([45.0, -75.0, 100.0], 180.0, 45.0, 0.0, "Ground looking up at ground alt"),
        # Looking above horizon from space - misses Earth
        ([0.0, 0.0, 400_000.0], 0.0, 0.0, 0.0, "Horizontal from 400km"),
        ([0.0, 0.0, 400_000.0], 0.0, -10.0, 0.0, "Shallow angle misses Earth"),
    ],
)
def test_no_intersection_returns_none(
    observer_lla: list[float],
    az: float,
    el: float,
    target_alt: float,
    description: str,
) -> None:
    """Verify None is returned when ray doesn't intersect target altitude."""
    observer_xyz = lla_to_xyz(observer_lla)
    result = slant_range(
        observer_xyz,
        azimuth_deg=az,
        elevation_deg=el,
        target_altitude_m=target_alt,
    )

    assert result is None, f"Expected None: {description}"
