"""
Extended tests for news/core/utils.py

Tests cover:
- get_local_date_string() function
- generate_card_id() function
- generate_subscription_id() function
- utc_now() function
- hours_ago() function
"""

from unittest.mock import Mock, patch
from datetime import datetime, timezone, timedelta
import os


class TestGetLocalDateString:
    """Tests for get_local_date_string() function."""

    def test_returns_string(self):
        """Returns a string."""
        from local_deep_research.news.core.utils import get_local_date_string

        result = get_local_date_string()

        assert isinstance(result, str)

    def test_returns_iso_format_date(self):
        """Returns date in ISO format (YYYY-MM-DD)."""
        from local_deep_research.news.core.utils import get_local_date_string

        result = get_local_date_string()

        # Should match YYYY-MM-DD pattern
        parts = result.split("-")
        assert len(parts) == 3
        assert len(parts[0]) == 4  # Year
        assert len(parts[1]) == 2  # Month
        assert len(parts[2]) == 2  # Day

    def test_uses_settings_manager_timezone(self):
        """Uses timezone from settings_manager."""
        from local_deep_research.news.core.utils import get_local_date_string

        mock_manager = Mock()
        mock_manager.get_setting.return_value = "America/New_York"

        result = get_local_date_string(mock_manager)

        mock_manager.get_setting.assert_called_with("app.timezone")
        assert isinstance(result, str)

    def test_falls_back_to_tz_env_var(self):
        """Falls back to TZ environment variable."""
        from local_deep_research.news.core.utils import get_local_date_string

        with patch.dict(os.environ, {"TZ": "UTC"}):
            result = get_local_date_string()

            assert isinstance(result, str)

    def test_defaults_to_utc(self):
        """Defaults to UTC when no timezone configured."""
        from local_deep_research.news.core.utils import get_local_date_string

        with patch.dict(os.environ, {}, clear=True):
            # Remove TZ from environment
            if "TZ" in os.environ:
                del os.environ["TZ"]

            result = get_local_date_string()

            assert isinstance(result, str)

    def test_handles_invalid_timezone(self):
        """Handles invalid timezone gracefully."""
        from local_deep_research.news.core.utils import get_local_date_string

        mock_manager = Mock()
        mock_manager.get_setting.return_value = "Invalid/Timezone"

        result = get_local_date_string(mock_manager)

        # Should still return a valid date (falls back to UTC)
        assert isinstance(result, str)
        parts = result.split("-")
        assert len(parts) == 3

    def test_handles_settings_exception(self):
        """Handles exception from settings_manager."""
        from local_deep_research.news.core.utils import get_local_date_string

        mock_manager = Mock()
        mock_manager.get_setting.side_effect = Exception("DB error")

        result = get_local_date_string(mock_manager)

        # Should still return a valid date
        assert isinstance(result, str)


class TestGenerateCardId:
    """Tests for generate_card_id() function."""

    def test_returns_string(self):
        """Returns a string."""
        from local_deep_research.news.core.utils import generate_card_id

        result = generate_card_id()

        assert isinstance(result, str)

    def test_returns_uuid_format(self):
        """Returns UUID format string."""
        from local_deep_research.news.core.utils import generate_card_id
        import uuid

        result = generate_card_id()

        # Should be valid UUID
        uuid.UUID(result)  # Will raise if invalid

    def test_generates_unique_ids(self):
        """Generates unique IDs."""
        from local_deep_research.news.core.utils import generate_card_id

        ids = [generate_card_id() for _ in range(100)]

        # All should be unique
        assert len(set(ids)) == 100

    def test_id_length(self):
        """ID has correct length (36 chars for UUID)."""
        from local_deep_research.news.core.utils import generate_card_id

        result = generate_card_id()

        assert len(result) == 36


class TestGenerateSubscriptionId:
    """Tests for generate_subscription_id() function."""

    def test_returns_string(self):
        """Returns a string."""
        from local_deep_research.news.core.utils import generate_subscription_id

        result = generate_subscription_id()

        assert isinstance(result, str)

    def test_returns_uuid_format(self):
        """Returns UUID format string."""
        from local_deep_research.news.core.utils import generate_subscription_id
        import uuid

        result = generate_subscription_id()

        # Should be valid UUID
        uuid.UUID(result)  # Will raise if invalid

    def test_generates_unique_ids(self):
        """Generates unique IDs."""
        from local_deep_research.news.core.utils import generate_subscription_id

        ids = [generate_subscription_id() for _ in range(100)]

        # All should be unique
        assert len(set(ids)) == 100

    def test_id_length(self):
        """ID has correct length (36 chars for UUID)."""
        from local_deep_research.news.core.utils import generate_subscription_id

        result = generate_subscription_id()

        assert len(result) == 36


class TestUtcNow:
    """Tests for utc_now() function."""

    def test_returns_datetime(self):
        """Returns datetime object."""
        from local_deep_research.news.core.utils import utc_now

        result = utc_now()

        assert isinstance(result, datetime)

    def test_is_timezone_aware(self):
        """Returns timezone-aware datetime."""
        from local_deep_research.news.core.utils import utc_now

        result = utc_now()

        assert result.tzinfo is not None

    def test_is_utc(self):
        """Returns UTC timezone."""
        from local_deep_research.news.core.utils import utc_now

        result = utc_now()

        assert result.tzinfo == timezone.utc

    def test_is_current_time(self):
        """Returns approximately current time."""
        from local_deep_research.news.core.utils import utc_now

        before = datetime.now(timezone.utc)
        result = utc_now()
        after = datetime.now(timezone.utc)

        assert before <= result <= after


class TestHoursAgo:
    """Tests for hours_ago() function."""

    def test_returns_float(self):
        """Returns a float."""
        from local_deep_research.news.core.utils import hours_ago

        dt = datetime.now(timezone.utc)
        result = hours_ago(dt)

        assert isinstance(result, float)

    def test_one_hour_ago(self):
        """Returns 1.0 for datetime 1 hour ago."""
        from local_deep_research.news.core.utils import hours_ago

        one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1)
        result = hours_ago(one_hour_ago)

        assert 0.99 < result < 1.01

    def test_two_hours_ago(self):
        """Returns 2.0 for datetime 2 hours ago."""
        from local_deep_research.news.core.utils import hours_ago

        two_hours_ago = datetime.now(timezone.utc) - timedelta(hours=2)
        result = hours_ago(two_hours_ago)

        assert 1.99 < result < 2.01

    def test_future_datetime(self):
        """Returns negative for future datetime."""
        from local_deep_research.news.core.utils import hours_ago

        future = datetime.now(timezone.utc) + timedelta(hours=1)
        result = hours_ago(future)

        assert result < 0

    def test_handles_naive_datetime(self):
        """Handles naive datetime (assumes UTC)."""
        from local_deep_research.news.core.utils import hours_ago

        naive_dt = datetime.utcnow() - timedelta(hours=1)
        result = hours_ago(naive_dt)

        # Should be approximately 1 hour
        assert 0.9 < result < 1.1

    def test_zero_hours_ago(self):
        """Returns approximately 0 for current time."""
        from local_deep_research.news.core.utils import hours_ago

        now = datetime.now(timezone.utc)
        result = hours_ago(now)

        assert abs(result) < 0.01

    def test_24_hours_ago(self):
        """Returns 24.0 for datetime 24 hours ago."""
        from local_deep_research.news.core.utils import hours_ago

        day_ago = datetime.now(timezone.utc) - timedelta(hours=24)
        result = hours_ago(day_ago)

        assert 23.99 < result < 24.01

    def test_fractional_hours(self):
        """Returns fractional hours."""
        from local_deep_research.news.core.utils import hours_ago

        ninety_mins_ago = datetime.now(timezone.utc) - timedelta(minutes=90)
        result = hours_ago(ninety_mins_ago)

        assert 1.49 < result < 1.51


class TestUtilsEdgeCases:
    """Edge case tests for utility functions."""

    def test_generate_card_id_thread_safe(self):
        """generate_card_id is thread-safe."""
        import threading

        from local_deep_research.news.core.utils import generate_card_id

        ids = []
        errors = []

        def generate():
            try:
                for _ in range(100):
                    ids.append(generate_card_id())
            except Exception as e:
                errors.append(e)

        threads = [threading.Thread(target=generate) for _ in range(10)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        assert len(errors) == 0
        assert len(set(ids)) == len(ids)  # All unique

    def test_hours_ago_with_different_timezones(self):
        """hours_ago works with different timezones."""
        from local_deep_research.news.core.utils import hours_ago
        from zoneinfo import ZoneInfo

        # Create datetime in different timezone
        ny_tz = ZoneInfo("America/New_York")
        ny_time = datetime.now(ny_tz) - timedelta(hours=1)

        result = hours_ago(ny_time)

        assert 0.9 < result < 1.1

    def test_get_local_date_string_with_none_settings(self):
        """get_local_date_string handles None settings_manager."""
        from local_deep_research.news.core.utils import get_local_date_string

        result = get_local_date_string(None)

        assert isinstance(result, str)

    def test_utc_now_consistency(self):
        """utc_now returns consistent results."""
        from local_deep_research.news.core.utils import utc_now

        times = [utc_now() for _ in range(100)]

        # All should be within 1 second of each other
        for i in range(len(times) - 1):
            diff = abs((times[i + 1] - times[i]).total_seconds())
            assert diff < 1.0

    def test_hours_ago_very_old_date(self):
        """hours_ago handles very old dates."""
        from local_deep_research.news.core.utils import hours_ago

        old_date = datetime(2000, 1, 1, tzinfo=timezone.utc)
        result = hours_ago(old_date)

        # Should be many hours (years worth)
        assert result > 8760 * 20  # More than 20 years in hours
