"""
Deep behavioral tests for BaseRecommender and recommendation patterns.
Tests strategy naming, dependency injection, topic filtering,
query generation, and recommendation data structures.
"""

from unittest.mock import Mock

import pytest

from local_deep_research.news.recommender.base_recommender import (
    BaseRecommender,
)
from local_deep_research.news.recommender.topic_based import (
    TopicBasedRecommender,
    SearchBasedRecommender,
)


# --- BaseRecommender abstract enforcement ---


class TestBaseRecommenderAbstract:
    """Tests for BaseRecommender abstract patterns."""

    def test_cannot_instantiate_directly(self):
        with pytest.raises(TypeError):
            BaseRecommender()

    def test_strategy_name_default(self):
        r = TopicBasedRecommender()
        assert r.strategy_name == "TopicBasedRecommender"

    def test_strategy_name_is_class_name(self):
        r = SearchBasedRecommender()
        assert r.strategy_name == "SearchBasedRecommender"


# --- Dependency injection ---


class TestRecommenderDependencyInjection:
    """Tests for recommender dependency injection."""

    def test_preference_manager_default_none(self):
        r = TopicBasedRecommender()
        assert r.preference_manager is None

    def test_preference_manager_injected(self):
        pm = Mock()
        r = TopicBasedRecommender(preference_manager=pm)
        assert r.preference_manager is pm

    def test_topic_registry_default_none(self):
        r = TopicBasedRecommender()
        assert r.topic_registry is None

    def test_topic_registry_injected(self):
        tr = Mock()
        r = TopicBasedRecommender(topic_registry=tr)
        assert r.topic_registry is tr

    def test_search_system_default_none(self):
        r = TopicBasedRecommender()
        assert r.search_system is None

    def test_search_system_injected(self):
        ss = Mock()
        r = TopicBasedRecommender(search_system=ss)
        assert r.search_system is ss

    def test_rating_system_default_none(self):
        r = TopicBasedRecommender()
        assert r.rating_system is None

    def test_rating_system_injected(self):
        rs = Mock()
        r = TopicBasedRecommender(rating_system=rs)
        assert r.rating_system is rs

    def test_progress_callback_default_none(self):
        r = TopicBasedRecommender()
        assert r.progress_callback is None

    def test_set_progress_callback(self):
        r = TopicBasedRecommender()
        cb = Mock()
        r.set_progress_callback(cb)
        assert r.progress_callback is cb


# --- max_recommendations ---


class TestMaxRecommendations:
    """Tests for max_recommendations configuration."""

    def test_default_max_recommendations(self):
        r = TopicBasedRecommender()
        assert r.max_recommendations == 5


# --- Default trending topics ---


class TestDefaultTrendingTopics:
    """Tests for the default trending topics list."""

    def test_returns_list(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert isinstance(topics, list)

    def test_has_ai_topic(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert any("artificial intelligence" in t for t in topics)

    def test_multiple_defaults(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert len(topics) >= 3

    def test_context_topics_added(self):
        r = TopicBasedRecommender()
        context = {"current_news_topics": ["Custom Topic"]}
        topics = r._get_trending_topics(context)
        assert "Custom Topic" in topics

    def test_has_cybersecurity(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert any("cybersecurity" in t for t in topics)

    def test_has_climate(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert any("climate" in t for t in topics)


# --- Query generation ---


class TestQueryGeneration:
    """Tests for topic query generation."""

    def test_query_contains_topic(self):
        r = TopicBasedRecommender()
        query = r._generate_topic_query("climate change")
        assert "climate change" in query

    def test_query_has_news_context(self):
        r = TopicBasedRecommender()
        query = r._generate_topic_query("AI")
        lower = query.lower()
        assert "news" in lower

    def test_query_has_latest(self):
        r = TopicBasedRecommender()
        query = r._generate_topic_query("AI")
        assert "latest" in query.lower()

    def test_query_has_breaking(self):
        r = TopicBasedRecommender()
        query = r._generate_topic_query("AI")
        assert "breaking" in query.lower()

    def test_different_topics_different_queries(self):
        r = TopicBasedRecommender()
        q1 = r._generate_topic_query("AI")
        q2 = r._generate_topic_query("Climate")
        assert q1 != q2

    def test_empty_topic(self):
        r = TopicBasedRecommender()
        query = r._generate_topic_query("")
        assert "news" in query.lower()


# --- SearchBasedRecommender ---


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

    def test_always_returns_empty(self):
        r = SearchBasedRecommender()
        assert r.generate_recommendations("user1") == []

    def test_with_context_returns_empty(self):
        r = SearchBasedRecommender()
        assert r.generate_recommendations("user1", context={"key": "val"}) == []

    def test_strategy_name(self):
        r = SearchBasedRecommender()
        assert r.strategy_name == "SearchBasedRecommender"

    def test_inherits_from_base(self):
        r = SearchBasedRecommender()
        assert isinstance(r, BaseRecommender)


# --- Filter topics by preferences ---


class TestFilterTopicsByPreferences:
    """Tests for _filter_topics_by_preferences."""

    def test_no_preferences_returns_all(self):
        r = TopicBasedRecommender()
        topics = ["AI News", "Climate Change"]
        result = r._filter_topics_by_preferences(topics, {})
        assert len(result) == 2

    def test_disliked_removes_matches(self):
        r = TopicBasedRecommender()
        topics = ["Sports Update", "AI News"]
        prefs = {"disliked_topics": ["sports"]}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert "Sports Update" not in result
        assert "AI News" in result

    def test_disliked_case_insensitive(self):
        r = TopicBasedRecommender()
        topics = ["SPORTS update"]
        prefs = {"disliked_topics": ["sports"]}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert len(result) == 0

    def test_interest_boost_reorders(self):
        r = TopicBasedRecommender()
        topics = ["Regular Topic", "Super AI Topic"]
        prefs = {"interests": {"ai": 10.0}}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert result[0] == "Super AI Topic"

    def test_negative_weight_interest(self):
        r = TopicBasedRecommender()
        topics = ["AI News", "Climate Change"]
        prefs = {"interests": {"ai": -1.0}}
        result = r._filter_topics_by_preferences(topics, prefs)
        # Negative weight should still keep the topic
        assert "AI News" in result

    def test_zero_weight_interest(self):
        r = TopicBasedRecommender()
        topics = ["AI News", "Climate Change"]
        prefs = {"interests": {"ai": 0.0}}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert len(result) == 2

    def test_very_large_weight(self):
        r = TopicBasedRecommender()
        topics = ["Regular Topic", "Super Topic"]
        prefs = {"interests": {"super": 999.0}}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert result[0] == "Super Topic"

    def test_single_topic(self):
        r = TopicBasedRecommender()
        topics = ["AI"]
        result = r._filter_topics_by_preferences(topics, {})
        assert result == ["AI"]

    def test_disliked_removes_all_matches(self):
        r = TopicBasedRecommender()
        topics = ["Sports Update", "Sports Score", "Sports Analysis"]
        prefs = {"disliked_topics": ["sports"]}
        result = r._filter_topics_by_preferences(topics, prefs)
        assert len(result) == 0

    def test_unicode_topics(self):
        r = TopicBasedRecommender()
        topics = ["AI研究", "技術ニュース"]
        result = r._filter_topics_by_preferences(topics, {})
        assert len(result) == 2

    def test_empty_topics_list(self):
        r = TopicBasedRecommender()
        result = r._filter_topics_by_preferences([], {})
        assert result == []


# --- Trending topics with registry ---


class TestTrendingWithRegistry:
    """Tests for trending topic retrieval with registry."""

    def test_registry_results_used(self):
        registry = Mock()
        registry.get_trending_topics.return_value = ["Registry A", "Registry B"]
        r = TopicBasedRecommender(topic_registry=registry)
        topics = r._get_trending_topics(None)
        assert "Registry A" in topics

    def test_empty_registry_gets_defaults(self):
        registry = Mock()
        registry.get_trending_topics.return_value = []
        r = TopicBasedRecommender(topic_registry=registry)
        topics = r._get_trending_topics(None)
        assert len(topics) > 0

    def test_context_and_registry_combined(self):
        registry = Mock()
        registry.get_trending_topics.return_value = ["From Registry"]
        r = TopicBasedRecommender(topic_registry=registry)
        context = {"current_news_topics": ["From Context"]}
        topics = r._get_trending_topics(context)
        assert "From Registry" in topics
        assert "From Context" in topics

    def test_none_context_and_none_registry(self):
        r = TopicBasedRecommender()
        topics = r._get_trending_topics(None)
        assert isinstance(topics, list)
        assert len(topics) > 0


# --- Get user preferences ---


class TestGetUserPreferences:
    """Tests for _get_user_preferences helper."""

    def test_returns_empty_without_manager(self):
        r = TopicBasedRecommender()
        prefs = r._get_user_preferences("user1")
        assert prefs == {}

    def test_delegates_to_manager(self):
        pm = Mock()
        pm.get_preferences.return_value = {"key": "val"}
        r = TopicBasedRecommender(preference_manager=pm)
        prefs = r._get_user_preferences("user1")
        assert prefs == {"key": "val"}
        pm.get_preferences.assert_called_once_with("user1")


# --- Get user ratings ---


class TestGetUserRatings:
    """Tests for _get_user_ratings helper."""

    def test_returns_empty_without_system(self):
        r = TopicBasedRecommender()
        ratings = r._get_user_ratings("user1")
        assert ratings == []

    def test_delegates_to_rating_system(self):
        rs = Mock()
        rs.get_recent_ratings.return_value = [{"id": 1}]
        r = TopicBasedRecommender(rating_system=rs)
        ratings = r._get_user_ratings("user1")
        assert ratings == [{"id": 1}]


# --- Execute search ---


class TestExecuteSearch:
    """Tests for _execute_search helper."""

    def test_returns_error_without_system(self):
        r = TopicBasedRecommender()
        result = r._execute_search("query")
        assert "error" in result

    def test_delegates_to_search_system(self):
        ss = Mock()
        ss.analyze_topic.return_value = {"results": []}
        r = TopicBasedRecommender(search_system=ss)
        result = r._execute_search("query")
        assert result == {"results": []}

    def test_handles_search_exception(self):
        ss = Mock()
        ss.analyze_topic.side_effect = Exception("search failed")
        r = TopicBasedRecommender(search_system=ss)
        result = r._execute_search("query")
        assert "error" in result


# --- Strategy info ---


class TestStrategyInfo:
    """Tests for get_strategy_info."""

    def test_has_name(self):
        r = TopicBasedRecommender()
        info = r.get_strategy_info()
        assert info["name"] == "TopicBasedRecommender"

    def test_reports_no_preference_manager(self):
        r = TopicBasedRecommender()
        info = r.get_strategy_info()
        assert info["has_preference_manager"] is False

    def test_reports_preference_manager_present(self):
        r = TopicBasedRecommender(preference_manager=Mock())
        info = r.get_strategy_info()
        assert info["has_preference_manager"] is True

    def test_reports_no_search_system(self):
        r = TopicBasedRecommender()
        info = r.get_strategy_info()
        assert info["has_search_system"] is False

    def test_has_description(self):
        r = TopicBasedRecommender()
        info = r.get_strategy_info()
        assert "description" in info
