"""
Comprehensive tests for the Semantic Scholar search engine.
Tests initialization, configuration, and API parameters.
"""

from unittest.mock import Mock, patch
import pytest


class TestSemanticScholarSearchEngineInit:
    """Tests for Semantic Scholar search engine initialization."""

    def test_init_default_parameters(self):
        """Test initialization with default parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        assert engine.max_results >= 10
        assert engine.is_public is True
        assert engine.is_scientific is True

    def test_init_with_api_key(self):
        """Test initialization with API key."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(api_key="test_api_key")
        assert engine.api_key == "test_api_key"

    def test_init_custom_max_results(self):
        """Test initialization with custom max results."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(max_results=50)
        assert engine.max_results >= 50

    def test_init_with_custom_parameters(self):
        """Test initialization with various custom parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            max_results=100,
            api_key="my_key",
            max_retries=5,
        )

        assert engine.max_results >= 100
        assert engine.api_key == "my_key"


class TestSemanticScholarSession:
    """Tests for Semantic Scholar session creation."""

    def test_session_created(self):
        """Test that a requests session is created."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()
        session = engine._create_session()

        assert session is not None

    def test_session_has_retry_adapter(self):
        """Test that session has retry adapter configured."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()
        session = engine._create_session()

        # Check that adapters are mounted
        assert "https://" in session.adapters
        assert "http://" in session.adapters


class TestSemanticScholarAPIConfiguration:
    """Tests for Semantic Scholar API configuration."""

    def test_api_key_in_headers(self):
        """Test that API key is included in headers when provided."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(api_key="my_s2_api_key")
        assert engine.api_key == "my_s2_api_key"

    def test_base_url_configured(self):
        """Test that base URL is properly configured."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()
        assert hasattr(engine, "base_url")
        assert "semanticscholar" in engine.base_url.lower()


class TestSemanticScholarEngineType:
    """Tests for Semantic Scholar engine type identification."""

    def test_engine_type_set(self):
        """Test that engine type is properly set."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()
        # engine_type is derived from class name
        assert (
            "semantic" in engine.engine_type.lower()
            or "scholar" in engine.engine_type.lower()
        )

    def test_engine_is_scientific(self):
        """Test that engine is marked as scientific."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()
        assert engine.is_scientific is True
        assert engine.is_generic is False


class TestSemanticScholarAdvancedInit:
    """Tests for advanced initialization options."""

    def test_init_year_range(self):
        """Test initialization with year range filter."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(year_range=(2020, 2024))
        assert engine.year_range == (2020, 2024)

    def test_init_fields_of_study(self):
        """Test initialization with fields of study filter."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            fields_of_study=["Computer Science", "Biology"]
        )
        assert engine.fields_of_study == ["Computer Science", "Biology"]

    def test_init_publication_types(self):
        """Test initialization with publication types filter."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            publication_types=["JournalArticle", "Conference"]
        )
        assert engine.publication_types == ["JournalArticle", "Conference"]

    def test_init_with_llm(self):
        """Test initialization with LLM for optimization."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        engine = SemanticScholarSearchEngine(
            llm=mock_llm, optimize_queries=True
        )

        assert engine.llm is mock_llm
        assert engine.optimize_queries is True

    def test_init_reference_citation_options(self):
        """Test initialization with citation and reference options."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            get_references=True,
            get_citations=True,
            citation_limit=20,
            reference_limit=15,
        )

        assert engine.get_references is True
        assert engine.get_citations is True
        assert engine.citation_limit == 20
        assert engine.reference_limit == 15


class TestSemanticScholarMakeRequest:
    """Tests for _make_request method."""

    def test_make_request_get_success(self):
        """Test successful GET request."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"data": [{"title": "Test Paper"}]}
        mock_response.raise_for_status = Mock()

        with patch.object(engine.session, "get", return_value=mock_response):
            result = engine._make_request("https://api.test.com", {"q": "test"})

            assert result == {"data": [{"title": "Test Paper"}]}

    def test_make_request_handles_rate_limit(self):
        """Test that 429 status triggers RateLimitError."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        engine = SemanticScholarSearchEngine()

        mock_response = Mock()
        mock_response.status_code = 429

        with patch.object(engine.session, "get", return_value=mock_response):
            with pytest.raises(RateLimitError):
                engine._make_request("https://api.test.com", {"q": "test"})

    def test_make_request_post_method(self):
        """Test POST request method."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"result": "success"}
        mock_response.raise_for_status = Mock()

        with patch.object(
            engine.session, "post", return_value=mock_response
        ) as mock_post:
            result = engine._make_request(
                "https://api.test.com",
                params={"a": "b"},
                data={"x": "y"},
                method="POST",
            )

            assert result == {"result": "success"}
            mock_post.assert_called_once()

    def test_make_request_invalid_method(self):
        """Test that invalid method raises ValueError."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with pytest.raises(ValueError, match="Unsupported HTTP method"):
            engine._make_request("https://api.test.com", method="DELETE")

    def test_make_request_returns_empty_on_error(self):
        """Test that request error returns empty dict."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )
        import requests

        engine = SemanticScholarSearchEngine()

        with patch.object(
            engine.session,
            "get",
            side_effect=requests.RequestException("Network error"),
        ):
            result = engine._make_request("https://api.test.com")

            assert result == {}


class TestSemanticScholarOptimizeQuery:
    """Tests for _optimize_query method."""

    def test_optimize_query_without_llm(self):
        """Test that query is returned unchanged without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(llm=None)
        result = engine._optimize_query("machine learning for climate")

        assert result == "machine learning for climate"

    def test_optimize_query_disabled(self):
        """Test that query is returned unchanged when optimization disabled."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        engine = SemanticScholarSearchEngine(
            llm=mock_llm, optimize_queries=False
        )
        result = engine._optimize_query("machine learning for climate")

        assert result == "machine learning for climate"
        mock_llm.invoke.assert_not_called()

    def test_optimize_query_with_llm(self):
        """Test query optimization using LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="machine learning climate prediction"
        )

        engine = SemanticScholarSearchEngine(
            llm=mock_llm, optimize_queries=True
        )
        result = engine._optimize_query("What is machine learning for climate?")

        assert "machine learning" in result.lower()
        mock_llm.invoke.assert_called_once()

    def test_optimize_query_handles_verbose_response(self):
        """Test that verbose LLM responses fall back to original query."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        # Return a very verbose response
        mock_llm.invoke.return_value = Mock(
            content="Here is the optimized query: this is a very long explanation that "
            "should not be used as a search query because it is too verbose"
        )

        engine = SemanticScholarSearchEngine(
            llm=mock_llm, optimize_queries=True
        )
        result = engine._optimize_query("test query")

        # Should fall back to original query
        assert result == "test query"

    def test_optimize_query_handles_error(self):
        """Test that LLM error falls back to original query."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("LLM error")

        engine = SemanticScholarSearchEngine(
            llm=mock_llm, optimize_queries=True
        )
        result = engine._optimize_query("test query")

        assert result == "test query"


class TestSemanticScholarDirectSearch:
    """Tests for _direct_search method."""

    def test_direct_search_success(self):
        """Test successful direct search."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_papers = [
            {"paperId": "123", "title": "Test Paper 1"},
            {"paperId": "456", "title": "Test Paper 2"},
        ]

        with patch.object(
            engine, "_make_request", return_value={"data": mock_papers}
        ):
            result = engine._direct_search("machine learning")

            assert len(result) == 2
            assert result[0]["title"] == "Test Paper 1"

    def test_direct_search_no_data(self):
        """Test direct search with no data in response."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with patch.object(engine, "_make_request", return_value={}):
            result = engine._direct_search("nonexistent topic")

            assert result == []

    def test_direct_search_with_year_range(self):
        """Test direct search includes year filter."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(year_range=(2020, 2024))

        with patch.object(
            engine, "_make_request", return_value={"data": []}
        ) as mock_request:
            engine._direct_search("test query")

            # Check that year param was included
            call_args = mock_request.call_args
            params = call_args[0][1]
            assert params.get("year") == "2020-2024"

    def test_direct_search_with_fields_of_study(self):
        """Test direct search includes fields of study filter."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            fields_of_study=["Computer Science"]
        )

        with patch.object(
            engine, "_make_request", return_value={"data": []}
        ) as mock_request:
            engine._direct_search("test query")

            call_args = mock_request.call_args
            params = call_args[0][1]
            assert params.get("fieldsOfStudy") == "Computer Science"

    def test_direct_search_handles_error(self):
        """Test direct search handles errors gracefully."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with patch.object(
            engine, "_make_request", side_effect=Exception("API error")
        ):
            result = engine._direct_search("test query")

            assert result == []


class TestSemanticScholarAdaptiveSearch:
    """Tests for _adaptive_search method."""

    def test_adaptive_search_standard_success(self):
        """Test standard search returns results."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(llm=None)
        mock_papers = [{"paperId": "123", "title": "Test"}]

        with patch.object(engine, "_direct_search", return_value=mock_papers):
            papers, strategy = engine._adaptive_search("test query")

            assert len(papers) == 1
            assert strategy == "standard"

    def test_adaptive_search_unquoted_fallback(self):
        """Test unquoted fallback when quoted search fails."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(llm=None)

        def side_effect(query):
            if '"' in query:
                return []
            return [{"paperId": "123"}]

        with patch.object(engine, "_direct_search", side_effect=side_effect):
            papers, strategy = engine._adaptive_search('"exact phrase" search')

            assert len(papers) == 1
            assert strategy == "unquoted"

    def test_adaptive_search_llm_alternative(self):
        """Test LLM-suggested alternatives when search fails."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="alternative query\nbetter query\nfinal query"
        )

        engine = SemanticScholarSearchEngine(llm=mock_llm)

        call_count = 0

        def side_effect(query):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return []  # Original fails
            return [{"paperId": "123"}]  # Alternative succeeds

        with patch.object(engine, "_direct_search", side_effect=side_effect):
            papers, strategy = engine._adaptive_search("test query")

            assert len(papers) == 1
            assert strategy == "llm_alternative"

    def test_adaptive_search_key_terms_fallback(self):
        """Test key terms fallback when other strategies fail."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(llm=None)

        call_count = 0

        def side_effect(query):
            nonlocal call_count
            call_count += 1
            # Fail for full query, succeed for key terms (longer words)
            if len(query.split()) >= 3:
                return []
            return [{"paperId": "123"}]

        with patch.object(engine, "_direct_search", side_effect=side_effect):
            papers, strategy = engine._adaptive_search(
                "machine learning predictions"
            )

            assert strategy in ["key_terms", "single_term"]


class TestSemanticScholarGetPaperDetails:
    """Tests for _get_paper_details method."""

    def test_get_paper_details_success(self):
        """Test successful paper details retrieval."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_details = {
            "paperId": "123",
            "title": "Test Paper",
            "abstract": "Test abstract",
            "authors": [{"name": "Author A"}],
        }

        with patch.object(engine, "_make_request", return_value=mock_details):
            result = engine._get_paper_details("123")

            assert result["title"] == "Test Paper"
            assert result["abstract"] == "Test abstract"

    def test_get_paper_details_with_citations(self):
        """Test paper details includes citations when requested."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            get_citations=True, citation_limit=5
        )

        with patch.object(
            engine, "_make_request", return_value={}
        ) as mock_request:
            engine._get_paper_details("123")

            call_args = mock_request.call_args
            params = call_args[0][1]
            fields = params.get("fields")
            assert "citations.limit(5)" in fields

    def test_get_paper_details_with_references(self):
        """Test paper details includes references when requested."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine(
            get_references=True, reference_limit=10
        )

        with patch.object(
            engine, "_make_request", return_value={}
        ) as mock_request:
            engine._get_paper_details("456")

            call_args = mock_request.call_args
            params = call_args[0][1]
            fields = params.get("fields")
            assert "references.limit(10)" in fields

    def test_get_paper_details_handles_error(self):
        """Test paper details handles errors gracefully."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with patch.object(
            engine, "_make_request", side_effect=Exception("API error")
        ):
            result = engine._get_paper_details("123")

            assert result == {}


class TestSemanticScholarGetPreviews:
    """Tests for _get_previews method."""

    def test_get_previews_success(self):
        """Test successful preview retrieval."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_papers = [
            {
                "paperId": "123",
                "title": "Test Paper",
                "abstract": "This is a test abstract",
                "url": "https://example.com/paper",
                "authors": [{"name": "Author A"}],
                "venue": "Test Journal",
                "year": 2023,
            }
        ]

        with patch.object(engine, "_optimize_query", return_value="test"):
            with patch.object(
                engine,
                "_adaptive_search",
                return_value=(mock_papers, "standard"),
            ):
                result = engine._get_previews("test query")

                assert len(result) == 1
                assert result[0]["title"] == "Test Paper"

    def test_get_previews_no_results(self):
        """Test preview returns empty list when no results."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with patch.object(engine, "_optimize_query", return_value="test"):
            with patch.object(
                engine, "_adaptive_search", return_value=([], "standard")
            ):
                result = engine._get_previews("test query")

                assert result == []

    def test_get_previews_handles_missing_fields(self):
        """Test preview handles papers with missing fields."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_papers = [
            {
                "paperId": "123",
                "title": "Test Paper",
                # Missing abstract, authors, etc.
            }
        ]

        with patch.object(engine, "_optimize_query", return_value="test"):
            with patch.object(
                engine,
                "_adaptive_search",
                return_value=(mock_papers, "standard"),
            ):
                result = engine._get_previews("test query")

                assert len(result) == 1
                assert result[0]["title"] == "Test Paper"

    def test_get_previews_formats_authors(self):
        """Test preview correctly formats authors."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        mock_papers = [
            {
                "paperId": "123",
                "title": "Test Paper",
                "authors": [
                    {"name": "Alice Smith"},
                    {"name": "Bob Jones"},
                ],
            }
        ]

        with patch.object(engine, "_optimize_query", return_value="test"):
            with patch.object(
                engine,
                "_adaptive_search",
                return_value=(mock_papers, "standard"),
            ):
                result = engine._get_previews("test query")

                # Authors should be extracted
                assert len(result) == 1


class TestSemanticScholarResourceCleanup:
    """Tests for session cleanup and resource management."""

    def test_close_closes_session(self):
        """Test that close() properly closes the session."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        # Verify session exists
        assert engine.session is not None

        # Close the engine
        engine.close()

        # Session should be None after close
        assert engine.session is None

    def test_close_handles_none_session(self):
        """Test that close() handles already-closed session gracefully."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        # Close twice - should not raise
        engine.close()
        engine.close()  # Should not raise

        assert engine.session is None

    def test_close_handles_exception(self):
        """Test that close() handles session.close() exceptions."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        # Mock session to raise on close
        mock_session = Mock()
        mock_session.close.side_effect = Exception("Close failed")
        engine.session = mock_session

        # Should not raise, just log the exception
        engine.close()

        # Session should be set to None even after exception
        assert engine.session is None

    def test_del_calls_close(self):
        """Test that __del__ calls close()."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        with patch.object(engine, "close") as mock_close:
            engine.__del__()
            mock_close.assert_called_once()

    def test_context_manager_calls_close(self):
        """Test that exiting context manager calls close()."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        with patch.object(SemanticScholarSearchEngine, "close") as mock_close:
            with SemanticScholarSearchEngine() as engine:
                assert engine is not None

            mock_close.assert_called_once()

    def test_context_manager_returns_self(self):
        """Test that __enter__ returns self."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = SemanticScholarSearchEngine()

        result = engine.__enter__()

        assert result is engine

        # Clean up
        engine.close()

    def test_context_manager_closes_on_exception(self):
        """Test that context manager closes session even when exception occurs."""
        from local_deep_research.web_search_engines.engines.search_engine_semantic_scholar import (
            SemanticScholarSearchEngine,
        )

        engine = None
        try:
            with SemanticScholarSearchEngine() as eng:
                engine = eng
                raise ValueError("Test exception")
        except ValueError:
            pass

        # Session should be closed even after exception
        assert engine.session is None
