"""
Deep behavioral tests for topic_based.py pure logic.
Tests trending topics retrieval, preference filtering,
query generation, and recommendation sorting.
"""


# --- Default fallback topics ---


class TestDefaultFallbackTopics:
    """Tests for default fallback topics."""

    def _get_fallback_topics(self):
        return [
            "artificial intelligence developments",
            "cybersecurity threats",
            "climate change",
            "economic policy",
            "technology innovation",
        ]

    def test_has_ai_topic(self):
        topics = self._get_fallback_topics()
        assert any("artificial intelligence" in t for t in topics)

    def test_has_cybersecurity_topic(self):
        topics = self._get_fallback_topics()
        assert any("cybersecurity" in t for t in topics)

    def test_has_five_topics(self):
        topics = self._get_fallback_topics()
        assert len(topics) == 5

    def test_topics_are_strings(self):
        topics = self._get_fallback_topics()
        for topic in topics:
            assert isinstance(topic, str)


# --- Trending topics from registry ---


class TestTrendingTopicsFromRegistry:
    """Tests for _get_trending_topics with registry."""

    def _get_trending_from_registry(self, registry, hours, limit):
        if registry:
            return registry.get("trending", [])[:limit]
        return []

    def test_with_registry(self):
        registry = {"trending": ["AI", "ML", "NLP"]}
        result = self._get_trending_from_registry(registry, 24, 10)
        assert "AI" in result

    def test_without_registry(self):
        result = self._get_trending_from_registry(None, 24, 10)
        assert result == []

    def test_respects_limit(self):
        registry = {"trending": ["A", "B", "C", "D", "E"]}
        result = self._get_trending_from_registry(registry, 24, 3)
        assert len(result) == 3


# --- Context topics extraction ---


class TestContextTopicsExtraction:
    """Tests for extracting topics from context."""

    def _extract_context_topics(self, context):
        topics = []
        if context:
            if "current_news_topics" in context:
                topics.extend(context["current_news_topics"])
        return topics

    def test_with_news_topics(self):
        context = {"current_news_topics": ["AI", "Tech"]}
        result = self._extract_context_topics(context)
        assert result == ["AI", "Tech"]

    def test_without_news_topics(self):
        context = {"other_key": "value"}
        result = self._extract_context_topics(context)
        assert result == []

    def test_none_context(self):
        result = self._extract_context_topics(None)
        assert result == []


# --- Topic preference filtering ---


class TestTopicPreferenceFiltering:
    """Tests for _filter_topics_by_preferences."""

    def _should_skip_topic(self, topic, disliked_topics):
        topic_lower = topic.lower()
        return any(disliked in topic_lower for disliked in disliked_topics)

    def test_skips_disliked(self):
        disliked = ["politics"]
        assert self._should_skip_topic("US Politics News", disliked) is True

    def test_keeps_not_disliked(self):
        disliked = ["politics"]
        assert self._should_skip_topic("Technology Updates", disliked) is False

    def test_case_insensitive(self):
        disliked = ["politics"]
        assert self._should_skip_topic("POLITICS", disliked) is True


class TestTopicInterestBoosting:
    """Tests for interest boosting in filtering."""

    def _get_interest_boost(self, topic, interests):
        topic_lower = topic.lower()
        for interest, weight in interests.items():
            if interest.lower() in topic_lower:
                return weight
        return 1.0

    def test_matching_interest(self):
        interests = {"AI": 2.0, "Tech": 1.5}
        boost = self._get_interest_boost("AI developments", interests)
        assert boost == 2.0

    def test_no_matching_interest(self):
        interests = {"AI": 2.0}
        boost = self._get_interest_boost("Sports news", interests)
        assert boost == 1.0

    def test_case_insensitive(self):
        interests = {"ai": 2.0}
        boost = self._get_interest_boost("AI NEWS", interests)
        assert boost == 2.0


class TestTopicBoostSorting:
    """Tests for sorting topics by boost."""

    def _sort_by_boost(self, topic_boost_pairs):
        sorted_pairs = sorted(
            topic_boost_pairs, key=lambda x: x[1], reverse=True
        )
        return [topic for topic, _ in sorted_pairs]

    def test_sorts_by_boost(self):
        pairs = [("A", 1.0), ("B", 2.0), ("C", 1.5)]
        result = self._sort_by_boost(pairs)
        assert result == ["B", "C", "A"]

    def test_equal_boosts(self):
        pairs = [("A", 1.0), ("B", 1.0)]
        result = self._sort_by_boost(pairs)
        assert len(result) == 2


class TestFullPreferenceFiltering:
    """Tests for complete preference filtering."""

    def _filter_topics_by_preferences(self, topics, preferences):
        filtered = []
        disliked_topics = [
            t.lower() for t in preferences.get("disliked_topics", [])
        ]
        interests = preferences.get("interests", {})

        for topic in topics:
            topic_lower = topic.lower()

            # Skip disliked
            if any(disliked in topic_lower for disliked in disliked_topics):
                continue

            # Calculate boost
            boost = 1.0
            for interest, weight in interests.items():
                if interest.lower() in topic_lower:
                    boost = weight
                    break

            filtered.append((topic, boost))

        # Sort by boost
        filtered.sort(key=lambda x: x[1], reverse=True)
        return [topic for topic, _ in filtered]

    def test_filters_and_sorts(self):
        topics = ["AI news", "Politics", "Tech updates"]
        prefs = {
            "disliked_topics": ["Politics"],
            "interests": {"AI": 2.0},
        }
        result = self._filter_topics_by_preferences(topics, prefs)
        assert "Politics" not in result
        assert result[0] == "AI news"

    def test_empty_preferences(self):
        topics = ["AI", "Tech"]
        result = self._filter_topics_by_preferences(topics, {})
        assert len(result) == 2


# --- Topic query generation ---


class TestTopicQueryGeneration:
    """Tests for _generate_topic_query."""

    def _generate_topic_query(self, topic):
        return f"{topic} latest news today breaking developments"

    def test_includes_topic(self):
        result = self._generate_topic_query("AI")
        assert "AI" in result

    def test_includes_news_context(self):
        result = self._generate_topic_query("technology")
        assert "latest" in result
        assert "news" in result
        assert "today" in result

    def test_includes_breaking(self):
        result = self._generate_topic_query("topic")
        assert "breaking" in result


# --- Progress update calculation ---


class TestProgressUpdateCalculation:
    """Tests for progress percentage calculation."""

    def _calc_progress(self, base_progress, index, total):
        if total == 0:
            return base_progress
        return base_progress + (40 * index / total)

    def test_first_item(self):
        progress = self._calc_progress(50, 0, 5)
        assert progress == 50

    def test_last_item(self):
        progress = self._calc_progress(50, 5, 5)
        assert progress == 90  # 50 + 40

    def test_middle_item(self):
        progress = self._calc_progress(50, 2, 4)
        assert progress == 70  # 50 + 20


# --- Max recommendations limit ---


class TestMaxRecommendationsLimit:
    """Tests for max_recommendations default."""

    def test_default_limit(self):
        max_recommendations = 5
        assert max_recommendations == 5

    def test_slice_within_limit(self):
        topics = ["A", "B", "C", "D", "E", "F", "G"]
        limited = topics[:5]
        assert len(limited) == 5


# --- Card metadata for recommendations ---


class TestRecommendationCardMetadata:
    """Tests for additional_metadata structure."""

    def _build_metadata(
        self, recommender, topic, query, items_found, big_picture, topics
    ):
        return {
            "recommender": recommender,
            "original_topic": topic,
            "query_used": query,
            "total_items_found": items_found,
            "big_picture": big_picture,
            "topics_extracted": topics,
        }

    def test_has_recommender(self):
        metadata = self._build_metadata("topic_based", "AI", "q", 5, "", [])
        assert metadata["recommender"] == "topic_based"

    def test_has_original_topic(self):
        metadata = self._build_metadata("x", "AI news", "q", 5, "", [])
        assert metadata["original_topic"] == "AI news"

    def test_has_query_used(self):
        metadata = self._build_metadata("x", "t", "AI latest news", 5, "", [])
        assert metadata["query_used"] == "AI latest news"


# --- News data structure ---


class TestNewsDataStructure:
    """Tests for news_data structure from search results."""

    def _build_news_data(self, news_items, formatted_findings):
        return {
            "items": news_items,
            "item_count": len(news_items),
            "big_picture": formatted_findings,
            "topics": [],
        }

    def test_has_items(self):
        items = [{"id": "1"}, {"id": "2"}]
        data = self._build_news_data(items, "")
        assert data["items"] == items

    def test_item_count(self):
        items = [{"id": "1"}, {"id": "2"}, {"id": "3"}]
        data = self._build_news_data(items, "")
        assert data["item_count"] == 3

    def test_has_big_picture(self):
        data = self._build_news_data([], "Summary of findings")
        assert data["big_picture"] == "Summary of findings"


# --- Max impact item selection ---


class TestMaxImpactItemSelection:
    """Tests for selecting most impactful news item."""

    def _get_max_impact_item(self, news_items):
        return max(news_items, key=lambda x: x.get("impact_score", 0))

    def test_selects_highest_impact(self):
        items = [
            {"id": "1", "impact_score": 5},
            {"id": "2", "impact_score": 8},
            {"id": "3", "impact_score": 3},
        ]
        result = self._get_max_impact_item(items)
        assert result["id"] == "2"

    def test_missing_impact_defaults_zero(self):
        items = [
            {"id": "1", "impact_score": 5},
            {"id": "2"},  # No impact_score
        ]
        result = self._get_max_impact_item(items)
        assert result["id"] == "1"


# --- Search results error handling ---


class TestSearchResultsErrorHandling:
    """Tests for search results error detection."""

    def _has_error(self, results):
        return "error" in results

    def test_detects_error(self):
        results = {"error": "Search failed"}
        assert self._has_error(results) is True

    def test_no_error(self):
        results = {"items": []}
        assert self._has_error(results) is False


# --- Empty news items handling ---


class TestEmptyNewsItemsHandling:
    """Tests for handling empty news items."""

    def _should_return_none(self, news_items):
        return not news_items

    def test_empty_list(self):
        assert self._should_return_none([]) is True

    def test_non_empty_list(self):
        assert self._should_return_none([{"id": "1"}]) is False

    def test_none(self):
        assert self._should_return_none(None) is True


# --- Version data structure ---


class TestVersionDataStructure:
    """Tests for version data structure for recommendations."""

    def _build_version_data(
        self, search_results, news_analysis, query, strategy
    ):
        return {
            "search_results": search_results,
            "news_analysis": news_analysis,
            "query": query,
            "strategy": strategy,
        }

    def test_has_search_results(self):
        data = self._build_version_data({"items": []}, {}, "q", "s")
        assert "search_results" in data

    def test_has_news_analysis(self):
        data = self._build_version_data({}, {"items": []}, "q", "s")
        assert "news_analysis" in data

    def test_strategy_is_news_aggregation(self):
        data = self._build_version_data({}, {}, "q", "news_aggregation")
        assert data["strategy"] == "news_aggregation"


# --- SearchBasedRecommender ---


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

    def test_returns_empty_when_disabled(self):
        # Search tracking is off by default
        result = []
        assert result == []

    def test_search_tracking_default_off(self):
        search_tracking_enabled = False
        assert search_tracking_enabled is False


# --- Recommendation sorting ---


class TestRecommendationSorting:
    """Tests for _sort_by_relevance behavior."""

    def _sort_by_score(self, cards, score_key="impact_score"):
        return sorted(cards, key=lambda c: c.get(score_key, 0), reverse=True)

    def test_sorts_by_impact(self):
        cards = [
            {"id": "1", "impact_score": 3},
            {"id": "2", "impact_score": 8},
            {"id": "3", "impact_score": 5},
        ]
        result = self._sort_by_score(cards)
        assert result[0]["id"] == "2"
        assert result[1]["id"] == "3"
        assert result[2]["id"] == "1"


# --- Topics list merging ---


class TestTopicsListMerging:
    """Tests for merging topics from multiple sources."""

    def test_extend_from_registry(self):
        topics = []
        registry_topics = ["AI", "ML"]
        topics.extend(registry_topics)
        assert topics == ["AI", "ML"]

    def test_extend_from_context(self):
        topics = ["AI"]
        context_topics = ["Tech", "Science"]
        topics.extend(context_topics)
        assert topics == ["AI", "Tech", "Science"]

    def test_merge_order_preserved(self):
        topics = []
        topics.extend(["A", "B"])
        topics.extend(["C", "D"])
        assert topics == ["A", "B", "C", "D"]
