"""Tests for app.utils.time_utils."""

import datetime
import time

from app.utils.time_utils import (
    add_minutes,
    hours_ago,
    is_expired,
    since_unix,
    utc_from_timestamp,
    utc_now,
)
from app.utils.constants import TIME_RANGE_SLACK_SECONDS


class TestUtcNow:
    """Tests for :func:`utc_now`."""

    def test_utc_now_returns_timezone_aware_datetime(self) -> None:
        result = utc_now()
        assert result.tzinfo is not None

    def test_utc_now_timezone_is_utc(self) -> None:
        result = utc_now()
        assert result.tzinfo == datetime.UTC

    def test_utc_now_is_recent(self) -> None:
        before = datetime.datetime.now(datetime.UTC)
        result = utc_now()
        after = datetime.datetime.now(datetime.UTC)
        assert before <= result <= after


class TestUtcFromTimestamp:
    """Tests for :func:`utc_from_timestamp`."""

    def test_utc_from_timestamp_epoch_returns_utc_epoch(self) -> None:
        result = utc_from_timestamp(0.0)
        assert result == datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC)

    def test_utc_from_timestamp_returns_aware_datetime(self) -> None:
        result = utc_from_timestamp(1_000_000_000.0)
        assert result.tzinfo is not None


class TestAddMinutes:
    """Tests for :func:`add_minutes`."""

    def test_add_minutes_positive(self) -> None:
        dt = datetime.datetime(2024, 1, 1, 12, 0, 0, tzinfo=datetime.UTC)
        result = add_minutes(dt, 30)
        expected = datetime.datetime(2024, 1, 1, 12, 30, 0, tzinfo=datetime.UTC)
        assert result == expected

    def test_add_minutes_negative(self) -> None:
        dt = datetime.datetime(2024, 1, 1, 12, 0, 0, tzinfo=datetime.UTC)
        result = add_minutes(dt, -60)
        expected = datetime.datetime(2024, 1, 1, 11, 0, 0, tzinfo=datetime.UTC)
        assert result == expected


class TestIsExpired:
    """Tests for :func:`is_expired`."""

    def test_is_expired_past_timestamp_returns_true(self) -> None:
        past = datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC)
        assert is_expired(past) is True

    def test_is_expired_future_timestamp_returns_false(self) -> None:
        future = datetime.datetime(2099, 1, 1, tzinfo=datetime.UTC)
        assert is_expired(future) is False


class TestHoursAgo:
    """Tests for :func:`hours_ago`."""

    def test_hours_ago_returns_past_datetime(self) -> None:
        result = hours_ago(24)
        assert result < utc_now()

    def test_hours_ago_correct_delta(self) -> None:
        before = utc_now()
        result = hours_ago(1)
        after = utc_now()
        expected_min = before - datetime.timedelta(hours=1, seconds=1)
        expected_max = after - datetime.timedelta(hours=1) + datetime.timedelta(seconds=1)
        assert expected_min <= result <= expected_max


class TestSinceUnix:
    """Tests for :func:`since_unix`."""

    def test_since_unix_24h_returns_unix_timestamp(self) -> None:
        """Verify since_unix returns an integer timestamp."""
        result = since_unix("24h")
        assert isinstance(result, int)

    def test_since_unix_24h_is_roughly_24_hours_ago(self) -> None:
        """Verify 24h preset returns a timestamp ~24 hours in the past."""
        before = int(time.time())
        result = since_unix("24h")
        after = int(time.time())

        # Allow 1 second tolerance for execution time
        expected_min = before - (24 * 3600) - TIME_RANGE_SLACK_SECONDS - 1
        expected_max = after - (24 * 3600) - TIME_RANGE_SLACK_SECONDS + 1

        assert expected_min <= result <= expected_max

    def test_since_unix_7d_is_roughly_7_days_ago(self) -> None:
        """Verify 7d preset returns a timestamp ~7 days in the past."""
        before = int(time.time())
        result = since_unix("7d")
        after = int(time.time())

        # Allow 1 second tolerance for execution time
        expected_min = before - (7 * 24 * 3600) - TIME_RANGE_SLACK_SECONDS - 1
        expected_max = after - (7 * 24 * 3600) - TIME_RANGE_SLACK_SECONDS + 1

        assert expected_min <= result <= expected_max

    def test_since_unix_includes_slack_window(self) -> None:
        """Verify 60-second slack is included in all presets."""
        now = int(time.time())
        result = since_unix("24h")

        # Verify slack is included: result should be (now - 24h - 60s)
        # within tolerance
        diff_without_slack = now - result
        expected_without_slack = 24 * 3600
        actual_slack = diff_without_slack - expected_without_slack

        # The slack should be ~60 seconds
        assert actual_slack >= TIME_RANGE_SLACK_SECONDS - 1
        assert actual_slack <= TIME_RANGE_SLACK_SECONDS + 1
