"""
Extra coverage tests for GooglePSESearchEngine targeting statements not hit
by the existing test_search_engine_google_pse_coverage.py.

Covers:
- _validate_connection: success, API error in response body, unexpected exception
- _make_request: no last_exception branch (all retries hit generic Exception with
  no rate-limit keyword)
- _get_previews: exception inside loop is swallowed and breaks loop; item with
  empty-string link is skipped; max_results cap honoured mid-page
- Language mapping: unknown language defaults to "en"; several known languages
- safe_search=False sets safe="off"
- class-level flags (is_public, is_generic)
"""

from unittest.mock import Mock, patch

import pytest
from requests.exceptions import RequestException

MODULE = (
    "local_deep_research.web_search_engines.engines.search_engine_google_pse"
)

from local_deep_research.web_search_engines.engines.search_engine_google_pse import (  # noqa: E402
    GooglePSESearchEngine,
)


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


def _make_engine(**overrides):
    """Build an engine with _validate_connection bypassed."""
    defaults = dict(
        api_key="k", search_engine_id="eid", max_retries=2, retry_delay=0.01
    )
    defaults.update(overrides)
    with patch.object(GooglePSESearchEngine, "_validate_connection"):
        return GooglePSESearchEngine(**defaults)


def _ok_response(json_body=None):
    resp = Mock()
    resp.json.return_value = json_body or {}
    resp.raise_for_status = Mock()
    return resp


# ---------------------------------------------------------------------------
# Class-level flags
# ---------------------------------------------------------------------------


class TestClassFlags:
    def test_is_public(self):
        assert GooglePSESearchEngine.is_public is True

    def test_is_generic(self):
        assert GooglePSESearchEngine.is_generic is True


# ---------------------------------------------------------------------------
# __init__ — language mapping
# ---------------------------------------------------------------------------


class TestLanguageMapping:
    @pytest.mark.parametrize(
        "lang_str,expected_code",
        [
            ("Spanish", "es"),
            ("french", "fr"),
            ("GERMAN", "de"),
            ("Italian", "it"),
            ("Japanese", "ja"),
            ("korean", "ko"),
            ("Portuguese", "pt"),
            ("Russian", "ru"),
            ("Chinese", "zh-CN"),
            ("Swahili", "en"),  # unknown → default
        ],
    )
    def test_language_code_resolved(self, lang_str, expected_code):
        engine = _make_engine(search_language=lang_str)
        assert engine.language == expected_code

    def test_safe_search_false_sets_off(self):
        engine = _make_engine(safe_search=False)
        assert engine.safe == "off"

    def test_safe_search_true_sets_active(self):
        engine = _make_engine(safe_search=True)
        assert engine.safe == "active"


# ---------------------------------------------------------------------------
# _validate_connection
# ---------------------------------------------------------------------------


class TestValidateConnection:
    def test_success_logs_info(self):
        """_validate_connection returns True when _make_request returns valid body."""
        with patch.object(GooglePSESearchEngine, "_validate_connection"):
            engine = GooglePSESearchEngine(api_key="k", search_engine_id="eid")

        # Now actually test _validate_connection with _make_request mocked
        engine._make_request = Mock(return_value={})
        result = engine._validate_connection()
        assert result is True

    def test_api_error_in_body_raises_value_error(self):
        """If response body contains 'error', a ValueError is raised."""
        with patch.object(GooglePSESearchEngine, "_validate_connection"):
            engine = GooglePSESearchEngine(api_key="k", search_engine_id="eid")

        engine._make_request = Mock(
            return_value={"error": {"message": "API key invalid"}}
        )
        with pytest.raises(ValueError, match="API key invalid"):
            engine._validate_connection()

    def test_make_request_raises_propagates(self):
        """If _make_request raises, _validate_connection re-raises."""
        with patch.object(GooglePSESearchEngine, "_validate_connection"):
            engine = GooglePSESearchEngine(api_key="k", search_engine_id="eid")

        engine._make_request = Mock(side_effect=RequestException("boom"))
        with pytest.raises(RequestException):
            engine._validate_connection()


# ---------------------------------------------------------------------------
# __init__ — missing api_key / engine_id raises
# ---------------------------------------------------------------------------


class TestInitValidation:
    def test_missing_api_key_raises(self):
        with pytest.raises(ValueError, match="Google API key"):
            GooglePSESearchEngine(search_engine_id="eid")

    def test_missing_engine_id_raises(self):
        with patch.object(GooglePSESearchEngine, "_validate_connection"):
            with pytest.raises(ValueError, match="Search Engine ID"):
                GooglePSESearchEngine(api_key="k")


# ---------------------------------------------------------------------------
# _make_request — no last_exception branch
# ---------------------------------------------------------------------------


class TestMakeRequestNoLastException:
    """When all retries exhaust via generic Exception that has no rate-limit keyword,
    the 'else' branch of `if last_exception` should NOT be reached because
    last_exception is always set before the loop exits.  This test documents the
    actual behaviour: last_exception IS set, so the 'if last_exception' branch fires."""

    def test_last_exception_included_in_raise(self):
        engine = _make_engine(max_retries=1)
        with (
            patch(f"{MODULE}.safe_get", side_effect=Exception("oops")),
            patch("time.sleep"),
        ):
            with pytest.raises(RequestException, match="oops"):
                engine._make_request("q")


# ---------------------------------------------------------------------------
# _get_previews — exception in loop swallowed and breaks
# ---------------------------------------------------------------------------


class TestGetPreviewsExceptionInLoop:
    def test_exception_inside_loop_breaks_gracefully(self):
        """If _make_request raises inside the while loop, the exception is caught
        and the loop breaks, returning whatever was collected so far."""
        engine = _make_engine(max_results=5)
        first_page = {
            "items": [
                {"title": "X", "snippet": "s", "link": "https://x.com"},
            ]
        }
        engine._make_request = Mock(
            side_effect=[first_page, RuntimeError("network failure")]
        )
        previews = engine._get_previews("query")
        assert len(previews) == 1
        assert previews[0]["title"] == "X"

    def test_item_with_empty_link_is_skipped(self):
        """An item with link='' (empty string, falsy) must be skipped."""
        engine = _make_engine(max_results=2)
        page = {
            "items": [
                {"title": "A", "snippet": "a", "link": ""},  # empty → skip
                {"title": "B", "snippet": "b", "link": "https://b.com"},
                {"title": "C", "snippet": "c", "link": "https://c.com"},
            ]
        }
        engine._make_request = Mock(return_value=page)
        previews = engine._get_previews("query")
        assert len(previews) == 2
        titles = [p["title"] for p in previews]
        assert "A" not in titles
        assert "B" in titles
        assert "C" in titles

    def test_max_results_cap_mid_page(self):
        """max_results is enforced precisely even within a single page."""
        engine = _make_engine(max_results=2)
        page = {
            "items": [
                {"title": f"R{i}", "snippet": "s", "link": f"https://r{i}.com"}
                for i in range(5)
            ]
        }
        engine._make_request = Mock(return_value=page)
        previews = engine._get_previews("query")
        assert len(previews) == 2

    def test_source_field_is_google_programmable_search(self):
        """Each result must carry the correct source label."""
        engine = _make_engine(max_results=1)
        page = {
            "items": [{"title": "T", "snippet": "s", "link": "https://t.com"}]
        }
        engine._make_request = Mock(return_value=page)
        previews = engine._get_previews("query")
        assert previews[0]["source"] == "Google Programmable Search"


# ---------------------------------------------------------------------------
# _get_full_content delegates to super()
# ---------------------------------------------------------------------------


class TestGetFullContentDelegates:
    def test_delegates_to_base(self):
        engine = _make_engine()
        items = [{"title": "T", "link": "https://t.com", "snippet": "s"}]
        with patch(
            "local_deep_research.web_search_engines.search_engine_base.BaseSearchEngine._get_full_content",
            return_value=items,
        ) as mock_super:
            result = engine._get_full_content(items)
        mock_super.assert_called_once_with(items)
        assert result is items
