"""
Deep behavioral tests for _format_time_ago utility function.
Tests time formatting, edge cases, pluralization, timezone handling, and error recovery.
"""

from datetime import datetime, timedelta, timezone

import pytest

from local_deep_research.news.api import _format_time_ago


# --- Days ago ---


class TestFormatTimeAgoDays:
    """Tests for day-based formatting."""

    def test_one_day_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
        result = _format_time_ago(ts)
        assert result == "1 day ago"

    def test_two_days_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=2)).isoformat()
        result = _format_time_ago(ts)
        assert result == "2 days ago"

    def test_seven_days_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
        result = _format_time_ago(ts)
        assert result == "7 days ago"

    def test_thirty_days_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat()
        result = _format_time_ago(ts)
        assert result == "30 days ago"

    def test_one_day_no_plural(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
        result = _format_time_ago(ts)
        assert "days" not in result
        assert "day" in result

    def test_three_days_plural(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=3)).isoformat()
        result = _format_time_ago(ts)
        assert "days" in result

    def test_365_days_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(days=365)).isoformat()
        result = _format_time_ago(ts)
        assert "365 days ago" == result


# --- Hours ago ---


class TestFormatTimeAgoHours:
    """Tests for hour-based formatting."""

    def test_two_hours_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=2)).isoformat()
        result = _format_time_ago(ts)
        assert result == "2 hours ago"

    def test_one_hour_ago(self):
        ts = (
            datetime.now(timezone.utc) - timedelta(hours=1, minutes=30)
        ).isoformat()
        result = _format_time_ago(ts)
        assert result == "1 hour ago"

    def test_one_hour_no_plural(self):
        ts = (
            datetime.now(timezone.utc) - timedelta(hours=1, minutes=30)
        ).isoformat()
        result = _format_time_ago(ts)
        assert "hours" not in result
        assert "hour" in result

    def test_twelve_hours_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=12)).isoformat()
        result = _format_time_ago(ts)
        assert result == "12 hours ago"

    def test_twenty_three_hours_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=23)).isoformat()
        result = _format_time_ago(ts)
        assert result == "23 hours ago"

    def test_six_hours_plural(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=6)).isoformat()
        result = _format_time_ago(ts)
        assert "hours" in result


# --- Minutes ago ---


class TestFormatTimeAgoMinutes:
    """Tests for minute-based formatting."""

    def test_five_minutes_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
        result = _format_time_ago(ts)
        assert result == "5 minutes ago"

    def test_one_minute_ago(self):
        ts = (
            datetime.now(timezone.utc) - timedelta(minutes=1, seconds=30)
        ).isoformat()
        result = _format_time_ago(ts)
        assert result == "1 minute ago"

    def test_one_minute_no_plural(self):
        ts = (
            datetime.now(timezone.utc) - timedelta(minutes=1, seconds=30)
        ).isoformat()
        result = _format_time_ago(ts)
        assert "minutes" not in result
        assert "minute" in result

    def test_thirty_minutes_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(minutes=30)).isoformat()
        result = _format_time_ago(ts)
        assert result == "30 minutes ago"

    def test_fifty_nine_minutes_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(minutes=59)).isoformat()
        result = _format_time_ago(ts)
        assert result == "59 minutes ago"


# --- Just now ---


class TestFormatTimeAgoJustNow:
    """Tests for 'Just now' output."""

    def test_zero_seconds(self):
        ts = datetime.now(timezone.utc).isoformat()
        result = _format_time_ago(ts)
        assert result == "Just now"

    def test_thirty_seconds_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(seconds=30)).isoformat()
        result = _format_time_ago(ts)
        assert result == "Just now"

    def test_fifty_nine_seconds_ago(self):
        ts = (datetime.now(timezone.utc) - timedelta(seconds=59)).isoformat()
        result = _format_time_ago(ts)
        assert result == "Just now"


# --- Timezone handling ---


class TestFormatTimeAgoTimezone:
    """Tests for timezone handling."""

    def test_utc_timestamp(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=3)).isoformat()
        result = _format_time_ago(ts)
        assert result == "3 hours ago"

    def test_z_suffix(self):
        dt = datetime.now(timezone.utc) - timedelta(hours=2)
        ts = dt.strftime("%Y-%m-%dT%H:%M:%SZ")
        result = _format_time_ago(ts)
        assert "2 hours ago" == result

    def test_plus_offset(self):
        dt = datetime.now(timezone.utc) - timedelta(hours=5)
        ts = dt.strftime("%Y-%m-%dT%H:%M:%S+00:00")
        result = _format_time_ago(ts)
        assert result == "5 hours ago"

    def test_naive_datetime_assumed_utc(self):
        """Naive timestamps should be treated as UTC."""
        dt = datetime.now(timezone.utc) - timedelta(hours=4)
        # Strip timezone info to make it naive
        ts = dt.strftime("%Y-%m-%dT%H:%M:%S")
        result = _format_time_ago(ts)
        assert result == "4 hours ago"


# --- Error handling ---


class TestFormatTimeAgoErrorHandling:
    """Unparseable input raises rather than returning a neutral label.

    created_at is NOT NULL and always written as isoformat(), so anything
    that won't parse is corrupt data. The per-row loop in get_news_feed
    catches the raise, logs it, and skips the row.
    """

    def test_invalid_string_raises(self):
        with pytest.raises(ValueError):
            _format_time_ago("not-a-timestamp")

    def test_empty_string_raises(self):
        with pytest.raises(ValueError):
            _format_time_ago("")

    def test_none_raises(self):
        with pytest.raises(TypeError):
            _format_time_ago(None)

    def test_integer_raises(self):
        with pytest.raises(TypeError):
            _format_time_ago(12345)

    def test_gibberish_raises(self):
        with pytest.raises(ValueError):
            _format_time_ago("xyz123!@#")


# --- Input format variants ---


class TestFormatTimeAgoInputFormats:
    """Tests for various timestamp format inputs."""

    def test_iso_format(self):
        ts = (
            datetime.now(timezone.utc) - timedelta(hours=1, minutes=30)
        ).isoformat()
        result = _format_time_ago(ts)
        assert "hour" in result

    def test_date_only_string(self):
        # Yesterday's date
        yesterday = (datetime.now(timezone.utc) - timedelta(days=1)).strftime(
            "%Y-%m-%d"
        )
        result = _format_time_ago(yesterday)
        assert "day" in result

    def test_datetime_object_as_string(self):
        dt = datetime.now(timezone.utc) - timedelta(minutes=10)
        result = _format_time_ago(str(dt))
        assert "10 minutes ago" == result

    def test_microseconds_in_timestamp(self):
        ts = (datetime.now(timezone.utc) - timedelta(hours=2)).isoformat()
        result = _format_time_ago(ts)
        assert result == "2 hours ago"


# --- Boundary conditions ---


class TestFormatTimeAgoBoundaries:
    """Tests for boundary conditions between time ranges."""

    def test_exactly_60_seconds(self):
        """60 seconds should show as 1 minute, not 'Just now'."""
        ts = (datetime.now(timezone.utc) - timedelta(seconds=61)).isoformat()
        result = _format_time_ago(ts)
        assert "minute" in result

    def test_exactly_3600_seconds(self):
        """3600 seconds should show as 1 hour, not minutes."""
        ts = (datetime.now(timezone.utc) - timedelta(seconds=3601)).isoformat()
        result = _format_time_ago(ts)
        assert "hour" in result

    def test_just_under_one_day(self):
        """23 hours 59 min should show hours, not days."""
        ts = (
            datetime.now(timezone.utc) - timedelta(hours=23, minutes=59)
        ).isoformat()
        result = _format_time_ago(ts)
        assert "hour" in result

    def test_exactly_one_day(self):
        """24 hours should show as 1 day."""
        ts = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
        result = _format_time_ago(ts)
        assert "1 day ago" == result

    def test_just_over_one_hour(self):
        """1 hour 1 second should show 1 hour."""
        ts = (
            datetime.now(timezone.utc) - timedelta(hours=1, seconds=1)
        ).isoformat()
        result = _format_time_ago(ts)
        assert "1 hour ago" == result
