"""
Deep behavioral tests for core utility functions.
Tests utc_now, hours_ago, generate_card_id, generate_subscription_id, and get_local_date_string.
"""

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


from local_deep_research.news.core.utils import (
    generate_card_id,
    generate_subscription_id,
    get_local_date_string,
    hours_ago,
    utc_now,
)


# --- utc_now ---


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

    def test_returns_datetime(self):
        result = utc_now()
        assert isinstance(result, datetime)

    def test_has_timezone(self):
        result = utc_now()
        assert result.tzinfo is not None

    def test_is_utc(self):
        result = utc_now()
        assert result.tzinfo == timezone.utc

    def test_is_close_to_now(self):
        before = datetime.now(timezone.utc)
        result = utc_now()
        after = datetime.now(timezone.utc)
        assert before <= result <= after

    def test_subsequent_calls_monotonic(self):
        t1 = utc_now()
        t2 = utc_now()
        assert t2 >= t1


# --- generate_card_id ---


class TestGenerateCardId:
    """Tests for generate_card_id."""

    def test_returns_string(self):
        result = generate_card_id()
        assert isinstance(result, str)

    def test_returns_valid_uuid(self):
        result = generate_card_id()
        uuid.UUID(result)  # Raises if invalid

    def test_unique_ids(self):
        ids = [generate_card_id() for _ in range(100)]
        assert len(set(ids)) == 100

    def test_non_empty(self):
        result = generate_card_id()
        assert len(result) > 0


# --- generate_subscription_id ---


class TestGenerateSubscriptionId:
    """Tests for generate_subscription_id."""

    def test_returns_string(self):
        result = generate_subscription_id()
        assert isinstance(result, str)

    def test_returns_valid_uuid(self):
        result = generate_subscription_id()
        uuid.UUID(result)

    def test_unique_ids(self):
        ids = [generate_subscription_id() for _ in range(100)]
        assert len(set(ids)) == 100

    def test_different_from_card_id(self):
        """Card IDs and subscription IDs should typically differ."""
        card_id = generate_card_id()
        sub_id = generate_subscription_id()
        assert card_id != sub_id


# --- hours_ago ---


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

    def test_recent_time(self):
        now = utc_now()
        result = hours_ago(now)
        assert abs(result) < 0.01  # Very close to 0

    def test_one_hour_ago(self):
        one_hour_ago = utc_now() - timedelta(hours=1)
        result = hours_ago(one_hour_ago)
        assert abs(result - 1.0) < 0.01

    def test_24_hours_ago(self):
        yesterday = utc_now() - timedelta(hours=24)
        result = hours_ago(yesterday)
        assert abs(result - 24.0) < 0.01

    def test_future_returns_negative(self):
        future = utc_now() + timedelta(hours=2)
        result = hours_ago(future)
        assert result < 0

    def test_handles_naive_datetime(self):
        """Test handles datetime without timezone by treating as UTC."""
        # Create a naive datetime that's 1 hour before current UTC
        naive = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(
            hours=1
        )
        result = hours_ago(naive)
        # Should be approximately 1 hour
        assert abs(result - 1.0) < 0.1

    def test_fractional_hours(self):
        half_hour_ago = utc_now() - timedelta(minutes=30)
        result = hours_ago(half_hour_ago)
        assert abs(result - 0.5) < 0.01

    def test_large_time_difference(self):
        week_ago = utc_now() - timedelta(days=7)
        result = hours_ago(week_ago)
        assert abs(result - 168.0) < 0.1  # 7 * 24 hours


# --- get_local_date_string ---


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

    def test_returns_string(self):
        result = get_local_date_string()
        assert isinstance(result, str)

    def test_returns_iso_format(self):
        result = get_local_date_string()
        # Should be YYYY-MM-DD format
        parts = result.split("-")
        assert len(parts) == 3
        assert len(parts[0]) == 4
        assert len(parts[1]) == 2
        assert len(parts[2]) == 2

    def test_parseable_date(self):
        result = get_local_date_string()
        datetime.fromisoformat(result)

    def test_uses_settings_manager_timezone(self):
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "America/New_York"
        result = get_local_date_string(settings_manager=mock_settings)
        assert isinstance(result, str)
        mock_settings.get_setting.assert_called_with("app.timezone")

    def test_falls_back_when_settings_raise(self):
        mock_settings = Mock()
        mock_settings.get_setting.side_effect = Exception("settings broken")
        result = get_local_date_string(settings_manager=mock_settings)
        # Should still return a valid date
        datetime.fromisoformat(result)

    @patch.dict(os.environ, {"TZ": "Europe/London"})
    def test_uses_tz_env_var(self):
        result = get_local_date_string()
        assert isinstance(result, str)
        datetime.fromisoformat(result)

    @patch.dict(os.environ, {"TZ": ""}, clear=False)
    def test_defaults_to_utc(self):
        result = get_local_date_string()
        assert isinstance(result, str)
        datetime.fromisoformat(result)

    def test_invalid_timezone_falls_back_to_utc(self):
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "Invalid/Timezone/Name"
        result = get_local_date_string(settings_manager=mock_settings)
        # Should fall back to UTC and still return a valid date
        datetime.fromisoformat(result)

    def test_settings_timezone_takes_priority_over_env(self):
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "Asia/Tokyo"
        with patch.dict(os.environ, {"TZ": "America/Chicago"}):
            result = get_local_date_string(settings_manager=mock_settings)
        # Should use settings timezone, not env
        assert isinstance(result, str)
