from datetime import UTC, datetime

import numpy as np
import pytest

from gri_utils.conversion import datetime_to_gmst, jd_to_gmst, unix_to_gmst
from gri_utils.conversion.julian import JD_J2000

# Reference value: GMST at J2000.0 epoch (2000-01-01 12:00:00 TT)
# The expected GMST at J2000.0 is approximately 18h 41m 50.55s
# Computed using IAU 2006 precession model
GMST_J2000_RAD = 4.8949612831508285


@pytest.mark.parametrize(
    ("jd", "expected_gmst"),
    [
        # J2000.0 epoch - well-known reference
        (JD_J2000, GMST_J2000_RAD),
        # One sidereal day later, GMST should be nearly the same (within 2*pi)
        # A sidereal day is ~86164.0905 seconds, or ~0.99727 solar days
    ],
)
def test_jd_to_gmst_reference(jd: float, expected_gmst: float) -> None:
    result = jd_to_gmst(jd)
    # Allow ~1 arcsecond tolerance (about 5e-6 radians)
    assert result == pytest.approx(expected_gmst, abs=5e-6)


def test_gmst_range() -> None:
    """GMST should always be in [0, 2*pi)."""
    test_jds = [JD_J2000, JD_J2000 + 0.5, JD_J2000 + 1.0, JD_J2000 + 100.0]
    for jd in test_jds:
        result = jd_to_gmst(jd)
        assert 0 <= result < 2 * np.pi


def test_gmst_progression() -> None:
    """GMST should increase by approximately 2*pi per sidereal day."""
    # One sidereal day is ~0.99726957 solar days
    sidereal_day_jd = 0.99726956632908
    jd1 = JD_J2000
    jd2 = JD_J2000 + sidereal_day_jd

    gmst1 = float(jd_to_gmst(jd1))
    gmst2 = float(jd_to_gmst(jd2))

    # After one sidereal day, GMST should be nearly the same (wrapped)
    # The difference should be very small (modulo 2*pi)
    diff = abs(gmst2 - gmst1)
    # Either very close to 0 or very close to 2*pi
    assert diff < 1e-4 or abs(diff - 2 * np.pi) < 1e-4


def test_unix_to_gmst() -> None:
    """Test unix_to_gmst gives same result as jd_to_gmst."""
    # J2000.0 in Unix time
    unix_j2000 = 946728000.0
    result_unix = unix_to_gmst(unix_j2000)
    result_jd = jd_to_gmst(JD_J2000)
    assert result_unix == pytest.approx(result_jd)


def test_datetime_to_gmst() -> None:
    """Test datetime_to_gmst gives same result as jd_to_gmst."""
    dt_j2000 = datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC)
    result_dt = datetime_to_gmst(dt_j2000)
    result_jd = float(jd_to_gmst(JD_J2000))
    assert result_dt == pytest.approx(result_jd)


def test_datetime_to_gmst_naive() -> None:
    """Naive datetime should be treated as UTC."""
    dt_naive = datetime(2000, 1, 1, 12, 0, 0)  # noqa: DTZ001
    dt_utc = datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC)
    assert datetime_to_gmst(dt_naive) == pytest.approx(datetime_to_gmst(dt_utc))


def test_vectorized_jd_to_gmst() -> None:
    """Test that jd_to_gmst works with arrays."""
    jd_arr = np.array([JD_J2000, JD_J2000 + 0.5, JD_J2000 + 1.0])
    result = jd_to_gmst(jd_arr)
    assert isinstance(result, np.ndarray)
    assert len(result) == 3
    # All values should be in [0, 2*pi)
    assert np.all(result >= 0)
    assert np.all(result < 2 * np.pi)


def test_vectorized_unix_to_gmst() -> None:
    """Test that unix_to_gmst works with arrays."""
    unix_arr = np.array([946728000.0, 946728000.0 + 43200.0])  # J2000 and 12h later
    result = unix_to_gmst(unix_arr)
    assert isinstance(result, np.ndarray)
    assert len(result) == 2


def test_vectorized_list_input() -> None:
    """Test that functions accept lists as input."""
    jd_list = [JD_J2000, JD_J2000 + 1.0]
    result = jd_to_gmst(jd_list)
    assert isinstance(result, np.ndarray)
    assert len(result) == 2


def test_half_day_gmst_change() -> None:
    """GMST should change by approximately pi radians over half a solar day."""
    jd1 = JD_J2000
    # Half solar day in Julian days
    half_solar_day = 0.5

    gmst1 = float(jd_to_gmst(jd1))
    gmst2 = float(jd_to_gmst(jd1 + half_solar_day))

    # The change should be slightly more than pi radians (due to sidereal vs solar)
    # In half a solar day, Earth rotates ~180.5 degrees in sidereal terms
    diff = gmst2 - gmst1
    if diff < 0:
        diff += 2 * np.pi
    # Allow tolerance for the sidereal/solar difference (~0.5 degrees = 0.009 rad)
    assert diff == pytest.approx(np.pi, abs=0.01)
