"""
Tests for advanced_search_system/filters/journal_reputation_filter.py

Tests cover:
- JournalReputationFilter initialization
- create_default class method
- __check_result method
- __clean_journal_name method
- __analyze_journal_reputation method
- filter_results method
"""

from datetime import timedelta
from unittest.mock import MagicMock, Mock, patch

import pytest


class TestJournalFilterError:
    """Tests for JournalFilterError exception."""

    def test_exception_exists(self):
        """Test that JournalFilterError is defined."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalFilterError,
        )

        assert issubclass(JournalFilterError, Exception)

    def test_exception_can_be_raised(self):
        """Test that JournalFilterError can be raised and caught."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalFilterError,
        )

        with pytest.raises(JournalFilterError):
            raise JournalFilterError("Test error")


class TestJournalReputationFilterInit:
    """Tests for JournalReputationFilter initialization."""

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_init_with_all_params(self, mock_get_llm, mock_create_engine):
        """Test initialization with all parameters provided."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_model = Mock()
        mock_engine = Mock()
        mock_create_engine.return_value = mock_engine

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=5,
            max_context=2000,
            exclude_non_published=True,
            quality_reanalysis_period=timedelta(days=180),
        )

        assert filter_obj.model is mock_model
        mock_get_llm.assert_not_called()  # Should not call since model provided

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_init_without_model_uses_default(
        self, mock_get_llm, mock_create_engine
    ):
        """Test that default model is fetched when none provided."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_default_model = Mock()
        mock_get_llm.return_value = mock_default_model
        mock_engine = Mock()
        mock_create_engine.return_value = mock_engine

        filter_obj = JournalReputationFilter(
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        mock_get_llm.assert_called_once()
        assert filter_obj.model is mock_default_model

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_init_raises_when_searxng_unavailable(
        self, mock_get_llm, mock_create_engine
    ):
        """Test that init raises when SearXNG is unavailable."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalFilterError,
            JournalReputationFilter,
        )

        mock_create_engine.return_value = None

        with pytest.raises(JournalFilterError):
            JournalReputationFilter(
                model=Mock(),
                reliability_threshold=4,
                max_context=3000,
                exclude_non_published=False,
                quality_reanalysis_period=timedelta(days=365),
            )

    @patch("local_deep_research.config.search_config.get_setting_from_snapshot")
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_init_reads_settings_when_params_not_provided(
        self, mock_get_llm, mock_create_engine, mock_get_setting
    ):
        """Test that settings are read when parameters not provided."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_create_engine.return_value = Mock()
        mock_get_setting.side_effect = [4, 3000, False, 365]

        JournalReputationFilter(model=Mock())

        assert mock_get_setting.call_count == 4


class TestCreateDefault:
    """Tests for JournalReputationFilter.create_default class method."""

    @patch("local_deep_research.config.search_config.get_setting_from_snapshot")
    def test_returns_none_when_disabled(self, mock_get_setting):
        """Test that None is returned when filtering is disabled."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_get_setting.return_value = False

        result = JournalReputationFilter.create_default(
            model=Mock(), engine_name="test_engine"
        )

        assert result is None

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch("local_deep_research.config.search_config.get_setting_from_snapshot")
    def test_returns_filter_when_enabled(
        self, mock_get_setting, mock_create_engine
    ):
        """Test that filter is returned when enabled."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_get_setting.side_effect = [True, 4, 3000, False, 365]
        mock_create_engine.return_value = Mock()

        result = JournalReputationFilter.create_default(
            model=Mock(), engine_name="test_engine"
        )

        assert result is not None
        assert isinstance(result, JournalReputationFilter)

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch("local_deep_research.config.search_config.get_setting_from_snapshot")
    def test_returns_none_on_init_error(
        self, mock_get_setting, mock_create_engine
    ):
        """Test that None is returned on initialization error."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_get_setting.side_effect = [True, 4, 3000, False, 365]
        mock_create_engine.return_value = None  # Will cause init error

        result = JournalReputationFilter.create_default(
            model=Mock(), engine_name="test_engine"
        )

        assert result is None


class TestFilterResults:
    """Tests for filter_results method."""

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_user_db_session"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_search_context"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_returns_results_on_exception(
        self, mock_get_llm, mock_create_engine, mock_context, mock_session
    ):
        """Test that original results are returned on exception."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_create_engine.return_value = Mock()
        mock_context.return_value = {
            "username": "test",
            "user_password": "pass",
        }

        # Make session raise an exception
        mock_session.side_effect = Exception("DB error")

        filter_obj = JournalReputationFilter(
            model=Mock(),
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        results = [{"title": "Test", "journal_ref": "Test Journal"}]
        filtered = filter_obj.filter_results(results, "test query")

        assert filtered == results  # Should return original on error

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_user_db_session"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_search_context"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_excludes_non_published_when_configured(
        self, mock_get_llm, mock_create_engine, mock_context, mock_session
    ):
        """Test that results without journal_ref are excluded when configured."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_create_engine.return_value = Mock()
        mock_context.return_value = {
            "username": "test",
            "user_password": "pass",
        }

        # Mock session to return None (no existing journal record)
        mock_session_context = MagicMock()
        mock_session_context.__enter__.return_value.query.return_value.filter_by.return_value.first.return_value = None
        mock_session.return_value = mock_session_context

        filter_obj = JournalReputationFilter(
            model=Mock(),
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=True,  # Exclude non-published
            quality_reanalysis_period=timedelta(days=365),
        )

        results = [
            {"title": "Test 1", "journal_ref": None},  # No journal
            {"title": "Test 2"},  # No journal_ref key
        ]
        filtered = filter_obj.filter_results(results, "test query")

        assert len(filtered) == 0  # Both should be excluded

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_user_db_session"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_search_context"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_includes_non_published_by_default(
        self, mock_get_llm, mock_create_engine, mock_context, mock_session
    ):
        """Test that results without journal_ref are included by default."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_create_engine.return_value = Mock()
        mock_context.return_value = {
            "username": "test",
            "user_password": "pass",
        }

        filter_obj = JournalReputationFilter(
            model=Mock(),
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,  # Include non-published
            quality_reanalysis_period=timedelta(days=365),
        )

        results = [
            {"title": "Test 1"},  # No journal_ref key
        ]
        filtered = filter_obj.filter_results(results, "test query")

        assert len(filtered) == 1  # Should be included


class TestCleanJournalName:
    """Tests for __clean_journal_name private method."""

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_invokes_model_for_cleaning(self, mock_get_llm, mock_create_engine):
        """Test that model is invoked to clean journal name."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_model = Mock()
        mock_response = Mock()
        mock_response.text.return_value = "Nature"
        mock_model.invoke.return_value = mock_response
        mock_create_engine.return_value = Mock()

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        # Access the private method for testing
        result = filter_obj._JournalReputationFilter__clean_journal_name(
            "Nature Vol. 123, pp. 45-67"
        )

        assert mock_model.invoke.called
        assert "Nature" in result


class TestAnalyzeJournalReputation:
    """Tests for __analyze_journal_reputation private method."""

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.AdvancedSearchSystem"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_analyzes_reputation_successfully(
        self, mock_get_llm, mock_create_engine, mock_search_system
    ):
        """Test successful journal reputation analysis."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_model = Mock()
        mock_response = Mock()
        mock_response.text.return_value = "8"
        mock_model.invoke.return_value = mock_response
        mock_create_engine.return_value = Mock()

        # Mock search system
        mock_system_instance = Mock()
        mock_system_instance.analyze_topic.return_value = {
            "findings": [{"content": "Nature is a Q1 journal"}]
        }
        mock_search_system.return_value = mock_system_instance

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        # Access the private method for testing
        result = (
            filter_obj._JournalReputationFilter__analyze_journal_reputation(
                "Nature"
            )
        )

        assert result == 8

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.AdvancedSearchSystem"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_clamps_reputation_to_valid_range(
        self, mock_get_llm, mock_create_engine, mock_search_system
    ):
        """Test that reputation is clamped to 1-10 range."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_model = Mock()
        mock_response = Mock()
        mock_response.text.return_value = "15"  # Above max
        mock_model.invoke.return_value = mock_response
        mock_create_engine.return_value = Mock()

        mock_system_instance = Mock()
        mock_system_instance.analyze_topic.return_value = {
            "findings": [{"content": "Info"}]
        }
        mock_search_system.return_value = mock_system_instance

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        # Clear cache for fresh test
        filter_obj._JournalReputationFilter__analyze_journal_reputation.cache_clear()

        result = (
            filter_obj._JournalReputationFilter__analyze_journal_reputation(
                "Test Journal"
            )
        )

        assert result == 10  # Clamped to max

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.AdvancedSearchSystem"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_raises_on_invalid_response(
        self, mock_get_llm, mock_create_engine, mock_search_system
    ):
        """Test that ValueError is raised on invalid response."""
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_model = Mock()
        mock_response = Mock()
        mock_response.text.return_value = "not a number"
        mock_model.invoke.return_value = mock_response
        mock_create_engine.return_value = Mock()

        mock_system_instance = Mock()
        mock_system_instance.analyze_topic.return_value = {
            "findings": [{"content": "Info"}]
        }
        mock_search_system.return_value = mock_system_instance

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        # Clear cache for fresh test
        filter_obj._JournalReputationFilter__analyze_journal_reputation.cache_clear()

        with pytest.raises(ValueError):
            filter_obj._JournalReputationFilter__analyze_journal_reputation(
                "Bad Journal"
            )


class TestCheckResult:
    """Tests for __check_result private method."""

    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_user_db_session"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_search_context"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.create_search_engine"
    )
    @patch(
        "local_deep_research.advanced_search_system.filters.journal_reputation_filter.get_llm"
    )
    def test_uses_cached_journal_quality(
        self, mock_get_llm, mock_create_engine, mock_context, mock_session
    ):
        """Test that cached journal quality is used when available."""
        import time

        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        mock_create_engine.return_value = Mock()
        mock_context.return_value = {
            "username": "test",
            "user_password": "pass",
        }

        # Mock journal from database with recent analysis
        mock_journal = Mock()
        mock_journal.quality = 8
        mock_journal.quality_analysis_time = int(time.time())  # Recent

        mock_session_context = MagicMock()
        mock_session_context.__enter__.return_value.query.return_value.filter_by.return_value.first.return_value = mock_journal
        mock_session.return_value = mock_session_context

        mock_model = Mock()
        mock_response = Mock()
        mock_response.text.return_value = "Test Journal"
        mock_model.invoke.return_value = mock_response

        filter_obj = JournalReputationFilter(
            model=mock_model,
            reliability_threshold=4,
            max_context=3000,
            exclude_non_published=False,
            quality_reanalysis_period=timedelta(days=365),
        )

        result = filter_obj._JournalReputationFilter__check_result(
            {"title": "Test", "journal_ref": "Test Journal"}
        )

        assert result is True  # 8 >= 4 threshold


class TestInheritance:
    """Tests for class inheritance."""

    def test_inherits_from_base_filter(self):
        """Test that JournalReputationFilter inherits from BaseFilter."""
        from local_deep_research.advanced_search_system.filters.base_filter import (
            BaseFilter,
        )
        from local_deep_research.advanced_search_system.filters.journal_reputation_filter import (
            JournalReputationFilter,
        )

        assert issubclass(JournalReputationFilter, BaseFilter)
