"""Tests for from_circular (circular orbit from ECEF point)."""

import math

import numpy as np
import pytest

from gri_utils.constants import GM_EARTH
from gri_utils.orbit import from_circular, orbital_properties


def test_equatorial_point_gives_equatorial_orbit() -> None:
    """Test that point on equator gives equatorial orbit."""
    # Point on equator at 600 km altitude
    xyz_ecef = np.array([6_978_000.0, 0.0, 0.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # Should be nearly equatorial
    assert elements.inclination_rad == pytest.approx(0.0, abs=1e-6)


def test_orbital_radius_matches_point() -> None:
    """Test that orbital radius matches distance from Earth center."""
    radius = 7_200_000.0
    xyz_ecef = np.array([radius, 0.0, 0.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # For circular orbit, a = r
    assert elements.semi_major_axis_m == pytest.approx(radius, rel=1e-6)


def test_circular_eccentricity() -> None:
    """Test that resulting orbit is circular (e ≈ 0)."""
    xyz_ecef = np.array([7_000_000.0, 1_000_000.0, 500_000.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # Should be very close to zero
    assert elements.eccentricity < 1e-10


def test_inclined_point_gives_inclined_orbit() -> None:
    """Test that point off equator gives inclined orbit."""
    # Point at 45 degrees latitude
    radius = 7_000_000.0
    lat = math.radians(45)
    xyz_ecef = np.array(
        [
            radius * math.cos(lat),
            0.0,
            radius * math.sin(lat),
        ],
    )

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # Orbit must have inclination >= latitude to reach that point
    # For prograde circular, inclination should equal |latitude|
    assert elements.inclination_rad >= abs(lat) - 0.01


def test_circular_orbit_period() -> None:
    """Test that period is correct for the radius."""
    radius = 8_000_000.0
    xyz_ecef = np.array([radius, 0.0, 0.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)
    props = orbital_properties(elements)

    # T = 2*pi * sqrt(r^3 / mu)
    expected_period = 2 * math.pi * math.sqrt(radius**3 / GM_EARTH)
    assert props.period_s == pytest.approx(expected_period, rel=1e-6)


def test_different_times_give_different_raan() -> None:
    """Test that different times give different RAAN."""
    xyz_ecef = np.array([7_000_000.0, 0.0, 0.0])

    elements1 = from_circular(xyz_ecef, unix_s=0.0)
    elements2 = from_circular(xyz_ecef, unix_s=3600.0)  # 1 hour later

    # RAAN should differ due to Earth rotation
    # (But elements should otherwise be similar)
    assert elements1.semi_major_axis_m == pytest.approx(
        elements2.semi_major_axis_m,
        rel=1e-6,
    )
    assert elements1.eccentricity == pytest.approx(elements2.eccentricity, abs=1e-10)


def test_zero_radius_raises() -> None:
    """Test that zero radius raises ValueError."""
    xyz_ecef = np.array([0.0, 0.0, 0.0])

    with pytest.raises(ValueError, match="zero radius"):
        from_circular(xyz_ecef, unix_s=0.0)


def test_epoch_is_set() -> None:
    """Test that epoch is set to the input time."""
    xyz_ecef = np.array([7_000_000.0, 0.0, 0.0])
    time = 1704067200.0

    elements = from_circular(xyz_ecef, time)

    assert elements.epoch_unix_s == time


def test_prograde_orbit() -> None:
    """Test that orbit is prograde (inclination < 90 deg for points off equator)."""
    # Point in northern hemisphere
    xyz_ecef = np.array([6_000_000.0, 0.0, 4_000_000.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # Prograde orbit has inclination < 90 degrees
    assert elements.inclination_rad < math.pi / 2


def test_polar_point() -> None:
    """Test orbit through point on z-axis (polar)."""
    # Point on z-axis
    xyz_ecef = np.array([0.0, 0.0, 7_000_000.0])

    elements = from_circular(xyz_ecef, unix_s=0.0)

    # Should still create a valid orbit
    assert elements.semi_major_axis_m == pytest.approx(7_000_000.0, rel=1e-6)
    assert elements.eccentricity < 1e-6


def test_sequence_input() -> None:
    """Test that list/tuple inputs work."""
    xyz_list = [7_000_000.0, 0.0, 0.0]
    xyz_tuple = (7_000_000.0, 0.0, 0.0)

    elements_list = from_circular(xyz_list, unix_s=0.0)
    elements_tuple = from_circular(xyz_tuple, unix_s=0.0)

    assert elements_list.semi_major_axis_m == pytest.approx(
        elements_tuple.semi_major_axis_m,
        rel=1e-10,
    )
