"""Tests for from_perigee_apogee (elliptical orbit from perigee and apogee)."""

import math

import numpy as np
import pytest

from gri_utils.orbit import from_perigee_apogee, orbital_properties


def test_apogee_perigee_match_input_radii() -> None:
    """Test that orbital properties match input perigee and apogee radii."""
    r_perigee = 6_878_000.0  # 500 km altitude
    r_apogee = 42_164_000.0  # GEO altitude

    xyz_perigee = np.array([r_perigee, 0.0, 0.0])
    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)
    props = orbital_properties(elements)

    assert props.perigee_radius_m == pytest.approx(r_perigee, rel=1e-6)
    assert props.apogee_radius_m == pytest.approx(r_apogee, rel=1e-6)


def test_eccentricity_calculation() -> None:
    """Test that eccentricity matches e = (r_a - r_p) / (r_a + r_p)."""
    r_perigee = 7_000_000.0
    r_apogee = 21_000_000.0

    expected_e = (r_apogee - r_perigee) / (r_apogee + r_perigee)

    xyz_perigee = np.array([r_perigee, 0.0, 0.0])
    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    assert elements.eccentricity == pytest.approx(expected_e, rel=1e-6)


def test_semi_major_axis_calculation() -> None:
    """Test that semi-major axis matches a = (r_a + r_p) / 2."""
    r_perigee = 6_800_000.0
    r_apogee = 40_000_000.0

    expected_a = (r_perigee + r_apogee) / 2

    xyz_perigee = np.array([r_perigee, 0.0, 0.0])
    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    assert elements.semi_major_axis_m == pytest.approx(expected_a, rel=1e-6)


def test_circular_when_radii_equal_raises() -> None:
    """Test that equal radii raises ValueError (use from_circular instead)."""
    radius = 7_200_000.0
    xyz_ecef = np.array([radius, 0.0, 0.0])

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


def test_equatorial_perigee_gives_equatorial_orbit() -> None:
    """Test that perigee on equator gives equatorial orbit."""
    xyz_perigee = np.array([6_878_000.0, 0.0, 0.0])
    r_apogee = 20_000_000.0

    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    assert elements.inclination_rad == pytest.approx(0.0, abs=1e-6)


def test_inclined_perigee_gives_inclined_orbit() -> None:
    """Test that perigee off equator gives appropriately inclined orbit."""
    # Perigee at 45 degrees latitude
    r_perigee = 7_000_000.0
    lat = math.radians(45)
    xyz_perigee = np.array(
        [
            r_perigee * math.cos(lat),
            0.0,
            r_perigee * math.sin(lat),
        ],
    )
    r_apogee = 30_000_000.0

    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    # Orbit inclination must be >= latitude to reach that point
    assert elements.inclination_rad >= abs(lat) - 0.01


def test_prograde_orbit() -> None:
    """Test that orbit is prograde (inclination < 90 degrees)."""
    # Perigee in northern hemisphere
    xyz_perigee = np.array([6_000_000.0, 0.0, 4_000_000.0])
    r_apogee = 25_000_000.0

    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    assert elements.inclination_rad < math.pi / 2


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

    elements = from_perigee_apogee(xyz_perigee, r_apogee, time)

    assert elements.epoch_unix_s == time


def test_zero_radius_raises() -> None:
    """Test that zero perigee radius raises ValueError."""
    xyz_perigee = np.array([0.0, 0.0, 0.0])
    r_apogee = 20_000_000.0

    with pytest.raises(ValueError, match="zero radius"):
        from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)


def test_auto_detects_apogee_vs_perigee() -> None:
    """Test that function auto-detects which point is apogee vs perigee.

    When given a point with larger radius than the opposite_radius,
    the function treats it as apogee and opposite_radius as perigee.
    """
    r_larger = 10_000_000.0
    r_smaller = 8_000_000.0

    # Provide point at larger radius - should be detected as apogee
    xyz_larger = np.array([r_larger, 0.0, 0.0])
    elements = from_perigee_apogee(xyz_larger, r_smaller, unix_s=0.0)
    props = orbital_properties(elements)

    # Regardless of which point we provided, radii should be assigned correctly
    assert props.apogee_radius_m == pytest.approx(r_larger, rel=1e-6)
    assert props.perigee_radius_m == pytest.approx(r_smaller, rel=1e-6)


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

    elements_list = from_perigee_apogee(xyz_list, r_apogee, unix_s=0.0)
    elements_tuple = from_perigee_apogee(xyz_tuple, r_apogee, unix_s=0.0)

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


def test_molniya_like_orbit() -> None:
    """Test realistic Molniya-like HEO orbit parameters.

    Molniya orbits typically have:
    - Perigee altitude: ~500 km
    - Apogee altitude: ~40,000 km
    - Period: ~12 hours
    - Eccentricity: ~0.74
    """
    earth_radius = 6_378_137.0
    perigee_alt = 500_000.0  # 500 km
    apogee_alt = 39_873_000.0  # ~40,000 km (tuned for ~12 hour period)

    r_perigee = earth_radius + perigee_alt
    r_apogee = earth_radius + apogee_alt

    xyz_perigee = np.array([r_perigee, 0.0, 0.0])
    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)
    props = orbital_properties(elements)

    # Check eccentricity is high (Molniya-like)
    expected_e = (r_apogee - r_perigee) / (r_apogee + r_perigee)
    assert elements.eccentricity == pytest.approx(expected_e, rel=1e-6)
    assert elements.eccentricity > 0.7

    # Check period is approximately 12 hours
    period_hours = props.period_s / 3600
    assert period_hours == pytest.approx(12.0, rel=0.01)


def test_polar_perigee() -> None:
    """Test orbit with perigee on z-axis (polar position)."""
    xyz_perigee = np.array([0.0, 0.0, 7_000_000.0])
    r_apogee = 20_000_000.0

    elements = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)

    # Should create a valid orbit
    assert elements.semi_major_axis_m == pytest.approx(
        (7_000_000.0 + r_apogee) / 2,
        rel=1e-6,
    )


def test_different_times_give_different_raan() -> None:
    """Test that different times give different RAAN due to Earth rotation."""
    xyz_perigee = np.array([7_000_000.0, 0.0, 0.0])
    r_apogee = 20_000_000.0

    elements1 = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=0.0)
    elements2 = from_perigee_apogee(xyz_perigee, r_apogee, unix_s=3600.0)

    # Semi-major axis and eccentricity should be the same
    assert elements1.semi_major_axis_m == pytest.approx(
        elements2.semi_major_axis_m,
        rel=1e-6,
    )
    assert elements1.eccentricity == pytest.approx(elements2.eccentricity, rel=1e-6)

    # RAAN may differ due to Earth rotation (depends on latitude)
    # For equatorial case, RAAN is degenerate, so this mainly tests
    # that the function handles different times correctly
