"""Create OrbitalElements from Two-Line Element (TLE) data.

This module parses TLE format data used by NORAD/Space-Track to distribute
satellite orbital data. The TLE contains mean orbital elements that are
fitted for use with the SGP4/SDP4 propagators.

IMPORTANT: TLE elements are "mean" elements specifically designed for SGP4
propagation. Using them with the Keplerian propagator in this library will
produce results that diverge from SGP4 over time. For accurate propagation
of TLE data, use a dedicated SGP4 library like python-sgp4.

TLE Format:
    Line 1: 1 NNNNNC NNNNNAAA NNNNN.NNNNNNNN +.NNNNNNNN +NNNNN-N +NNNNN-N N NNNNN
    Line 2: 2 NNNNN NNN.NNNN NNN.NNNN NNNNNNN NNN.NNNN NNN.NNNN NN.NNNNNNNNNNNNNN

References:
    - https://celestrak.org/NORAD/documentation/tle-fmt.php
    - Vallado, D. "Fundamentals of Astrodynamics and Applications", 4th ed.
    - AIAA 2006-6753 "Revisiting Spacetrack Report #3"
"""

from __future__ import annotations

import math
import re
from datetime import UTC, datetime, timedelta

from gri_utils.constants import GM_EARTH

from .orbital_elements import OrbitalElements


def from_tle(line1: str, line2: str) -> OrbitalElements:
    """Create OrbitalElements from TLE (Two-Line Element) data.

    Parses the standard TLE format and extracts orbital elements. The resulting
    elements are "mean" elements designed for SGP4 propagation.

    WARNING: The elements from a TLE are mean elements fitted for SGP4/SDP4
    propagators. Using them with simple Keplerian propagation (as in
    elements_to_state) will produce results that diverge from SGP4 predictions,
    especially for low-Earth orbits where atmospheric drag and J2 perturbations
    are significant. For accurate TLE propagation, use the python-sgp4 library.

    Args:
        line1: First line of TLE data (69 characters, starts with "1").
        line2: Second line of TLE data (69 characters, starts with "2").

    Returns:
        OrbitalElements extracted from the TLE. Note that these are mean elements
        and the epoch corresponds to the TLE epoch.

    Raises:
        ValueError: If the TLE format is invalid.

    Example:
        from gri_utils.orbit import from_tle
        # ISS (ZARYA) TLE
        line1 = "1 25544U 98067A   24001.50000000  .00016717  00000-0  10270-3 0  9993"
        line2 = "2 25544  51.6400  10.5000 0001234  90.0000 270.0000 15.49500000000000"
        elements = from_tle(line1, line2)
        print(f"Inclination: {elements.inclination_rad * 180 / 3.14159:.1f} deg")
    """
    # Basic validation
    line1 = line1.strip()
    line2 = line2.strip()

    if len(line1) < 69:  # noqa: PLR2004
        msg = f"Line 1 too short: {len(line1)} characters (need 69)"
        raise ValueError(msg)

    if len(line2) < 69:  # noqa: PLR2004
        msg = f"Line 2 too short: {len(line2)} characters (need 69)"
        raise ValueError(msg)

    if line1[0] != "1":
        msg = f"Line 1 must start with '1', got '{line1[0]}'"
        raise ValueError(msg)

    if line2[0] != "2":
        msg = f"Line 2 must start with '2', got '{line2[0]}'"
        raise ValueError(msg)

    # Parse Line 1
    # Columns are 1-indexed in TLE documentation, Python is 0-indexed
    # Epoch: columns 19-32 (0-indexed: 18:32)
    epoch_str = line1[18:32].strip()
    epoch_unix_s = _parse_tle_epoch(epoch_str)

    # Parse Line 2
    # Inclination: columns 9-16 (0-indexed: 8:16) in degrees
    inclination_deg = float(line2[8:16].strip())

    # RAAN: columns 18-25 (0-indexed: 17:25) in degrees
    raan_deg = float(line2[17:25].strip())

    # Eccentricity: columns 27-33 (0-indexed: 26:33) - implied decimal point
    eccentricity_str = line2[26:33].strip()
    eccentricity = float("0." + eccentricity_str)

    # Argument of perigee: columns 35-42 (0-indexed: 34:42) in degrees
    arg_perigee_deg = float(line2[34:42].strip())

    # Mean anomaly: columns 44-51 (0-indexed: 43:51) in degrees
    mean_anomaly_deg = float(line2[43:51].strip())

    # Mean motion: columns 53-63 (0-indexed: 52:63) in revolutions per day
    mean_motion_rev_day = float(line2[52:63].strip())

    # Convert mean motion to semi-major axis
    # n (rad/s) = mean_motion * 2*pi / 86400
    # a = (mu / n^2)^(1/3)
    mean_motion_rad_s = mean_motion_rev_day * 2.0 * math.pi / 86400.0

    # Import GM_EARTH here to avoid circular import at module level

    semi_major_axis_m = (GM_EARTH / (mean_motion_rad_s**2)) ** (1.0 / 3.0)

    # Convert angles to radians
    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 * deg_to_rad,
        arg_periapsis_rad=arg_perigee_deg * deg_to_rad,
        mean_anomaly_rad=mean_anomaly_deg * deg_to_rad,
        epoch_unix_s=epoch_unix_s,
    )


# -----------------------------------------------------------------------------
# Private helper functions
# -----------------------------------------------------------------------------


def _parse_tle_epoch(epoch_str: str) -> float:
    """Parse TLE epoch string (YYDDD.DDDDDDDD) to Unix timestamp.

    Args:
        epoch_str: Epoch string in format YYDDD.DDDDDDDD where YY is 2-digit year
            (00-56 = 2000-2056, 57-99 = 1957-1999) and DDD.DDDDDDDD is day of year.

    Returns:
        Unix timestamp in seconds.
    """
    year_2digit = int(epoch_str[:2])
    day_of_year = float(epoch_str[2:])

    # Year interpretation: 00-56 = 2000-2056, 57-99 = 1957-1999
    year = 2000 + year_2digit if year_2digit < 57 else 1900 + year_2digit  # noqa: PLR2004

    # Convert to datetime: January 1 of the year + (day_of_year - 1) days
    # day_of_year = 1 means January 1, so we add (day_of_year - 1) days
    jan1 = datetime(year, 1, 1, tzinfo=UTC)
    epoch_dt = jan1 + timedelta(days=day_of_year - 1)

    return epoch_dt.timestamp()


def _parse_decimal_with_assumed_point(s: str) -> float:
    """Parse a decimal number with assumed leading decimal point.

    TLE format uses implied decimal points for some fields, e.g., "12345-4"
    means 0.12345e-4 = 0.12345 * 10^-4.

    Args:
        s: String like "12345-4" meaning .12345e-4

    Returns:
        Float value.
    """
    s = s.strip()
    if not s:
        return 0.0

    # Handle sign at the beginning
    sign = 1.0
    if s[0] == "-":
        sign = -1.0
        s = s[1:]
    elif s[0] == "+":
        s = s[1:]

    # Find exponent (last sign in the string)
    exp_match = re.search(r"([+-])(\d+)$", s)
    if exp_match:
        mantissa_str = s[: exp_match.start()]
        exp_sign = 1 if exp_match.group(1) == "+" else -1
        exp_val = int(exp_match.group(2))
        exponent = exp_sign * exp_val
        mantissa = float("0." + mantissa_str)
        return sign * mantissa * (10**exponent)

    # No exponent, just implied decimal point
    return sign * float("0." + s)
