"""
Deep behavioral tests for BasePreferenceManager and TopicRegistry.
Tests interest management, topic ignore, source boosting, default preferences,
topic registration, and trending topic calculation.
"""

from datetime import timedelta
from unittest.mock import Mock

from local_deep_research.news.preference_manager.base_preference import (
    BasePreferenceManager,
    TopicRegistry,
)
from local_deep_research.news.core.utils import utc_now


# --- Concrete implementation for testing ---


class ConcretePreferenceManager(BasePreferenceManager):
    """Concrete implementation for testing the abstract base class."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._store = {}

    def get_preferences(self, user_id):
        return self._store.get(user_id, self.get_default_preferences())

    def update_preferences(self, user_id, preferences):
        self._store[user_id] = preferences
        return preferences


# --- BasePreferenceManager init ---


class TestBasePreferenceManagerInit:
    """Tests for BasePreferenceManager initialization."""

    def test_stores_storage_backend(self):
        backend = Mock()
        pm = ConcretePreferenceManager(storage_backend=backend)
        assert pm.storage_backend is backend

    def test_storage_backend_none_by_default(self):
        pm = ConcretePreferenceManager()
        assert pm.storage_backend is None


# --- get_default_preferences ---


class TestGetDefaultPreferences:
    """Tests for default preferences structure."""

    def test_has_liked_categories(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["liked_categories"] == []

    def test_has_disliked_categories(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["disliked_categories"] == []

    def test_has_liked_topics(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["liked_topics"] == []

    def test_has_disliked_topics(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["disliked_topics"] == []

    def test_has_interests_dict(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["interests"] == {}

    def test_has_source_weights(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["source_weights"] == {}

    def test_has_impact_threshold(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["impact_threshold"] == 5

    def test_has_focus_preferences(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        focus = prefs["focus_preferences"]
        assert focus["breaking"] is True
        assert focus["surprising"] is False
        assert focus["positive"] is False
        assert focus["local"] is False

    def test_has_custom_search_terms(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["custom_search_terms"] == ""

    def test_has_search_strategy(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert prefs["search_strategy"] == "news_aggregation"

    def test_has_created_at(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert "created_at" in prefs

    def test_has_preferences_updated_at(self):
        pm = ConcretePreferenceManager()
        prefs = pm.get_default_preferences()
        assert "preferences_updated_at" in prefs


# --- add_interest ---


class TestAddInterest:
    """Tests for adding interests."""

    def test_adds_interest(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        prefs = pm.get_preferences("user1")
        assert "AI" in prefs["interests"]

    def test_default_weight_is_1(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        prefs = pm.get_preferences("user1")
        assert prefs["interests"]["AI"] == 1.0

    def test_custom_weight(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI", weight=3.5)
        prefs = pm.get_preferences("user1")
        assert prefs["interests"]["AI"] == 3.5

    def test_multiple_interests(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        pm.add_interest("user1", "Climate")
        prefs = pm.get_preferences("user1")
        assert "AI" in prefs["interests"]
        assert "Climate" in prefs["interests"]

    def test_overwrites_existing_interest(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI", weight=1.0)
        pm.add_interest("user1", "AI", weight=5.0)
        prefs = pm.get_preferences("user1")
        assert prefs["interests"]["AI"] == 5.0

    def test_sets_interests_updated_at(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        prefs = pm.get_preferences("user1")
        assert "interests_updated_at" in prefs


# --- remove_interest ---


class TestRemoveInterest:
    """Tests for removing interests."""

    def test_removes_existing_interest(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        pm.remove_interest("user1", "AI")
        prefs = pm.get_preferences("user1")
        assert "AI" not in prefs["interests"]

    def test_removes_nothing_if_not_found(self):
        pm = ConcretePreferenceManager()
        pm.add_interest("user1", "AI")
        pm.remove_interest("user1", "Nonexistent")
        prefs = pm.get_preferences("user1")
        assert "AI" in prefs["interests"]

    def test_removes_nothing_if_no_interests(self):
        pm = ConcretePreferenceManager()
        pm.remove_interest("user1", "AI")  # Should not raise


# --- ignore_topic ---


class TestIgnoreTopic:
    """Tests for ignoring topics."""

    def test_adds_to_disliked_topics(self):
        pm = ConcretePreferenceManager()
        pm.ignore_topic("user1", "Sports")
        prefs = pm.get_preferences("user1")
        assert "Sports" in prefs["disliked_topics"]

    def test_no_duplicates(self):
        pm = ConcretePreferenceManager()
        pm.ignore_topic("user1", "Sports")
        pm.ignore_topic("user1", "Sports")
        prefs = pm.get_preferences("user1")
        assert prefs["disliked_topics"].count("Sports") == 1

    def test_multiple_topics(self):
        pm = ConcretePreferenceManager()
        pm.ignore_topic("user1", "Sports")
        pm.ignore_topic("user1", "Politics")
        prefs = pm.get_preferences("user1")
        assert "Sports" in prefs["disliked_topics"]
        assert "Politics" in prefs["disliked_topics"]

    def test_sets_preferences_updated_at(self):
        pm = ConcretePreferenceManager()
        pm.ignore_topic("user1", "Sports")
        prefs = pm.get_preferences("user1")
        assert "preferences_updated_at" in prefs


# --- boost_source ---


class TestBoostSource:
    """Tests for source boosting."""

    def test_adds_source_weight(self):
        pm = ConcretePreferenceManager()
        pm.boost_source("user1", "reuters.com")
        prefs = pm.get_preferences("user1")
        assert "reuters.com" in prefs["source_weights"]

    def test_default_weight_is_1_5(self):
        pm = ConcretePreferenceManager()
        pm.boost_source("user1", "reuters.com")
        prefs = pm.get_preferences("user1")
        assert prefs["source_weights"]["reuters.com"] == 1.5

    def test_custom_weight(self):
        pm = ConcretePreferenceManager()
        pm.boost_source("user1", "reuters.com", weight=2.0)
        prefs = pm.get_preferences("user1")
        assert prefs["source_weights"]["reuters.com"] == 2.0

    def test_overwrites_existing_source(self):
        pm = ConcretePreferenceManager()
        pm.boost_source("user1", "reuters.com", weight=1.5)
        pm.boost_source("user1", "reuters.com", weight=3.0)
        prefs = pm.get_preferences("user1")
        assert prefs["source_weights"]["reuters.com"] == 3.0


# --- TopicRegistry ---


class TestTopicRegistryInit:
    """Tests for TopicRegistry initialization."""

    def test_empty_topics_initially(self):
        registry = TopicRegistry()
        assert registry.topics == {}

    def test_stores_llm_client(self):
        client = Mock()
        registry = TopicRegistry(llm_client=client)
        assert registry.llm_client is client

    def test_llm_client_none_by_default(self):
        registry = TopicRegistry()
        assert registry.llm_client is None


# --- register_topic ---


class TestRegisterTopic:
    """Tests for topic registration."""

    def test_registers_new_topic(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        assert "AI" in registry.topics

    def test_sets_first_seen(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        assert "first_seen" in registry.topics["AI"]

    def test_sets_count_to_1(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        assert registry.topics["AI"]["count"] == 1

    def test_increments_count(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        registry.register_topic("AI")
        registry.register_topic("AI")
        assert registry.topics["AI"]["count"] == 3

    def test_updates_last_seen(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        first_seen = registry.topics["AI"]["last_seen"]
        registry.register_topic("AI")
        assert registry.topics["AI"]["last_seen"] >= first_seen

    def test_first_seen_unchanged_on_reregister(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        first_seen = registry.topics["AI"]["first_seen"]
        registry.register_topic("AI")
        assert registry.topics["AI"]["first_seen"] == first_seen


# --- get_trending_topics ---


class TestGetTrendingTopics:
    """Tests for trending topic retrieval."""

    def test_empty_registry(self):
        registry = TopicRegistry()
        assert registry.get_trending_topics() == []

    def test_returns_recent_topics(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        registry.register_topic("Climate")
        result = registry.get_trending_topics(hours=24)
        assert "AI" in result
        assert "Climate" in result

    def test_sorted_by_count(self):
        registry = TopicRegistry()
        registry.register_topic("Rare")
        for _ in range(5):
            registry.register_topic("Popular")
        result = registry.get_trending_topics()
        assert result[0] == "Popular"

    def test_respects_limit(self):
        registry = TopicRegistry()
        for i in range(20):
            registry.register_topic(f"Topic {i}")
        result = registry.get_trending_topics(limit=5)
        assert len(result) == 5

    def test_filters_old_topics(self):
        registry = TopicRegistry()
        registry.register_topic("Old Topic")
        # Manually set last_seen to be old
        registry.topics["Old Topic"]["last_seen"] = utc_now() - timedelta(
            hours=48
        )
        result = registry.get_trending_topics(hours=24)
        assert "Old Topic" not in result

    def test_default_limit_is_10(self):
        registry = TopicRegistry()
        for i in range(15):
            registry.register_topic(f"Topic {i}")
        result = registry.get_trending_topics()
        assert len(result) == 10


# --- get_topic_info ---


class TestGetTopicInfo:
    """Tests for topic information retrieval."""

    def test_returns_none_for_unknown(self):
        registry = TopicRegistry()
        assert registry.get_topic_info("Unknown") is None

    def test_returns_info_for_known(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        info = registry.get_topic_info("AI")
        assert info is not None
        assert "first_seen" in info
        assert "count" in info
        assert "last_seen" in info

    def test_count_reflects_registrations(self):
        registry = TopicRegistry()
        registry.register_topic("AI")
        registry.register_topic("AI")
        info = registry.get_topic_info("AI")
        assert info["count"] == 2
