from datetime import UTC, datetime

import numpy as np
import pytest

from gri_utils.conversion import datetime_to_jd, jd_to_datetime, jd_to_unix, unix_to_jd
from gri_utils.conversion.julian import JD_J2000, JD_UNIX_EPOCH

# Known reference values for validation
# Unix epoch: 1970-01-01 00:00:00 UTC = JD 2440587.5
# J2000.0: 2000-01-01 12:00:00 UTC = JD 2451545.0


@pytest.mark.parametrize(
    ("unix_s", "expected_jd"),
    [
        (0.0, JD_UNIX_EPOCH),  # Unix epoch
        (946728000.0, JD_J2000),  # J2000.0 (2000-01-01 12:00:00 UTC)
        (86400.0, JD_UNIX_EPOCH + 1.0),  # One day after Unix epoch
        (-86400.0, JD_UNIX_EPOCH - 1.0),  # One day before Unix epoch
    ],
)
def test_unix_to_jd(unix_s: float, expected_jd: float) -> None:
    result = unix_to_jd(unix_s)
    assert result == pytest.approx(expected_jd)


@pytest.mark.parametrize(
    ("jd", "expected_unix"),
    [
        (JD_UNIX_EPOCH, 0.0),  # Unix epoch
        (JD_J2000, 946728000.0),  # J2000.0
        (JD_UNIX_EPOCH + 1.0, 86400.0),  # One day after Unix epoch
        (JD_UNIX_EPOCH - 1.0, -86400.0),  # One day before Unix epoch
    ],
)
def test_jd_to_unix(jd: float, expected_unix: float) -> None:
    result = jd_to_unix(jd)
    assert result == pytest.approx(expected_unix)


@pytest.mark.parametrize(
    ("dt", "expected_jd"),
    [
        (datetime(1970, 1, 1, 0, 0, 0, tzinfo=UTC), JD_UNIX_EPOCH),
        (datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC), JD_J2000),
        (datetime(1970, 1, 2, 0, 0, 0, tzinfo=UTC), JD_UNIX_EPOCH + 1.0),
    ],
)
def test_datetime_to_jd(dt: datetime, expected_jd: float) -> None:
    result = datetime_to_jd(dt)
    assert result == pytest.approx(expected_jd)


def test_datetime_to_jd_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_jd(dt_naive) == pytest.approx(datetime_to_jd(dt_utc))


@pytest.mark.parametrize(
    ("jd", "expected_dt"),
    [
        (JD_UNIX_EPOCH, datetime(1970, 1, 1, 0, 0, 0, tzinfo=UTC)),
        (JD_J2000, datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC)),
        (JD_UNIX_EPOCH + 1.0, datetime(1970, 1, 2, 0, 0, 0, tzinfo=UTC)),
    ],
)
def test_jd_to_datetime(jd: float, expected_dt: datetime) -> None:
    result = jd_to_datetime(jd)
    assert result == expected_dt


def test_roundtrip_unix() -> None:
    """Test unix -> jd -> unix roundtrip."""
    test_values = [0.0, 946728000.0, 1234567890.0, -86400.0]
    for unix_s in test_values:
        result = jd_to_unix(unix_to_jd(unix_s))
        assert result == pytest.approx(unix_s)


def test_roundtrip_jd() -> None:
    """Test jd -> unix -> jd roundtrip."""
    test_values = [JD_UNIX_EPOCH, JD_J2000, 2450000.0, 2460000.0]
    for jd in test_values:
        result = unix_to_jd(jd_to_unix(jd))
        assert result == pytest.approx(jd)


def test_roundtrip_datetime() -> None:
    """Test datetime -> jd -> datetime roundtrip."""
    test_dates = [
        datetime(1970, 1, 1, 0, 0, 0, tzinfo=UTC),
        datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC),
        datetime(2024, 6, 15, 8, 30, 45, tzinfo=UTC),
    ]
    for dt in test_dates:
        result = jd_to_datetime(datetime_to_jd(dt))
        # Compare timestamps - allow ~1ms tolerance for floating point precision
        assert result.timestamp() == pytest.approx(dt.timestamp(), abs=1e-3)


def test_vectorized_unix_to_jd() -> None:
    """Test that unix_to_jd works with arrays."""
    unix_arr = np.array([0.0, 86400.0, 946728000.0])
    expected = np.array([JD_UNIX_EPOCH, JD_UNIX_EPOCH + 1.0, JD_J2000])
    result = unix_to_jd(unix_arr)
    assert np.allclose(result, expected)


def test_vectorized_jd_to_unix() -> None:
    """Test that jd_to_unix works with arrays."""
    jd_arr = np.array([JD_UNIX_EPOCH, JD_UNIX_EPOCH + 1.0, JD_J2000])
    expected = np.array([0.0, 86400.0, 946728000.0])
    result = jd_to_unix(jd_arr)
    assert np.allclose(result, expected)


def test_vectorized_list_input() -> None:
    """Test that functions accept lists as input."""
    unix_list = [0.0, 86400.0]
    result = unix_to_jd(unix_list)
    assert isinstance(result, np.ndarray)
    assert len(result) == 2
