"""Tests for line_of_sight_clear function.

Functional tests for WGS84 line-of-sight occlusion detection.
"""

import numpy as np
import pytest

from gri_utils.constants import ER_M, PR_M
from gri_utils.conversion import lla_to_xyz
from gri_utils.conversion.xyz_aer import translate_aer
from gri_utils.distance import line_of_sight_clear

# -----------------------------------------------------------------------------
# Clear line of sight scenarios
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("lla1", "lla2", "description"),
    [
        # Satellite directly overhead
        ([0.0, 0.0, 0.0], [0.0, 0.0, 400e3], "LEO overhead at equator"),
        ([40.0, -75.0, 0.0], [40.0, -75.0, 400e3], "LEO overhead mid-lat"),
        # GEO visible from ground
        ([40.0, -100.0, 0.0], [0.0, -100.0, 35786e3], "GEO from central US"),
        # Two satellites same side of Earth
        ([0.0, 0.0, 400e3], [10.0, 10.0, 420e3], "Two LEO same side"),
        # Nearby elevated points
        ([35.0, 105.0, 4000.0], [35.0, 105.01, 4000.0], "Two mountaintops"),
        ([0.0, 0.0, 100.0], [0.0, 0.01, 100.0], "Adjacent elevated points"),
    ],
)
def test_clear_line_of_sight(
    lla1: list[float],
    lla2: list[float],
    description: str,
) -> None:
    """Verify clear line of sight between points."""
    xyz1 = lla_to_xyz(lla1)
    xyz2 = lla_to_xyz(lla2)
    assert line_of_sight_clear(xyz1, xyz2), description


# -----------------------------------------------------------------------------
# Blocked line of sight scenarios
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("lla1", "lla2", "description"),
    [
        # Opposite sides of Earth
        ([0.0, 0.0, 400e3], [0.0, 180.0, 400e3], "Two LEO opposite sides"),
        ([0.0, 0.0, 35786e3], [0.0, 180.0, 35786e3], "Two GEO opposite sides"),
        # Surface chord (passes through Earth)
        ([0.0, 0.0, 0.0], [0.0, 0.01, 0.0], "Adjacent surface points"),
        # GEO not visible from high latitude
        ([85.0, 0.0, 0.0], [0.0, 180.0, 35786e3], "GEO from arctic opposite side"),
        # Grazing path at equator
        ([0.0, 89.0, 1000e3], [0.0, -89.0, 1000e3], "Grazing equatorial bulge"),
    ],
)
def test_blocked_line_of_sight(
    lla1: list[float],
    lla2: list[float],
    description: str,
) -> None:
    """Verify blocked line of sight between points."""
    xyz1 = lla_to_xyz(lla1)
    xyz2 = lla_to_xyz(lla2)
    assert not line_of_sight_clear(xyz1, xyz2), description


# -----------------------------------------------------------------------------
# Elevation angle tests (ground to target at various elevations)
# -----------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("lat", "az", "description"),
    [
        (0.0, 0.0, "equator north"),
        (0.0, 90.0, "equator east"),
        (45.0, 0.0, "mid-lat north"),
        (45.0, 180.0, "mid-lat south"),
        (80.0, 45.0, "high latitude"),
        (-45.0, 270.0, "southern hemisphere"),
    ],
)
def test_negative_elevation_blocked(lat: float, az: float, description: str) -> None:
    """Negative elevation angles are blocked by Earth."""
    ground_xyz = lla_to_xyz([lat, 0.0, 0.0])
    target_xyz = translate_aer(ground_xyz, [az, -0.5, 500e3])
    assert not line_of_sight_clear(ground_xyz, target_xyz), description


@pytest.mark.parametrize(
    ("lat", "az", "description"),
    [
        (0.0, 0.0, "equator north"),
        (0.0, 90.0, "equator east"),
        (45.0, 0.0, "mid-lat north"),
        (45.0, 180.0, "mid-lat south"),
        (80.0, 45.0, "high latitude"),
        (-45.0, 270.0, "southern hemisphere"),
    ],
)
def test_positive_elevation_clear(lat: float, az: float, description: str) -> None:
    """Positive elevation angles are clear."""
    ground_xyz = lla_to_xyz([lat, 0.0, 0.0])
    target_xyz = translate_aer(ground_xyz, [az, 5.0, 500e3])
    assert line_of_sight_clear(ground_xyz, target_xyz), description


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


def test_same_point_is_clear() -> None:
    """Line of sight from a point to itself is clear."""
    point = np.array([ER_M + 1000.0, 0.0, 0.0])
    assert line_of_sight_clear(point, point)


def test_symmetry() -> None:
    """LOS check is symmetric: f(a,b) == f(b,a)."""
    pt1 = lla_to_xyz([30.0, 45.0, 500e3])
    pt2 = lla_to_xyz([-20.0, -120.0, 300e3])
    assert line_of_sight_clear(pt1, pt2) == line_of_sight_clear(pt2, pt1)


def test_accepts_lists_and_tuples() -> None:
    """Function accepts lists and tuples, not just arrays."""
    sat = [42164e3, 0.0, 0.0]
    ground = (ER_M, 0.0, 0.0)
    assert line_of_sight_clear(sat, ground)


def test_wgs84_equatorial_radius() -> None:
    """Verify WGS84 equatorial radius is used."""
    # Point just outside ER_M should be clear to far satellite
    just_outside = np.array([ER_M + 1.0, 0.0, 0.0])
    far_sat = np.array([ER_M + 1000e3, 0.0, 0.0])
    assert line_of_sight_clear(just_outside, far_sat)

    # Opposite surface points blocked
    on_surface = np.array([ER_M, 0.0, 0.0])
    opposite = np.array([-ER_M, 0.0, 0.0])
    assert not line_of_sight_clear(on_surface, opposite)


def test_wgs84_polar_radius() -> None:
    """Verify WGS84 polar radius is used."""
    # Point just outside PR_M at pole should be clear
    just_outside = np.array([0.0, 0.0, PR_M + 1.0])
    far_sat = np.array([0.0, 0.0, PR_M + 1000e3])
    assert line_of_sight_clear(just_outside, far_sat)

    # Opposite poles blocked
    north = np.array([0.0, 0.0, PR_M])
    south = np.array([0.0, 0.0, -PR_M])
    assert not line_of_sight_clear(north, south)
