"""Create OrbitalElements from individual element values.

This module provides convenience functions to create OrbitalElements from
individual parameters, with support for angle inputs in radians (the standard)
or degrees (for convenience).

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

from __future__ import annotations

import math

from .orbital_elements import OrbitalElements


def from_elements(  # noqa: PLR0913
    semi_major_axis_m: float,
    eccentricity: float,
    inclination_rad: float,
    raan_rad: float,
    arg_periapsis_rad: float,
    mean_anomaly_rad: float,
    epoch_unix_s: float,
) -> OrbitalElements:
    """Create OrbitalElements from individual parameters with angles in radians.

    Args:
        semi_major_axis_m: Semi-major axis in meters. Must be positive.
        eccentricity: Orbital eccentricity. Must be in range [0, 1) for
            elliptical orbits.
        inclination_rad: Inclination in radians [0, pi]. 0 = equatorial prograde,
            pi/2 = polar, pi = equatorial retrograde.
        raan_rad: Right ascension of ascending node in radians [0, 2*pi).
        arg_periapsis_rad: Argument of periapsis in radians [0, 2*pi).
        mean_anomaly_rad: Mean anomaly at epoch in radians [0, 2*pi).
        epoch_unix_s: Epoch as Unix timestamp in seconds.

    Returns:
        OrbitalElements tuple.

    Raises:
        ValueError: If parameters are outside valid ranges.

    Example:
        >>> import math
        >>> from gri_utils.orbit import from_elements
        >>> elements = from_elements(
        ...     semi_major_axis_m=7_000_000,
        ...     eccentricity=0.001,
        ...     inclination_rad=math.radians(51.6),
        ...     raan_rad=0.0,
        ...     arg_periapsis_rad=0.0,
        ...     mean_anomaly_rad=0.0,
        ...     epoch_unix_s=0.0,
        ... )
    """
    if semi_major_axis_m <= 0:
        msg = f"Semi-major axis must be positive, got {semi_major_axis_m}"
        raise ValueError(msg)

    if eccentricity < 0 or eccentricity >= 1:
        msg = (
            f"Eccentricity must be in [0, 1) for elliptical orbits, got {eccentricity}"
        )
        raise ValueError(msg)

    two_pi = 2.0 * math.pi

    return OrbitalElements(
        semi_major_axis_m=semi_major_axis_m,
        eccentricity=eccentricity,
        inclination_rad=inclination_rad,
        raan_rad=raan_rad % two_pi,
        arg_periapsis_rad=arg_periapsis_rad % two_pi,
        mean_anomaly_rad=mean_anomaly_rad % two_pi,
        epoch_unix_s=epoch_unix_s,
    )


def from_elements_deg(  # noqa: PLR0913
    semi_major_axis_m: float,
    eccentricity: float,
    inclination_deg: float,
    raan_deg: float,
    arg_periapsis_deg: float,
    mean_anomaly_deg: float,
    epoch_unix_s: float,
) -> OrbitalElements:
    """Create OrbitalElements from individual parameters with angles in degrees.

    This is a convenience function that accepts angles in degrees and converts
    them to radians internally.

    Args:
        semi_major_axis_m: Semi-major axis in meters. Must be positive.
        eccentricity: Orbital eccentricity. Must be in range [0, 1) for
            elliptical orbits.
        inclination_deg: Inclination in degrees [0, 180]. 0 = equatorial prograde,
            90 = polar, 180 = equatorial retrograde.
        raan_deg: Right ascension of ascending node in degrees [0, 360).
        arg_periapsis_deg: Argument of periapsis in degrees [0, 360).
        mean_anomaly_deg: Mean anomaly at epoch in degrees [0, 360).
        epoch_unix_s: Epoch as Unix timestamp in seconds.

    Returns:
        OrbitalElements with angles converted to radians.

    Raises:
        ValueError: If parameters are outside valid ranges.

    Example:
        >>> from gri_utils.orbit import from_elements_deg
        >>> # ISS-like orbit
        >>> elements = from_elements_deg(
        ...     semi_major_axis_m=6_778_000,
        ...     eccentricity=0.0001,
        ...     inclination_deg=51.6,
        ...     raan_deg=0.0,
        ...     arg_periapsis_deg=0.0,
        ...     mean_anomaly_deg=0.0,
        ...     epoch_unix_s=1704067200.0,
        ... )
    """
    if semi_major_axis_m <= 0:
        msg = f"Semi-major axis must be positive, got {semi_major_axis_m}"
        raise ValueError(msg)

    if eccentricity < 0 or eccentricity >= 1:
        msg = (
            f"Eccentricity must be in [0, 1) for elliptical orbits, got {eccentricity}"
        )
        raise ValueError(msg)

    if inclination_deg < 0 or inclination_deg > 180:  # noqa: PLR2004
        msg = f"Inclination must be in [0, 180] degrees, got {inclination_deg}"
        raise ValueError(msg)

    deg_to_rad = math.pi / 180.0

    return OrbitalElements(
        semi_major_axis_m=semi_major_axis_m,
        eccentricity=eccentricity,
        inclination_rad=inclination_deg * deg_to_rad,
        raan_rad=(raan_deg % 360.0) * deg_to_rad,
        arg_periapsis_rad=(arg_periapsis_deg % 360.0) * deg_to_rad,
        mean_anomaly_rad=(mean_anomaly_deg % 360.0) * deg_to_rad,
        epoch_unix_s=epoch_unix_s,
    )
