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

Tests cover:
- BaseRecommender abstract class
- Initialization and configuration
- Progress tracking (_update_progress)
- User preference handling
- Card filtering
- Recommendation sorting
- Error handling
"""

import pytest
from unittest.mock import Mock


class TestBaseRecommenderInit:
    """Tests for BaseRecommender initialization."""

    def test_is_abstract_class(self):
        """BaseRecommender is an abstract class."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )
        from abc import ABC

        assert issubclass(BaseRecommender, ABC)

    def test_cannot_instantiate_directly(self):
        """Cannot directly instantiate BaseRecommender."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        with pytest.raises(TypeError):
            BaseRecommender()

    def test_concrete_class_initialization(self):
        """Concrete subclass can be instantiated."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        assert recommender is not None

    def test_accepts_topic_registry(self):
        """Accepts topic_registry parameter."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_registry = Mock()
        recommender = TestRecommender(topic_registry=mock_registry)

        assert recommender.topic_registry is mock_registry

    def test_accepts_preference_manager(self):
        """Accepts preference_manager parameter."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_pref_manager = Mock()
        recommender = TestRecommender(preference_manager=mock_pref_manager)

        assert recommender.preference_manager is mock_pref_manager

    def test_accepts_rating_system(self):
        """Accepts rating_system parameter."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_rating = Mock()
        recommender = TestRecommender(rating_system=mock_rating)

        assert recommender.rating_system is mock_rating

    def test_accepts_search_system(self):
        """Accepts search_system parameter."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_search = Mock()
        recommender = TestRecommender(search_system=mock_search)

        assert recommender.search_system is mock_search


class TestProgressTracking:
    """Tests for progress tracking functionality."""

    def test_update_progress_calls_callback(self):
        """_update_progress calls callback when set."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        mock_callback = Mock()
        recommender.set_progress_callback(mock_callback)

        recommender._update_progress("Processing...", 50)

        mock_callback.assert_called_once()

    def test_update_progress_passes_message(self):
        """_update_progress passes message to callback."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        mock_callback = Mock()
        recommender.set_progress_callback(mock_callback)

        recommender._update_progress("Loading", 75)

        args = mock_callback.call_args[0]
        assert args[0] == "Loading"
        assert args[1] == 75

    def test_update_progress_without_callback_no_error(self):
        """_update_progress works without callback set."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()

        # Should not raise
        recommender._update_progress("Working", 30)


class TestUserPreferences:
    """Tests for user preference handling."""

    def test_get_user_preferences_returns_dict(self):
        """_get_user_preferences returns dictionary."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        prefs = recommender._get_user_preferences("user123")

        assert isinstance(prefs, dict)

    def test_get_user_preferences_uses_manager(self):
        """_get_user_preferences uses preference_manager if available."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_manager = Mock()
        mock_manager.get_preferences.return_value = {"interests": {"AI": 1.0}}

        recommender = TestRecommender(preference_manager=mock_manager)
        prefs = recommender._get_user_preferences("user123")

        mock_manager.get_preferences.assert_called_once_with("user123")
        assert "interests" in prefs

    def test_get_user_preferences_returns_defaults_without_manager(self):
        """_get_user_preferences returns defaults without manager."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()  # No preference_manager
        prefs = recommender._get_user_preferences("user123")

        assert isinstance(prefs, dict)
        assert prefs == {}


class TestCardFiltering:
    """Tests for card filtering by preferences."""

    def test_filter_by_preferences_returns_list(self):
        """_filter_by_preferences returns list."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        cards = []
        preferences = {}

        result = recommender._filter_by_preferences(cards, preferences)

        assert isinstance(result, list)

    def test_filter_by_impact_threshold(self):
        """_filter_by_preferences filters by impact_threshold."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()

        mock_card1 = Mock()
        mock_card1.impact_score = 8
        mock_card1.topic = "tech"
        mock_card1.category = "tech"
        mock_card1.metadata = {}

        mock_card2 = Mock()
        mock_card2.impact_score = 3
        mock_card2.topic = "sports"
        mock_card2.category = "sports"
        mock_card2.metadata = {}

        cards = [mock_card1, mock_card2]
        preferences = {"impact_threshold": 5}

        result = recommender._filter_by_preferences(cards, preferences)

        assert len(result) == 1
        assert result[0].impact_score == 8


class TestRecommendationSorting:
    """Tests for recommendation sorting."""

    def test_sort_by_relevance_returns_list(self):
        """_sort_by_relevance returns list."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        cards = []

        result = recommender._sort_by_relevance(cards, "user123")

        assert isinstance(result, list)

    def test_sort_by_impact_score(self):
        """_sort_by_relevance sorts by impact score."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()

        mock_card1 = Mock()
        mock_card1.impact_score = 5
        mock_card1.metadata = {}

        mock_card2 = Mock()
        mock_card2.impact_score = 9
        mock_card2.metadata = {}

        cards = [mock_card1, mock_card2]

        result = recommender._sort_by_relevance(cards, "user123")

        # Higher impact score should be first
        assert result[0].impact_score == 9


class TestAbstractMethods:
    """Tests for abstract method requirements."""

    def test_generate_recommendations_is_abstract(self):
        """generate_recommendations must be implemented."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        # This should fail without implementing abstract method
        class IncompleteRecommender(BaseRecommender):
            pass

        with pytest.raises(TypeError):
            IncompleteRecommender()

    def test_concrete_implementation_works(self):
        """Concrete implementation with all methods works."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class CompleteRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return [{"id": "rec1", "topic": "Test"}]

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

        assert len(result) == 1


class TestRecommenderConfiguration:
    """Tests for recommender configuration."""

    def test_default_attributes(self):
        """Recommender has default attributes."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()

        # Should have these attributes
        assert hasattr(recommender, "topic_registry")
        assert hasattr(recommender, "preference_manager")
        assert hasattr(recommender, "rating_system")
        assert hasattr(recommender, "search_system")

    def test_none_defaults(self):
        """Dependencies default to None."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()

        assert recommender.topic_registry is None
        assert recommender.preference_manager is None
        assert recommender.rating_system is None
        assert recommender.search_system is None

    def test_strategy_name_set(self):
        """Strategy name is set from class name."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class MyCustomRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = MyCustomRecommender()

        assert recommender.strategy_name == "MyCustomRecommender"


class TestRecommenderEdgeCases:
    """Edge case tests for BaseRecommender."""

    def test_empty_user_id(self):
        """Handles empty user_id."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender.generate_recommendations("")

        assert result == []

    def test_none_context(self):
        """Handles None context."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender.generate_recommendations("user123", None)

        assert result == []

    def test_empty_cards_for_filtering(self):
        """Handles empty cards list for filtering."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender._filter_by_preferences([], {})

        assert result == []

    def test_empty_cards_for_sorting(self):
        """Handles empty cards for sorting."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender._sort_by_relevance([], "user123")

        assert result == []

    def test_get_strategy_info(self):
        """get_strategy_info returns dict with info."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        info = recommender.get_strategy_info()

        assert "name" in info
        assert info["name"] == "TestRecommender"


class TestSearchExecution:
    """Tests for search execution."""

    def test_execute_search_without_system(self):
        """_execute_search returns error without search system."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender._execute_search("test query")

        assert "error" in result

    def test_execute_search_with_system(self):
        """_execute_search uses search system."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_search = Mock()
        mock_search.analyze_topic.return_value = {"results": []}

        recommender = TestRecommender(search_system=mock_search)
        recommender._execute_search("test query")

        mock_search.analyze_topic.assert_called_once_with("test query")

    def test_execute_search_handles_exception(self):
        """_execute_search handles exceptions."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_search = Mock()
        mock_search.analyze_topic.side_effect = Exception("Search error")

        recommender = TestRecommender(search_system=mock_search)
        result = recommender._execute_search("test query")

        assert "error" in result


class TestUserRatings:
    """Tests for user ratings retrieval."""

    def test_get_user_ratings_without_system(self):
        """_get_user_ratings returns empty list without rating system."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        recommender = TestRecommender()
        result = recommender._get_user_ratings("user123")

        assert result == []

    def test_get_user_ratings_with_system(self):
        """_get_user_ratings uses rating system."""
        from local_deep_research.news.recommender.base_recommender import (
            BaseRecommender,
        )

        class TestRecommender(BaseRecommender):
            def generate_recommendations(self, user_id, context=None):
                return []

        mock_rating = Mock()
        mock_rating.get_recent_ratings.return_value = [{"id": 1}]

        recommender = TestRecommender(rating_system=mock_rating)
        result = recommender._get_user_ratings("user123")

        assert len(result) == 1
