"""
Test module for the time module.

Authors: Sebastián Rodríguez-Martínez
Contact: srodriguez@mbari.org
Last Update: 2025-04-17
"""

import numpy as np
import pytest
from freezegun import freeze_time

from navlib import (
    datestr2utime,
    time_interval_indices,
    time_now,
    time_now_utc,
    timestamps_make_uniform,
    utime2datestr,
)
from navlib.time import datestr2utime as datestr2utime_relative
from navlib.time import time_interval_indices as time_interval_indices_relative
from navlib.time import time_now as time_now_relative
from navlib.time import time_now_utc as time_now_utc_relative
from navlib.time import utime2datestr as utime2datestr_relative


def test_imports():
    # Checks that the absolute and relative imports work correctly
    assert time_now_relative.__code__.co_code == time_now.__code__.co_code
    assert time_now_utc_relative.__code__.co_code == time_now_utc.__code__.co_code
    assert utime2datestr_relative.__code__.co_code == utime2datestr.__code__.co_code
    assert datestr2utime_relative.__code__.co_code == datestr2utime.__code__.co_code
    assert (
        time_interval_indices_relative.__code__.co_code
        == time_interval_indices.__code__.co_code
    )
    assert (
        timestamps_make_uniform.__code__.co_code
        == timestamps_make_uniform.__code__.co_code
    )


@freeze_time("2022-01-01")
def test_time_now():
    """
    Test case for the time_now function.
    """
    # Test case: Given example
    expected_result = (
        1640995200000000.0  # This is the timestamp for 2022-01-01 in microseconds
    )
    assert time_now() == expected_result


@freeze_time("2022-01-01")
def test_time_now_utc():
    """
    Test case for the time_now_utc function.
    """
    # Test case: Given example
    expected_result = (
        1640995200000000.0  # This is the timestamp for 2022-01-01 in microseconds
    )
    assert time_now_utc() == expected_result


class TestUtime2Datestr:
    def test_utime2datestr_scalar(self):
        """
        Test utime2datestr function for a single (scalar) timestamp.
        """
        utime = 859057200000000.0
        expected_result = "1997/03/22 19:00:00"
        assert utime2datestr(utime) == expected_result

    def test_utime2datestr_list(self):
        """
        Test utime2datestr for a list of timestamps.
        """
        utime_list = [859057200000000.0, 1590678950000000.0]
        expected_result = np.array(["1997/03/22 19:00:00", "2020/05/28 15:15:50"])
        result = utime2datestr(utime_list)
        np.testing.assert_array_equal(result, expected_result)

    def test_utime2datestr_array(self):
        """
        Test utime2datestr for a numpy array of timestamps.
        """
        utime_array = np.array([859057200000000.0, 1590678950000000.0])
        expected_result = np.array(["1997/03/22 19:00:00", "2020/05/28 15:15:50"])
        result = utime2datestr(utime_array)
        np.testing.assert_array_equal(result, expected_result)


class TestDatestr2Utime:
    def test_datestr2utime_scalar(self):
        """
        Test datestr2utime function for a single (scalar) date string.
        """
        datestr = "1997/03/22 19:00:00"
        expected_result = 859057200000000.0
        assert datestr2utime(datestr) == expected_result

    def test_datestr2utime_list(self):
        """
        Test datestr2utime function for a list of date strings.
        """
        datestr_list = ["1997/03/22 19:00:00", "2020/05/28 15:15:50"]
        expected_result = np.array([859057200000000.0, 1590678950000000.0])
        result = np.array(datestr2utime(datestr_list))
        np.testing.assert_array_equal(result, expected_result)

    def test_datestr2utime_array(self):
        """
        Test datestr2utime function for a numpy array of date strings.
        """
        datestr_array = np.array(["1997/03/22 19:00:00", "2020/05/28 15:15:50"])
        expected_result = np.array([859057200000000.0, 1590678950000000.0])
        result = np.array(datestr2utime(datestr_array))
        np.testing.assert_array_equal(result, expected_result)


class TestTimestampsMakeUniform:
    def test_uniform(self):
        # Provide unsorted timestamps with non-uniform intervals.
        inp = [30, 10, 20, 15]
        # Sorted becomes [10, 15, 20, 30] so the uniform timestamps
        # should be evenly spaced between 10 and 30 with 4 samples.
        result = timestamps_make_uniform(inp)
        expected = np.linspace(10, 30, num=4)
        np.testing.assert_allclose(result, expected, rtol=1e-6)

    def test_invalid_shape(self):
        inp = np.array([[1, 2], [3, 4]])
        with pytest.raises(ValueError):
            timestamps_make_uniform(inp)


class TestTimeIntervalIndices:
    def test_time_interval_indices_valid(self):
        """
        Test a valid sorted time array with t0 and tn inside the bounds.
        For a time array of 0-9 (inclusive), using t0=2 and tn=7 should return indices [2,3,4,5,6,7].
        """
        time = np.arange(0, 10)
        indices = time_interval_indices(time, 2, 7)
        expected = np.arange(2, 8)
        np.testing.assert_array_equal(indices, expected)

    def test_time_interval_indices_exceed_bounds(self):
        """
        Test when t0 is below the first element and tn is above the last element.
        The function should return all indices.
        """
        time = np.arange(5, 15)
        # t0=0 and tn=20 exceed the actual range; expect indices 0 to len(time)-1.
        indices = time_interval_indices(time, 0, 20)
        expected = np.arange(0, len(time))
        np.testing.assert_array_equal(indices, expected)

    def test_time_interval_indices_list_input(self):
        """
        Test that a list input gets converted properly.
        """
        time_list = list(np.arange(0, 10))
        indices = time_interval_indices(time_list, 3, 8)
        expected = np.arange(3, 9)
        np.testing.assert_array_equal(indices, expected)

    def test_time_interval_indices_invalid_shape(self):
        """
        Test that a multi-dimensional time array raises a ValueError.
        """
        time = np.array([[0, 1, 2], [3, 4, 5]])
        with pytest.raises(ValueError):
            time_interval_indices(time, 1, 4)

    def test_time_interval_indices_type_error(self):
        """
        Test that non-numeric t0 or tn raises TypeError.
        """
        time = np.arange(0, 10)
        with pytest.raises(TypeError):
            time_interval_indices(time, "a string", 5)
        with pytest.raises(TypeError):
            time_interval_indices(time, 1, None)

    def test_time_interval_indices_invalid_interval(self):
        """
        Test that tn <= t0 raises ValueError.
        """
        time = np.arange(0, 10)
        with pytest.raises(ValueError):
            time_interval_indices(time, 5, 2)
