"""
Comprehensive tests for the OpenAlex search engine.
Tests initialization, search functionality, abstract reconstruction, and filtering.

Note: These tests mock HTTP requests to avoid requiring an API connection.
"""

import pytest
from unittest.mock import Mock


@pytest.fixture(autouse=True)
def mock_journal_filter(monkeypatch):
    """Mock JournalReputationFilter to avoid LLM initialization."""
    monkeypatch.setattr(
        "local_deep_research.web_search_engines.engines.search_engine_openalex.JournalReputationFilter.create_default",
        Mock(return_value=None),
    )


class TestOpenAlexSearchEngineInit:
    """Tests for OpenAlex search engine initialization."""

    def test_init_default_values(self):
        """Test initialization with default values."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()

        assert engine.max_results == 25
        assert engine.sort_by == "relevance"
        assert engine.filter_open_access is False
        assert engine.min_citations == 0
        assert engine.is_public is True
        assert engine.is_scientific is True

    def test_init_with_custom_max_results(self):
        """Test initialization with custom max results."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(max_results=50)
        assert engine.max_results == 50

    def test_init_with_email(self):
        """Test initialization with email for polite pool."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(email="test@example.com")
        assert engine.email == "test@example.com"
        assert "test@example.com" in engine.headers["User-Agent"]

    def test_init_without_email(self):
        """Test initialization without email."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()
        assert engine.email is None

    def test_init_with_sort_by(self):
        """Test initialization with different sort options."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(sort_by="cited_by_count")
        assert engine.sort_by == "cited_by_count"

        engine_date = OpenAlexSearchEngine(sort_by="publication_date")
        assert engine_date.sort_by == "publication_date"

    def test_init_with_open_access_filter(self):
        """Test initialization with open access filter."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(filter_open_access=True)
        assert engine.filter_open_access is True

    def test_init_with_min_citations(self):
        """Test initialization with minimum citations filter."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(min_citations=100)
        assert engine.min_citations == 100

    def test_init_with_publication_date_filter(self):
        """Test initialization with publication date filter."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(from_publication_date="2023-01-01")
        assert engine.from_publication_date == "2023-01-01"

    def test_init_ignores_false_string_email(self):
        """Test that 'False' string for email is treated as None."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(email="False")
        assert engine.email is None

    def test_init_ignores_false_string_date(self):
        """Test that 'False' string for from_publication_date is treated as None."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine(from_publication_date="False")
        assert engine.from_publication_date is None

    def test_api_base_url_set(self):
        """Test that API base URL is correctly set."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()
        assert engine.api_base == "https://api.openalex.org"


class TestOpenAlexEngineType:
    """Tests for OpenAlex 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_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()
        assert "openalex" in engine.engine_type.lower()

    def test_engine_is_public(self):
        """Test that engine is marked as public."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()
        assert engine.is_public is True

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

        engine = OpenAlexSearchEngine()
        assert engine.is_scientific is True


class TestOpenAlexAbstractReconstruction:
    """Tests for OpenAlex abstract reconstruction from inverted index."""

    def test_reconstruct_abstract_simple(self):
        """Test simple abstract reconstruction."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()

        inverted_index = {
            "This": [0],
            "is": [1],
            "a": [2],
            "test": [3],
            "abstract": [4],
        }

        result = engine._reconstruct_abstract(inverted_index)
        assert result == "This is a test abstract"

    def test_reconstruct_abstract_with_duplicate_words(self):
        """Test abstract reconstruction with words appearing multiple times."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()

        inverted_index = {
            "The": [0],
            "cat": [1],
            "sat": [2],
            "on": [3],
            "the": [4],
            "mat": [5],
        }

        result = engine._reconstruct_abstract(inverted_index)
        assert result == "The cat sat on the mat"

    def test_reconstruct_abstract_empty(self):
        """Test abstract reconstruction with empty inverted index."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()
        result = engine._reconstruct_abstract({})
        assert result == ""


class TestOpenAlexSearchExecution:
    """Tests for OpenAlex search execution."""

    @pytest.fixture
    def engine(self):
        """Create an OpenAlex engine."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        return OpenAlexSearchEngine(max_results=10)

    def test_get_previews_success(self, engine, monkeypatch):
        """Test successful preview retrieval."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.headers = {}
        mock_response.json = Mock(
            return_value={
                "meta": {"count": 100},
                "results": [
                    {
                        "id": "https://openalex.org/W123456",
                        "display_name": "Test Paper Title",
                        "publication_year": 2023,
                        "publication_date": "2023-06-15",
                        "doi": "https://doi.org/10.1234/test",
                        "primary_location": {
                            "source": {"display_name": "Nature"}
                        },
                        "authorships": [
                            {"author": {"display_name": "John Doe"}},
                            {"author": {"display_name": "Jane Smith"}},
                        ],
                        "cited_by_count": 50,
                        "open_access": {"is_oa": True},
                        "best_oa_location": {
                            "pdf_url": "https://example.com/paper.pdf"
                        },
                        "abstract_inverted_index": {
                            "This": [0],
                            "is": [1],
                            "an": [2],
                            "abstract": [3],
                        },
                    }
                ],
            }
        )

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("machine learning")

        assert len(previews) == 1
        assert previews[0]["title"] == "Test Paper Title"
        assert previews[0]["year"] == 2023
        assert "John Doe" in previews[0]["authors"]
        assert previews[0]["citations"] == 50
        assert previews[0]["is_open_access"] is True

    def test_get_previews_empty_results(self, engine, monkeypatch):
        """Test preview retrieval with no results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.headers = {}
        mock_response.json = Mock(
            return_value={"meta": {"count": 0}, "results": []}
        )

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("nonexistent topic xyz123")

        assert previews == []

    def test_get_previews_rate_limit_error(self, engine, monkeypatch):
        """Test that 429 errors raise RateLimitError for retry handling."""
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        mock_response = Mock()
        mock_response.status_code = 429
        mock_response.headers = {}

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.safe_get",
            Mock(return_value=mock_response),
        )

        # Rate limit should raise RateLimitError for base class retry handling
        with pytest.raises(RateLimitError):
            engine._get_previews("test query")

    def test_get_previews_handles_exception(self, engine, monkeypatch):
        """Test that exceptions are handled gracefully."""
        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.safe_get",
            Mock(side_effect=Exception("Network error")),
        )

        previews = engine._get_previews("test query")
        assert previews == []

    def test_get_previews_handles_api_error(self, engine, monkeypatch):
        """Test that API errors return empty results."""
        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.text = "Internal Server Error"
        mock_response.headers = {}

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test query")
        assert previews == []


class TestOpenAlexFormatWorkPreview:
    """Tests for OpenAlex work preview formatting."""

    @pytest.fixture
    def engine(self):
        """Create an OpenAlex engine."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        return OpenAlexSearchEngine()

    def test_format_work_with_doi(self, engine):
        """Test formatting work with DOI URL."""
        work = {
            "id": "https://openalex.org/W123",
            "display_name": "Test Paper",
            "publication_year": 2023,
            "publication_date": "2023-01-15",
            "doi": "https://doi.org/10.1234/test",
            "primary_location": {"source": {"display_name": "Test Journal"}},
            "authorships": [{"author": {"display_name": "Test Author"}}],
            "cited_by_count": 25,
            "open_access": {"is_oa": False},
        }

        preview = engine._format_work_preview(work)

        assert preview["title"] == "Test Paper"
        assert preview["link"] == "https://doi.org/10.1234/test"
        assert preview["year"] == 2023
        assert preview["journal"] == "Test Journal"
        assert preview["citations"] == 25
        assert preview["is_open_access"] is False

    def test_format_work_without_doi(self, engine):
        """Test formatting work without DOI falls back to OpenAlex URL."""
        work = {
            "id": "https://openalex.org/W123",
            "display_name": "Test Paper",
            "publication_year": 2023,
            "publication_date": "2023-01-15",
            "primary_location": {},
            "authorships": [],
            "cited_by_count": 0,
            "open_access": {"is_oa": False},
        }

        preview = engine._format_work_preview(work)

        assert preview["link"] == "https://openalex.org/W123"

    def test_format_work_with_many_authors(self, engine):
        """Test formatting work with many authors shows et al."""
        work = {
            "id": "https://openalex.org/W123",
            "display_name": "Test Paper",
            "publication_year": 2023,
            "publication_date": "2023-01-15",
            "primary_location": {},
            "authorships": [
                {"author": {"display_name": f"Author {i}"}} for i in range(10)
            ],
            "cited_by_count": 0,
            "open_access": {},
        }

        preview = engine._format_work_preview(work)

        assert "et al." in preview["authors"]

    def test_format_work_with_abstract(self, engine):
        """Test formatting work with abstract."""
        work = {
            "id": "https://openalex.org/W123",
            "display_name": "Test Paper",
            "publication_year": 2023,
            "publication_date": "2023-01-15",
            "primary_location": {},
            "authorships": [],
            "cited_by_count": 0,
            "open_access": {},
            "abstract_inverted_index": {
                "This": [0],
                "is": [1],
                "the": [2],
                "abstract": [3],
            },
        }

        preview = engine._format_work_preview(work)

        assert preview["abstract"] == "This is the abstract"
        assert "abstract" in preview["snippet"].lower()


class TestOpenAlexFullContent:
    """Tests for OpenAlex full content retrieval."""

    def test_get_full_content_returns_results(self):
        """Test that full content returns processed results."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        engine = OpenAlexSearchEngine()

        items = [
            {
                "title": "Test Paper",
                "link": "https://doi.org/10.1234/test",
                "snippet": "Test snippet",
                "abstract": "Full abstract text",
                "authors": "John Doe",
                "year": 2023,
                "journal": "Test Journal",
                "citations": 50,
                "is_open_access": True,
                "oa_url": "https://example.com/paper.pdf",
            }
        ]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert results[0]["title"] == "Test Paper"
        assert results[0]["content"] == "Full abstract text"
        assert results[0]["metadata"]["authors"] == "John Doe"
        assert results[0]["metadata"]["year"] == 2023


class TestOpenAlexFilterSettings:
    """Tests for OpenAlex filter and settings configuration."""

    def test_email_from_settings_snapshot(self, monkeypatch):
        """Test that email can be loaded from settings snapshot."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        settings = {"search.engine.web.openalex.email": "settings@example.com"}

        def mock_get_setting(key, settings_snapshot=None, default=None):
            return settings.get(key, default)

        monkeypatch.setattr(
            "local_deep_research.config.search_config.get_setting_from_snapshot",
            mock_get_setting,
        )

        engine = OpenAlexSearchEngine(settings_snapshot=settings)
        assert engine.email == "settings@example.com"

    def test_journal_reputation_filter_integration(self, monkeypatch):
        """Test that journal reputation filter is created when available."""
        from local_deep_research.web_search_engines.engines.search_engine_openalex import (
            OpenAlexSearchEngine,
        )

        # Create a mock filter to be returned
        mock_filter = Mock()
        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_openalex.JournalReputationFilter.create_default",
            Mock(return_value=mock_filter),
        )

        engine = OpenAlexSearchEngine()

        # Verify engine was created successfully
        assert engine is not None
        # When filter is returned, it should be in the filters list
        assert mock_filter in engine._content_filters
