"""
Extended tests for news/recommender/topic_based.py

Tests cover:
- TopicBasedRecommender initialization
- generate_recommendations() method
- _get_trending_topics() method
- _filter_topics_by_preferences() method
- _generate_topic_query() method
- _create_recommendation_card() method
- SearchBasedRecommender class
- Edge cases and error handling
"""

from unittest.mock import MagicMock, patch


class TestTopicBasedRecommenderInit:
    """Tests for TopicBasedRecommender initialization."""

    def test_init_sets_max_recommendations_default(self):
        """Default max_recommendations is 5."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        assert recommender.max_recommendations == 5

    def test_init_inherits_from_base_recommender(self):
        """TopicBasedRecommender inherits from BaseRecommender."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        recommender = TopicBasedRecommender()
        assert isinstance(recommender, BaseRecommender)

    def test_init_accepts_kwargs(self):
        """Init accepts and passes kwargs to parent."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        mock_registry = MagicMock()
        recommender = TopicBasedRecommender(topic_registry=mock_registry)
        assert recommender.topic_registry is mock_registry

    def test_init_has_strategy_name(self):
        """Recommender has a strategy name attribute."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        assert hasattr(recommender, "strategy_name")


class TestTopicBasedRecommenderGenerateRecommendations:
    """Tests for TopicBasedRecommender.generate_recommendations() method."""

    def test_returns_list(self):
        """Returns a list of recommendations."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch.object(recommender, "_get_trending_topics", return_value=[]):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                result = recommender.generate_recommendations("user123")

        assert isinstance(result, list)

    def test_calls_get_trending_topics(self):
        """Calls _get_trending_topics with context."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        context = {"current_category": "Technology"}

        with patch.object(
            recommender, "_get_trending_topics", return_value=[]
        ) as mock_get:
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                recommender.generate_recommendations("user123", context=context)

        mock_get.assert_called_once_with(context)

    def test_calls_get_user_preferences(self):
        """Calls _get_user_preferences with user_id."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch.object(recommender, "_get_trending_topics", return_value=[]):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ) as mock_get:
                recommender.generate_recommendations("user-456")

        mock_get.assert_called_once_with("user-456")

    def test_filters_topics_by_preferences(self):
        """Calls _filter_topics_by_preferences."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["Topic A", "Topic B"]
        preferences = {"interests": {"tech": 1.5}}

        with patch.object(
            recommender, "_get_trending_topics", return_value=topics
        ):
            with patch.object(
                recommender, "_get_user_preferences", return_value=preferences
            ):
                with patch.object(
                    recommender,
                    "_filter_topics_by_preferences",
                    return_value=[],
                ) as mock_filter:
                    recommender.generate_recommendations("user123")

        mock_filter.assert_called_once_with(topics, preferences)

    def test_limits_to_max_recommendations(self):
        """Only processes up to max_recommendations topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        recommender.max_recommendations = 2

        topics = ["Topic1", "Topic2", "Topic3", "Topic4", "Topic5"]
        cards_created = []

        def track_creation(*args, **kwargs):
            cards_created.append(args[0])
            return

        with patch.object(
            recommender, "_get_trending_topics", return_value=topics
        ):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                with patch.object(
                    recommender,
                    "_filter_topics_by_preferences",
                    return_value=topics,
                ):
                    with patch.object(
                        recommender,
                        "_create_recommendation_card",
                        side_effect=track_creation,
                    ):
                        recommender.generate_recommendations("user123")

        # Should only create cards for first 2 topics
        assert len(cards_created) == 2

    def test_handles_create_recommendation_exception(self):
        """Handles exception in _create_recommendation_card gracefully."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch.object(
            recommender, "_get_trending_topics", return_value=["Topic"]
        ):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                with patch.object(
                    recommender,
                    "_filter_topics_by_preferences",
                    return_value=["Topic"],
                ):
                    with patch.object(
                        recommender,
                        "_create_recommendation_card",
                        side_effect=Exception("Search failed"),
                    ):
                        # Should not raise
                        result = recommender.generate_recommendations("user123")

        assert result == []

    def test_updates_progress(self):
        """Updates progress during recommendation generation."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        progress_updates = []

        def track_progress(message, percent, metadata=None):
            progress_updates.append((message, percent))

        recommender.progress_callback = track_progress

        with patch.object(recommender, "_get_trending_topics", return_value=[]):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                recommender.generate_recommendations("user123")

        # Should have progress updates
        assert len(progress_updates) > 0

    def test_returns_empty_on_exception(self):
        """Returns empty list on exception."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch.object(
            recommender,
            "_get_trending_topics",
            side_effect=Exception("DB error"),
        ):
            result = recommender.generate_recommendations("user123")

        assert result == []

    def test_appends_valid_cards(self):
        """Appends valid cards to recommendations."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        mock_card = MagicMock()

        with patch.object(
            recommender, "_get_trending_topics", return_value=["Topic A"]
        ):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                with patch.object(
                    recommender,
                    "_filter_topics_by_preferences",
                    return_value=["Topic A"],
                ):
                    with patch.object(
                        recommender,
                        "_create_recommendation_card",
                        return_value=mock_card,
                    ):
                        with patch.object(
                            recommender,
                            "_sort_by_relevance",
                            return_value=[mock_card],
                        ):
                            result = recommender.generate_recommendations(
                                "user123"
                            )

        assert len(result) == 1
        assert result[0] is mock_card


class TestGetTrendingTopics:
    """Tests for TopicBasedRecommender._get_trending_topics() method."""

    def test_returns_list(self):
        """Returns a list of topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._get_trending_topics(None)

        assert isinstance(result, list)

    def test_gets_topics_from_registry(self):
        """Gets topics from topic_registry if available."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        mock_registry = MagicMock()
        mock_registry.get_trending_topics.return_value = ["AI", "Climate"]

        recommender = TopicBasedRecommender(topic_registry=mock_registry)
        result = recommender._get_trending_topics(None)

        mock_registry.get_trending_topics.assert_called_once_with(
            hours=24, limit=20
        )
        assert "AI" in result
        assert "Climate" in result

    def test_adds_context_topics(self):
        """Adds topics from context if provided."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        context = {"current_news_topics": ["Topic X", "Topic Y"]}

        result = recommender._get_trending_topics(context)

        assert "Topic X" in result
        assert "Topic Y" in result

    def test_returns_defaults_when_no_topics(self):
        """Returns default topics when none found."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._get_trending_topics(None)

        # Should have default topics
        assert len(result) > 0
        assert "artificial intelligence developments" in result

    def test_handles_empty_context(self):
        """Handles empty context dict."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._get_trending_topics({})

        assert isinstance(result, list)

    def test_combines_registry_and_context_topics(self):
        """Combines topics from registry and context."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        mock_registry = MagicMock()
        mock_registry.get_trending_topics.return_value = ["Registry Topic"]

        recommender = TopicBasedRecommender(topic_registry=mock_registry)
        context = {"current_news_topics": ["Context Topic"]}

        result = recommender._get_trending_topics(context)

        assert "Registry Topic" in result
        assert "Context Topic" in result


class TestFilterTopicsByPreferences:
    """Tests for TopicBasedRecommender._filter_topics_by_preferences() method."""

    def test_returns_list(self):
        """Returns a filtered list."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._filter_topics_by_preferences(["Topic A"], {})

        assert isinstance(result, list)

    def test_filters_out_disliked_topics(self):
        """Filters out topics matching disliked_topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["AI News", "Sports Update", "Tech Report"]
        preferences = {"disliked_topics": ["sports"]}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        assert "AI News" in result
        assert "Tech Report" in result
        assert "Sports Update" not in result

    def test_case_insensitive_filtering(self):
        """Filtering is case-insensitive."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["POLITICS Today", "Technology News"]
        preferences = {"disliked_topics": ["Politics"]}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        assert "POLITICS Today" not in result
        assert "Technology News" in result

    def test_boosts_topics_by_interests(self):
        """Topics matching interests are sorted higher."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["General News", "AI Development", "Weather Update"]
        preferences = {"interests": {"ai": 2.0}}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        # AI Development should be first due to boost
        assert result[0] == "AI Development"

    def test_empty_preferences(self):
        """Handles empty preferences dict."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["Topic A", "Topic B"]

        result = recommender._filter_topics_by_preferences(topics, {})

        assert len(result) == 2

    def test_empty_topics(self):
        """Handles empty topics list."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        preferences = {"interests": {"tech": 1.5}}

        result = recommender._filter_topics_by_preferences([], preferences)

        assert result == []

    def test_partial_match_filtering(self):
        """Partial match filters topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["Cryptocurrency boom", "Crypto trading tips"]
        preferences = {"disliked_topics": ["crypto"]}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        assert len(result) == 0


class TestGenerateTopicQuery:
    """Tests for TopicBasedRecommender._generate_topic_query() method."""

    def test_returns_string(self):
        """Returns a query string."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._generate_topic_query("climate change")

        assert isinstance(result, str)

    def test_includes_topic(self):
        """Query includes the original topic."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._generate_topic_query("artificial intelligence")

        assert "artificial intelligence" in result

    def test_adds_news_context(self):
        """Query adds news-related keywords."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._generate_topic_query("technology")

        assert "news" in result.lower()

    def test_handles_empty_topic(self):
        """Handles empty topic string."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        result = recommender._generate_topic_query("")

        assert isinstance(result, str)


class TestCreateRecommendationCard:
    """Tests for TopicBasedRecommender._create_recommendation_card() method."""

    def test_returns_none_on_search_error(self):
        """Returns None when search returns error."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch(
            "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
        ) as mock_search:
            mock_instance = MagicMock()
            mock_instance.analyze_topic.return_value = {
                "error": "Search failed"
            }
            mock_search.return_value = mock_instance

            result = recommender._create_recommendation_card(
                "topic", "query", "user123"
            )

        assert result is None

    def test_returns_none_when_no_news_items(self):
        """Returns None when no news items found."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch(
            "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
        ) as mock_search:
            mock_instance = MagicMock()
            mock_instance.analyze_topic.return_value = {"news_items": []}
            mock_search.return_value = mock_instance

            result = recommender._create_recommendation_card(
                "topic", "query", "user123"
            )

        assert result is None

    def test_uses_news_strategy(self):
        """Uses news search strategy."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with (
            patch(
                "local_deep_research.config.llm_config.get_llm",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.config.search_config.get_search",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
            ) as mock_search,
        ):
            mock_instance = MagicMock()
            mock_instance.analyze_topic.return_value = {"news_items": []}
            mock_search.return_value = mock_instance

            recommender._create_recommendation_card("topic", "query", "user123")

        assert mock_search.call_args[1]["strategy_name"] == "news"

    def test_calls_analyze_topic_with_is_news_search(self):
        """Calls analyze_topic with is_news_search=True."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with (
            patch(
                "local_deep_research.config.llm_config.get_llm",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.config.search_config.get_search",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
            ) as mock_search,
        ):
            mock_instance = MagicMock()
            mock_instance.analyze_topic.return_value = {"news_items": []}
            mock_search.return_value = mock_instance

            recommender._create_recommendation_card("topic", "query", "user123")

        mock_instance.analyze_topic.assert_called_once_with(
            "query", is_news_search=True
        )

    def test_handles_exception(self):
        """Handles exception gracefully."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        with patch(
            "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
        ) as mock_search:
            mock_search.return_value.analyze_topic.side_effect = Exception(
                "API error"
            )

            result = recommender._create_recommendation_card(
                "topic", "query", "user123"
            )

        assert result is None

    def test_selects_highest_impact_item(self):
        """Selects news item with highest impact_score."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()

        news_items = [
            {"headline": "Low", "impact_score": 3},
            {"headline": "High", "impact_score": 9},
            {"headline": "Medium", "impact_score": 5},
        ]

        with (
            patch(
                "local_deep_research.config.llm_config.get_llm",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.config.search_config.get_search",
                return_value=MagicMock(),
            ),
            patch(
                "local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
            ) as mock_search,
        ):
            mock_instance = MagicMock()
            mock_instance.analyze_topic.return_value = {
                "news_items": news_items
            }
            mock_search.return_value = mock_instance

            with patch(
                "local_deep_research.news.recommender.topic_based.CardFactory"
            ) as mock_factory:
                mock_card = MagicMock()
                mock_factory.create_news_card_from_analysis.return_value = (
                    mock_card
                )

                recommender._create_recommendation_card(
                    "topic", "query", "user123"
                )

                # Check that the high impact item was used
                call_args = (
                    mock_factory.create_news_card_from_analysis.call_args
                )
                assert call_args[1]["news_item"]["headline"] == "High"


class TestSearchBasedRecommender:
    """Tests for SearchBasedRecommender class."""

    def test_inherits_from_base_recommender(self):
        """SearchBasedRecommender inherits from BaseRecommender."""
        from local_deep_research.news.recommender.topic_based import (
            SearchBasedRecommender,
        )
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        recommender = SearchBasedRecommender()
        assert isinstance(recommender, BaseRecommender)

    def test_generate_recommendations_returns_empty_list(self):
        """Returns empty list (search tracking disabled by default)."""
        from local_deep_research.news.recommender.topic_based import (
            SearchBasedRecommender,
        )

        recommender = SearchBasedRecommender()
        result = recommender.generate_recommendations("user123")

        assert result == []

    def test_accepts_context(self):
        """Accepts optional context parameter."""
        from local_deep_research.news.recommender.topic_based import (
            SearchBasedRecommender,
        )

        recommender = SearchBasedRecommender()
        context = {"some": "context"}

        result = recommender.generate_recommendations(
            "user123", context=context
        )

        assert result == []


class TestEdgeCases:
    """Edge cases for topic-based recommender."""

    def test_topic_based_with_zero_max_recommendations(self):
        """Handles max_recommendations set to 0."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        recommender.max_recommendations = 0

        with patch.object(
            recommender, "_get_trending_topics", return_value=["Topic"]
        ):
            with patch.object(
                recommender, "_get_user_preferences", return_value={}
            ):
                with patch.object(
                    recommender,
                    "_filter_topics_by_preferences",
                    return_value=["Topic"],
                ):
                    result = recommender.generate_recommendations("user123")

        assert result == []

    def test_unicode_topics(self):
        """Handles unicode characters in topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["日本語トピック", "Emoji 🎉 Topic", "Ümläuts"]
        preferences = {}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        assert len(result) == 3

    def test_very_long_topic(self):
        """Handles very long topic strings."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        long_topic = "A" * 1000

        result = recommender._generate_topic_query(long_topic)

        assert long_topic in result

    def test_special_characters_in_topic(self):
        """Handles special characters in topics."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["C++", "Node.js", "SQL*Plus", "Topic [1]"]
        preferences = {}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        assert len(result) == 4

    def test_duplicate_topics(self):
        """Handles duplicate topics in list."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["AI", "AI", "Climate", "AI"]
        preferences = {}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        # Should keep duplicates (no deduplication in filter)
        assert result.count("AI") == 3

    def test_interest_weight_zero(self):
        """Handles interest with weight 0."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["AI Topic", "Other Topic"]
        preferences = {"interests": {"ai": 0}}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        # Should still include topics with 0 weight
        assert len(result) == 2

    def test_negative_interest_weight(self):
        """Handles negative interest weight (demotes topic)."""
        from local_deep_research.news.recommender.topic_based import (
            TopicBasedRecommender,
        )

        recommender = TopicBasedRecommender()
        topics = ["AI Topic", "Other Topic"]
        preferences = {"interests": {"ai": -1.0}}

        result = recommender._filter_topics_by_preferences(topics, preferences)

        # AI Topic should be last due to negative weight
        assert result[-1] == "AI Topic"
