"""Derived orbital properties computed from orbital elements.

This module provides the OrbitalProperties NamedTuple and the orbital_properties()
function for computing derived quantities like period, apogee/perigee altitudes,
and specific orbital energy from the classical orbital elements.

These properties are useful for orbit characterization, mission planning, and
understanding the physical characteristics of an orbit.

References:
    - Vallado, D. "Fundamentals of Astrodynamics and Applications", 4th ed.
    - Bate, Mueller, White "Fundamentals of Astrodynamics"
"""

from __future__ import annotations

import math
from typing import TYPE_CHECKING, NamedTuple

from gri_utils.constants import ER_M, GM_EARTH

if TYPE_CHECKING:
    from .orbital_elements import OrbitalElements


class OrbitalProperties(NamedTuple):
    """Derived properties computed from orbital elements.

    These quantities can be calculated from the classical orbital elements
    and are useful for orbit characterization.

    Attributes:
        semi_minor_axis_m: Semi-minor axis in meters. For an ellipse, this is
            half the shortest diameter. Computed as b = a * sqrt(1 - e^2).
        period_s: Orbital period in seconds. Time to complete one orbit.
            Computed as T = 2*pi * sqrt(a^3 / mu).
        apogee_radius_m: Distance from Earth's center to apogee (farthest point)
            in meters. Computed as r_a = a * (1 + e).
        perigee_radius_m: Distance from Earth's center to perigee (closest point)
            in meters. Computed as r_p = a * (1 - e).
        apogee_altitude_m: Height above Earth's surface at apogee in meters.
            Computed as r_a - R_earth.
        perigee_altitude_m: Height above Earth's surface at perigee in meters.
            Computed as r_p - R_earth.
        specific_energy_j_kg: Specific orbital energy in J/kg. This is the
            total mechanical energy per unit mass, constant for Keplerian orbits.
            Computed as epsilon = -mu / (2*a). Negative for bound orbits.
        specific_angular_momentum_m2_s: Specific angular momentum magnitude in
            m^2/s. This is the angular momentum per unit mass, constant for
            Keplerian orbits. Computed as h = sqrt(mu * a * (1 - e^2)).
        mean_motion_rad_s: Mean motion in radians per second. The average angular
            velocity of the orbit. Computed as n = sqrt(mu / a^3).

    Example:
        >>> from gri_utils.orbit import OrbitalElements, orbital_properties
        >>> elements = OrbitalElements(
        ...     semi_major_axis_m=6_778_000,
        ...     eccentricity=0.0001,
        ...     inclination_rad=0.9,
        ...     raan_rad=0.0,
        ...     arg_periapsis_rad=0.0,
        ...     mean_anomaly_rad=0.0,
        ...     epoch_unix_s=0.0,
        ... )
        >>> props = orbital_properties(elements)
        >>> print(f"Period: {props.period_s / 60:.1f} minutes")
        Period: 92.4 minutes
    """

    semi_minor_axis_m: float
    period_s: float
    apogee_radius_m: float
    perigee_radius_m: float
    apogee_altitude_m: float
    perigee_altitude_m: float
    specific_energy_j_kg: float
    specific_angular_momentum_m2_s: float
    mean_motion_rad_s: float


def orbital_properties(elements: OrbitalElements) -> OrbitalProperties:
    """Compute derived orbital properties from orbital elements.

    Calculates various useful quantities from the classical orbital elements,
    including period, apogee/perigee distances, and orbital energy.

    Args:
        elements: OrbitalElements defining the orbit.

    Returns:
        OrbitalProperties containing the derived quantities.

    Example:
        >>> from gri_utils.orbit import OrbitalElements, orbital_properties
        >>> # GEO orbit
        >>> elements = OrbitalElements(
        ...     semi_major_axis_m=42_164_000,
        ...     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)
        >>> print(f"Period: {props.period_s / 3600:.2f} hours")  # Should be ~24 hours
        Period: 23.93 hours
    """
    a = elements.semi_major_axis_m
    e = elements.eccentricity

    # Semi-minor axis: b = a * sqrt(1 - e^2)
    semi_minor_axis_m = a * math.sqrt(1.0 - e * e)

    # Mean motion: n = sqrt(mu / a^3)
    mean_motion_rad_s = math.sqrt(GM_EARTH / (a * a * a))

    # Orbital period: T = 2*pi / n
    period_s = 2.0 * math.pi / mean_motion_rad_s

    # Apogee and perigee radii
    apogee_radius_m = a * (1.0 + e)
    perigee_radius_m = a * (1.0 - e)

    # Altitudes above Earth's surface
    apogee_altitude_m = apogee_radius_m - ER_M
    perigee_altitude_m = perigee_radius_m - ER_M

    # Specific orbital energy: epsilon = -mu / (2*a)
    specific_energy_j_kg = -GM_EARTH / (2.0 * a)

    # Specific angular momentum: h = sqrt(mu * a * (1 - e^2))
    specific_angular_momentum_m2_s = math.sqrt(GM_EARTH * a * (1.0 - e * e))

    return OrbitalProperties(
        semi_minor_axis_m=semi_minor_axis_m,
        period_s=period_s,
        apogee_radius_m=apogee_radius_m,
        perigee_radius_m=perigee_radius_m,
        apogee_altitude_m=apogee_altitude_m,
        perigee_altitude_m=perigee_altitude_m,
        specific_energy_j_kg=specific_energy_j_kg,
        specific_angular_momentum_m2_s=specific_angular_momentum_m2_s,
        mean_motion_rad_s=mean_motion_rad_s,
    )
