"""
Coverage tests for OpenAlexSearchEngine targeting statements not hit by
the existing test_search_engine_openalex.py.

Covers:
- __init__: journal_filter is not None → appended to content_filters;
  email retrieved from settings_snapshot; empty email treated as None
- _get_previews: rate-limit header logging branch; work that returns None from
  _format_work_preview is skipped without count; exception from safe_get returns []
- _format_work_preview: doi already starts with "https://doi.org/"; doi starts with
  neither "10." nor "https://" uses work_id as fallback; open_access=None (falsy);
  best_oa_location has no pdf_url but has landing_page_url; exception returns None
- _reconstruct_abstract: exception inside returns ""
- _get_full_content: item without abstract falls back to snippet
- Sort map: publication_date sort; unknown sort key defaults to relevance
"""

from unittest.mock import Mock, patch
import pytest

MODULE = "local_deep_research.web_search_engines.engines.search_engine_openalex"

# Suppress JournalReputationFilter initialisation in all tests


@pytest.fixture(autouse=True)
def mock_journal_filter():
    with patch(
        f"{MODULE}.JournalReputationFilter.create_default",
        return_value=None,
    ):
        yield


from local_deep_research.web_search_engines.engines.search_engine_openalex import (  # noqa: E402
    OpenAlexSearchEngine,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _engine(**kw):
    return OpenAlexSearchEngine(**kw)


def _mock_response(status=200, json_body=None, headers=None, text=""):
    resp = Mock()
    resp.status_code = status
    resp.json.return_value = json_body if json_body is not None else {}
    resp.headers = headers or {}
    resp.text = text
    return resp


def _results_response(*works):
    return _mock_response(
        status=200,
        json_body={"meta": {"count": len(works)}, "results": list(works)},
    )


# ---------------------------------------------------------------------------
# __init__
# ---------------------------------------------------------------------------


class TestInitExtra:
    def test_journal_filter_appended_when_not_none(self):
        """When JournalReputationFilter.create_default returns a real object,
        it must be added to content_filters passed to BaseSearchEngine."""
        fake_filter = Mock()
        with patch(
            f"{MODULE}.JournalReputationFilter.create_default",
            return_value=fake_filter,
        ):
            engine = OpenAlexSearchEngine()
        # preview_filters is stored on the parent as _preview_filters
        assert fake_filter in engine._preview_filters

    def test_email_from_settings_snapshot(self):
        # The local import "from ...config.search_config import get_setting_from_snapshot"
        # resolves to local_deep_research.config.search_config.get_setting_from_snapshot.
        snapshot = {"dummy": True}
        with patch(
            "local_deep_research.config.search_config.get_setting_from_snapshot",
            return_value="snap@example.com",
        ):
            engine = OpenAlexSearchEngine(settings_snapshot=snapshot)
        assert engine.email == "snap@example.com"

    def test_empty_email_treated_as_none(self):
        engine = _engine(email="")
        assert engine.email is None

    def test_email_settings_exception_ignored(self):
        with patch(
            "local_deep_research.config.search_config.get_setting_from_snapshot",
            side_effect=RuntimeError("db gone"),
        ):
            engine = OpenAlexSearchEngine(settings_snapshot={})
        assert engine.email is None


# ---------------------------------------------------------------------------
# _get_previews — extra branches
# ---------------------------------------------------------------------------


class TestGetPreviewsExtra:
    def test_rate_limit_header_logged(self):
        """Response with x-ratelimit-remaining header must not crash."""
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "T",
            "doi": "https://doi.org/10.1/test",
        }
        resp = _mock_response(
            status=200,
            json_body={"meta": {"count": 1}, "results": [work]},
            headers={"x-ratelimit-remaining": "99", "x-ratelimit-limit": "100"},
        )
        with patch(f"{MODULE}.safe_get", return_value=resp):
            engine = _engine()
            previews = engine._get_previews("test")
        assert len(previews) == 1

    def test_format_work_returns_none_is_skipped(self):
        """Works for which _format_work_preview returns None are excluded."""
        works = [
            {"id": "https://openalex.org/W1", "display_name": "OK"},
            {"id": "https://openalex.org/W2", "display_name": "FAIL"},
        ]
        resp = _mock_response(
            status=200,
            json_body={"meta": {"count": 2}, "results": works},
        )

        def _format_side_effect(work):
            if "FAIL" in work.get("display_name", ""):
                return None
            return {
                "id": work["id"],
                "title": work["display_name"],
                "link": work["id"],
                "snippet": "s",
                "authors": "",
                "year": None,
                "date": None,
                "journal": "j",
                "citations": 0,
                "is_open_access": False,
                "oa_url": None,
                "abstract": None,
                "type": "academic_paper",
            }

        with patch(f"{MODULE}.safe_get", return_value=resp):
            engine = _engine()
            with patch.object(
                engine, "_format_work_preview", side_effect=_format_side_effect
            ):
                previews = engine._get_previews("test")
        assert len(previews) == 1

    def test_publication_date_sort(self):
        resp = _mock_response(
            status=200,
            json_body={"meta": {"count": 0}, "results": []},
        )
        with patch(f"{MODULE}.safe_get", return_value=resp) as mock_get:
            engine = _engine(sort_by="publication_date")
            engine._get_previews("test")
        params = mock_get.call_args[1]["params"]
        assert params["sort"] == "publication_date:desc"

    def test_unknown_sort_key_defaults_to_relevance(self):
        resp = _mock_response(
            status=200,
            json_body={"meta": {"count": 0}, "results": []},
        )
        with patch(f"{MODULE}.safe_get", return_value=resp) as mock_get:
            engine = _engine(sort_by="some_unknown_sort")
            engine._get_previews("test")
        params = mock_get.call_args[1]["params"]
        assert params["sort"] == "relevance_score:desc"


# ---------------------------------------------------------------------------
# _format_work_preview — extra branches
# ---------------------------------------------------------------------------


class TestFormatWorkPreviewExtra:
    def test_doi_already_full_https_doi_url(self):
        """DOI that already starts with https://doi.org/ is used as-is."""
        engine = _engine()
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "T",
            "doi": "https://doi.org/10.1/already-full",
        }
        preview = engine._format_work_preview(work)
        assert preview["link"] == "https://doi.org/10.1/already-full"

    def test_doi_not_http_not_10_uses_work_id(self):
        """DOI that starts with neither http nor '10.' falls back to work_id."""
        engine = _engine()
        work = {
            "id": "https://openalex.org/W99",
            "display_name": "T",
            "doi": "some-weird-doi-format",
        }
        preview = engine._format_work_preview(work)
        assert preview["link"] == "https://openalex.org/W99"

    def test_open_access_none_is_falsy(self):
        """open_access=None should result in is_oa=False, oa_url=None."""
        engine = _engine()
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "T",
            "open_access": None,
        }
        preview = engine._format_work_preview(work)
        assert preview["is_open_access"] is False
        assert preview["oa_url"] is None

    def test_oa_url_falls_back_to_landing_page_when_no_pdf(self):
        engine = _engine()
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "T",
            "open_access": {"is_oa": True},
            "best_oa_location": {"landing_page_url": "https://landing.com"},
        }
        preview = engine._format_work_preview(work)
        assert preview["oa_url"] == "https://landing.com"

    def test_exception_returns_none(self):
        engine = _engine()
        # Pass something that will blow up during dict access
        with patch.object(
            engine, "_reconstruct_abstract", side_effect=RuntimeError("boom")
        ):
            work = {
                "id": "https://openalex.org/W1",
                "display_name": "T",
                "abstract_inverted_index": {"word": [0]},
            }
            result = engine._format_work_preview(work)
        assert result is None

    def test_snippet_uses_title_when_no_abstract(self):
        engine = _engine()
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "My Title",
        }
        preview = engine._format_work_preview(work)
        assert "My Title" in preview["snippet"]

    def test_primary_location_without_source(self):
        """primary_location present but source=None → journal emitted as None.

        The "unknown" sentinel is intentionally stripped at the engine
        boundary so it never reaches the citation normalizer or matches a
        real OpenAlex source named "unknown".
        """
        engine = _engine()
        work = {
            "id": "https://openalex.org/W1",
            "display_name": "T",
            "primary_location": {"source": None},
        }
        preview = engine._format_work_preview(work)
        assert preview["journal"] is None


# ---------------------------------------------------------------------------
# _reconstruct_abstract — exception path
# ---------------------------------------------------------------------------


class TestReconstructAbstractExtra:
    def test_exception_returns_empty(self):
        engine = _engine()
        # Pass a non-dict to trigger the exception inside the method
        result = engine._reconstruct_abstract(None)  # type: ignore[arg-type]
        assert result == ""


# ---------------------------------------------------------------------------
# _get_full_content
# ---------------------------------------------------------------------------


class TestGetFullContentExtra:
    def test_item_without_abstract_uses_snippet(self):
        engine = _engine()
        items = [
            {"title": "T", "link": "https://t.com", "snippet": "the snippet"}
        ]
        results = engine._get_full_content(items)
        assert results[0]["content"] == "the snippet"

    def test_metadata_defaults_when_fields_missing(self):
        engine = _engine()
        items = [{"title": "T", "link": "https://t.com"}]
        results = engine._get_full_content(items)
        meta = results[0]["metadata"]
        assert meta["citations"] == 0
        assert meta["is_open_access"] is False
        assert meta["oa_url"] is None
