"""Tests for orbital_properties (derived orbital quantities)."""

import math

import pytest

from gri_utils.constants import ER_M, GM_EARTH
from gri_utils.orbit import OrbitalElements, OrbitalProperties, orbital_properties


def test_circular_orbit_properties() -> None:
    """Test properties of a circular orbit."""
    radius = 7_000_000  # 7000 km
    elements = OrbitalElements(
        semi_major_axis_m=radius,
        eccentricity=0.0,
        inclination_rad=0.0,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # For circular orbit: semi_minor = semi_major
    assert props.semi_minor_axis_m == pytest.approx(radius, rel=1e-10)

    # Apogee = perigee = radius
    assert props.apogee_radius_m == pytest.approx(radius, rel=1e-10)
    assert props.perigee_radius_m == pytest.approx(radius, rel=1e-10)

    # Altitudes
    expected_altitude = radius - ER_M
    assert props.apogee_altitude_m == pytest.approx(expected_altitude, rel=1e-6)
    assert props.perigee_altitude_m == pytest.approx(expected_altitude, rel=1e-6)


def test_elliptical_orbit_apsis() -> None:
    """Test apogee/perigee calculations for elliptical orbit."""
    a = 10_000_000  # 10000 km semi-major axis
    e = 0.2

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=e,
        inclination_rad=0.5,
        raan_rad=1.0,
        arg_periapsis_rad=0.5,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # r_a = a(1+e), r_p = a(1-e)
    expected_apogee = a * (1 + e)
    expected_perigee = a * (1 - e)

    assert props.apogee_radius_m == pytest.approx(expected_apogee, rel=1e-10)
    assert props.perigee_radius_m == pytest.approx(expected_perigee, rel=1e-10)


def test_semi_minor_axis() -> None:
    """Test semi-minor axis calculation."""
    a = 8_000_000
    e = 0.3

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=e,
        inclination_rad=0.0,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # b = a * sqrt(1 - e^2)
    expected_b = a * math.sqrt(1 - e**2)
    assert props.semi_minor_axis_m == pytest.approx(expected_b, rel=1e-10)


def test_orbital_period() -> None:
    """Test orbital period calculation."""
    a = 7_000_000

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=0.0,
        inclination_rad=0.0,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

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


def test_geo_orbital_period() -> None:
    """Test that GEO orbit has ~24 hour period."""
    # GEO altitude: ~35,786 km -> radius ~42,164 km
    geo_radius = 42_164_000

    elements = OrbitalElements(
        semi_major_axis_m=geo_radius,
        eccentricity=0.0,
        inclination_rad=0.0,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # Should be close to 24 hours (86400 seconds)
    # Actually sidereal day is ~86164 seconds
    sidereal_day = 86164.0
    assert props.period_s == pytest.approx(sidereal_day, rel=0.01)


def test_specific_energy() -> None:
    """Test specific orbital energy calculation."""
    a = 9_000_000

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=0.1,
        inclination_rad=0.5,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # epsilon = -mu / (2a)
    expected_energy = -GM_EARTH / (2 * a)
    assert props.specific_energy_j_kg == pytest.approx(expected_energy, rel=1e-10)

    # Energy should be negative for bound orbit
    assert props.specific_energy_j_kg < 0


def test_specific_angular_momentum() -> None:
    """Test specific angular momentum calculation."""
    a = 8_000_000
    e = 0.2

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=e,
        inclination_rad=0.5,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # h = sqrt(mu * a * (1 - e^2))
    expected_h = math.sqrt(GM_EARTH * a * (1 - e**2))
    assert props.specific_angular_momentum_m2_s == pytest.approx(expected_h, rel=1e-10)


def test_mean_motion() -> None:
    """Test mean motion calculation."""
    a = 7_500_000

    elements = OrbitalElements(
        semi_major_axis_m=a,
        eccentricity=0.05,
        inclination_rad=0.3,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    # n = sqrt(mu / a^3)
    expected_n = math.sqrt(GM_EARTH / a**3)
    assert props.mean_motion_rad_s == pytest.approx(expected_n, rel=1e-10)

    # Also check consistency: T = 2*pi / n
    assert props.period_s == pytest.approx(
        2 * math.pi / props.mean_motion_rad_s,
        rel=1e-10,
    )


def test_properties_return_type() -> None:
    """Test that orbital_properties returns OrbitalProperties type."""
    elements = OrbitalElements(
        semi_major_axis_m=7_000_000,
        eccentricity=0.01,
        inclination_rad=0.5,
        raan_rad=0.0,
        arg_periapsis_rad=0.0,
        mean_anomaly_rad=0.0,
        epoch_unix_s=0.0,
    )

    props = orbital_properties(elements)

    assert isinstance(props, OrbitalProperties)
    # Check all fields are accessible
    assert props.semi_minor_axis_m > 0
    assert props.period_s > 0
    assert props.apogee_radius_m > 0
    assert props.perigee_radius_m > 0
    assert props.mean_motion_rad_s > 0
