"""Create OrbitalElements for a prograde circular orbit through an ECEF point.

This module provides a convenient way to define a circular orbit that passes
through a specific location on or above Earth at a given time. The orbit is
assumed to be prograde (eastward velocity).

The function determines the orbit radius from the point's distance from Earth's
center, and computes the appropriate inclination and RAAN based on where the
point is located.

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

from __future__ import annotations

import math
from typing import TYPE_CHECKING

import numpy as np

from gri_utils.constants import GM_EARTH
from gri_utils.conversion import ecef_to_eci

from .from_state import from_position_velocity

if TYPE_CHECKING:
    from collections.abc import Sequence

    from numpy.typing import NDArray

    from .orbital_elements import OrbitalElements

TOLERANCE = 1e-10
"""Numerical tolerance for near-zero checks to avoid division by zero."""


def from_circular(
    xyz_ecef_m: NDArray[np.floating] | Sequence,
    unix_s: float,
) -> OrbitalElements:
    """Create a prograde circular orbit passing through an ECEF point at a time.

    Given an Earth-fixed position and time, creates orbital elements for a
    circular orbit that:
    1. Passes through that position at that time
    2. Has prograde (eastward) velocity at that point
    3. Has the minimum inclination required to reach the given latitude

    This is useful for quickly defining an orbit over a specific location,
    such as "a circular orbit that passes over Munich at noon".

    Args:
        xyz_ecef_m: Position in ECEF (Earth-Centered Earth-Fixed) coordinates
            in meters. Shape (3,). This defines both the orbital radius and
            the orbit plane orientation.
        unix_s: Unix timestamp in seconds when the orbit passes through this
            point. This becomes the epoch of the orbital elements.

    Returns:
        OrbitalElements for a circular prograde orbit through the specified
        point at the specified time.

    Raises:
        ValueError: If the position is at Earth's center (zero radius).

    Example:
        >>> import numpy as np
        >>> from gri_utils.orbit import from_circular
        >>> # Circular orbit passing over a point at 400 km altitude
        >>> # at the equator on prime meridian
        >>> xyz_ecef = np.array([6_778_000.0, 0.0, 0.0])  # Equatorial point
        >>> elements = from_circular(xyz_ecef, unix_s=1704067200.0)
        >>> print(f"Eccentricity: {elements.eccentricity:.6f}")  # ~0 (circular)

    Note:
        The resulting orbit will have:
        - Eccentricity ~= 0 (circular)
        - Inclination = |latitude| of the ECEF point (minimum inclination to
          reach that latitude)
        - RAAN and argument of periapsis determined by the point's position
          and the time
    """
    xyz_ecef = np.asarray(xyz_ecef_m, dtype=np.float64)

    # Convert ECEF to ECI at the given time
    xyz_eci = ecef_to_eci(xyz_ecef, unix_s)

    # Orbital radius
    r_mag = float(np.linalg.norm(xyz_eci))

    if r_mag < 1e-6:  # noqa: PLR2004
        msg = "Position cannot be at Earth's center (zero radius)"
        raise ValueError(msg)

    # Circular velocity magnitude: v = sqrt(mu/r)
    v_mag = math.sqrt(GM_EARTH / r_mag)

    # For a prograde circular orbit, velocity is perpendicular to position
    # and in the direction of increasing longitude (eastward).
    #
    # The velocity should be perpendicular to r and have a component in the
    # direction of increasing azimuth (prograde).
    #
    # In the orbit plane, velocity is perpendicular to position.
    # For prograde motion, we want angular momentum to point generally
    # toward celestial north (positive z component for inclinations < 90 deg).
    #
    # We compute velocity as: v = (h x r) / |r|^2 * |v|
    # where h is in the direction we want angular momentum.
    #
    # For prograde: h should be roughly aligned with +z (celestial north)
    # This means v = (h_unit x r) * v_mag / |r|
    #
    # Actually, simpler approach: for circular orbit velocity is perpendicular
    # to r. The velocity direction that gives prograde motion (h pointing
    # generally north) is:
    #
    # v_direction = (z_axis x r) / |z_axis x r|  if the result has the right sign
    # or we need to construct it more carefully.

    # Position unit vector
    r_unit = xyz_eci / r_mag

    # For prograde orbit, we want h = r x v to point generally toward +z
    # This means v should be in the direction of z x r (perpendicular to r,
    # in the eastward sense).

    z_axis = np.array([0.0, 0.0, 1.0])

    # z x r gives the eastward direction perpendicular to z and r
    # But this isn't perpendicular to r in general.
    # We need v perpendicular to r and with the right prograde sense.

    # The prograde velocity direction for a point is:
    # It should be perpendicular to r and have positive component in the
    # "eastward" direction (direction of increasing longitude).
    #
    # In the orbit plane, velocity is simply perpendicular to r.
    # The orbit plane contains r and is tilted at the inclination angle.
    #
    # For minimum inclination prograde orbit through this point:
    # - The inclination equals the latitude of the point
    # - The velocity at this point is horizontal (perpendicular to r)
    #   and eastward

    # Compute eastward direction at this point
    # Eastward in ECI is perpendicular to both r and the z-axis, roughly
    # It's in the direction of z x r_xy (where r_xy is r projected onto xy plane)

    # More precisely: the "eastward" direction at a point is the direction of
    # increasing azimuth, which is perpendicular to both the radial direction
    # and the z-axis, in the sense that makes it prograde.

    # Cross product z x r gives a vector perpendicular to z and r
    z_cross_r = np.cross(z_axis, r_unit)
    z_cross_r_mag = float(np.linalg.norm(z_cross_r))

    if z_cross_r_mag < TOLERANCE:
        # Point is on z-axis (polar), so any direction perpendicular to r works
        # Just pick x-direction as velocity direction
        v_direction = np.array([1.0, 0.0, 0.0])
    else:
        # Normalize to get eastward component direction
        eastward = z_cross_r / z_cross_r_mag

        # The velocity should be perpendicular to r.
        # Decompose into component perpendicular to r in the r-z plane (north/south)
        # and the eastward component.
        #
        # For prograde orbit at minimum inclination, velocity is purely eastward
        # at the highest/lowest latitude point of the orbit.
        #
        # Actually for a circular orbit, velocity is always perpendicular to r.
        # The orbit plane contains r, and v is perpendicular to r within that plane.
        #
        # For prograde, the angular momentum h = r x v should have positive z component
        # (for inclinations < 90 deg).

        # Let's use a cleaner approach:
        # 1. The orbit plane normal (h direction) passes through origin and r
        # 2. For prograde with minimum inclination, h is tilted from z toward
        #    the direction perpendicular to r in the xy plane

        # The velocity perpendicular to r with prograde sense is:
        # v = h_unit x r_unit * v_mag
        # where h_unit is chosen to give prograde motion.

        # For simplicity at this point:
        # v should be perpendicular to r and have positive "eastward" component
        # The component of eastward that's perpendicular to r:
        v_direction = eastward - np.dot(eastward, r_unit) * r_unit
        v_direction_mag = float(np.linalg.norm(v_direction))

        if v_direction_mag < TOLERANCE:
            # r is nearly parallel to eastward direction (shouldn't happen normally)
            # Fall back to z x r direction
            v_direction = z_cross_r / z_cross_r_mag
        else:
            v_direction = v_direction / v_direction_mag

    # Compute velocity vector
    v_eci = v_direction * v_mag

    # Convert to orbital elements
    return from_position_velocity(xyz_eci, v_eci, unix_s)
