"""
Tests for the MojeekSearchEngine class.

Tests cover:
- Initialization and configuration
- API key handling
- URL validation
- Search result parsing
- Preview generation
- Full content retrieval
- Error handling (including 403 rate limits)
"""

from unittest.mock import Mock, patch

import pytest


class TestMojeekSearchEngineInit:
    """Tests for MojeekSearchEngine initialization."""

    def test_init_with_api_key(self):
        """Initialize with API key."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key", include_full_content=False
        )

        assert engine.api_key == "test-api-key"
        assert engine.max_results == 10
        assert engine.include_full_content is False

    def test_init_with_default_parameters(self):
        """Initialize with default parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key", include_full_content=False
        )

        assert engine.max_results == 10
        assert engine.language == "en"
        assert engine.region == ""
        assert engine.safe_search is False

    def test_init_with_custom_parameters(self):
        """Initialize with custom parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key",
            max_results=20,
            language="fr",
            region="FR",
            safe_search=True,
            include_full_content=False,
        )

        assert engine.max_results == 20
        assert engine.language == "fr"
        assert engine.region == "FR"
        assert engine.safe_search is True

    def test_init_with_custom_region(self):
        """Initialize with custom region."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key",
            region="GB",
            include_full_content=False,
        )

        assert engine.region == "GB"

    def test_init_with_safe_search_enabled(self):
        """Initialize with safe_search enabled."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key",
            safe_search=True,
            include_full_content=False,
        )

        assert engine.safe_search is True

    def test_init_without_api_key_raises(self):
        """Initialize without API key raises ValueError."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        with patch(
            "local_deep_research.web_search_engines.search_engine_base.get_setting_from_snapshot",
            return_value=None,
        ):
            with pytest.raises(
                ValueError, match="No valid API key found for Mojeek"
            ):
                MojeekSearchEngine(include_full_content=False)

    def test_init_with_api_key_from_settings(self):
        """Initialize with API key from settings snapshot."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        with patch(
            "local_deep_research.web_search_engines.search_engine_base.get_setting_from_snapshot",
            return_value="settings-api-key",
        ):
            engine = MojeekSearchEngine(
                include_full_content=False,
                settings_snapshot={
                    "search.engine.web.mojeek.api_key": "settings-api-key"
                },
            )

            assert engine.api_key == "settings-api-key"

    def test_init_with_include_full_content_false(self):
        """Initialize with include_full_content=False skips FullSearchResults."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key", include_full_content=False
        )

        assert engine.include_full_content is False
        assert not hasattr(engine, "full_search")


class TestURLValidation:
    """Tests for URL validation logic."""

    @pytest.mark.parametrize(
        "url,expected",
        [
            ("https://example.com", True),
            ("http://example.com", True),
            ("example.com", False),
            ("/relative/path", False),
            ("//example.com", False),
            ("ftp://example.com", False),
            ("", False),
        ],
    )
    def test_is_valid_search_result(self, url, expected):
        """Validate URL parsing logic."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-api-key", include_full_content=False
        )
        assert engine._is_valid_search_result(url) == expected


class TestGetSearchResults:
    """Tests for _get_search_results method."""

    def test_successful_api_response(self):
        """Test successful API response with correct nesting."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {
                "status": "OK",
                "results": [
                    {
                        "title": "Test Title",
                        "url": "https://example.com",
                        "desc": "Test description",
                        "cats": "Technology",
                    }
                ],
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-api-key", include_full_content=False
            )
            results = engine._get_search_results("test query")

        assert len(results) == 1
        assert results[0]["title"] == "Test Title"
        assert results[0]["url"] == "https://example.com"
        assert results[0]["content"] == "Test description"
        assert results[0]["engine"] == "mojeek"
        assert results[0]["category"] == "Technology"

    def test_whitespace_padded_url_survives_validity_gate(self):
        """Regression: a URL with surrounding whitespace must NOT be
        silently dropped by _is_valid_search_result().

        The url is gated on a http(s):// *prefix* check that runs before the
        SSRF validator's internal strip, so without the extraction-time strip
        a padded url fails ``startswith("https://")`` and the whole result is
        dropped (len would be 0). See BaseSearchEngine._clean_result_url.
        """
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {
                "status": "OK",
                "results": [
                    {
                        "title": "Padded",
                        "url": "  https://example.com/padded  ",
                        "desc": "d",
                        "cats": "c",
                    }
                ],
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-api-key", include_full_content=False
            )
            results = engine._get_search_results("test query")

        assert len(results) == 1  # would be 0 before the strip fix
        assert results[0]["url"] == "https://example.com/padded"

    def test_empty_results(self):
        """Test empty results array."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "OK", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-api-key", include_full_content=False
            )
            results = engine._get_search_results("test query")

        assert len(results) == 0

    def test_non_200_response(self):
        """Test non-200 API response returns empty list."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.text = "Internal Server Error"

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-api-key", include_full_content=False
            )
            results = engine._get_search_results("test query")

        assert len(results) == 0

    def test_non_ok_status(self):
        """Test 200 response with non-OK status returns empty."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "ERROR", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-api-key", include_full_content=False
            )
            results = engine._get_search_results("test query")

        assert len(results) == 0

    def test_api_key_passed_in_params(self):
        """Test that api_key is included in request params."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "OK", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = MojeekSearchEngine(
                api_key="my-secret-key", include_full_content=False
            )
            engine._get_search_results("test")

            call_kwargs = mock_get.call_args
            assert call_kwargs[1]["params"]["api_key"] == "my-secret-key"

    def test_region_boost_params(self):
        """Test that region boost params are set when region is provided."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "OK", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = MojeekSearchEngine(
                api_key="test-key",
                region="GB",
                include_full_content=False,
            )
            engine._get_search_results("test")

            params = mock_get.call_args[1]["params"]
            assert params["rb"] == "GB"
            assert params["rbb"] == 10

    def test_no_region_params(self):
        """Test that region params are absent when region is empty."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "OK", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = MojeekSearchEngine(
                api_key="test-key",
                region="",
                include_full_content=False,
            )
            engine._get_search_results("test")

            params = mock_get.call_args[1]["params"]
            assert "rb" not in params
            assert "rbb" not in params

    def test_safe_search_param(self):
        """Test safe_search is passed as integer 1."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "response": {"status": "OK", "results": []}
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = MojeekSearchEngine(
                api_key="test-key",
                safe_search=True,
                include_full_content=False,
            )
            engine._get_search_results("test")

            params = mock_get.call_args[1]["params"]
            assert params["safe"] == 1

    def test_general_error_returns_empty(self):
        """Test that exceptions during request return empty list."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            side_effect=Exception("Connection error"),
        ):
            engine = MojeekSearchEngine(
                api_key="test-key", include_full_content=False
            )
            results = engine._get_search_results("test")

        assert results == []

    def test_rate_limit_403_raises(self):
        """Test that 403 response raises RateLimitError."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        mock_response = Mock()
        mock_response.status_code = 403

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_mojeek.safe_get",
            return_value=mock_response,
        ):
            engine = MojeekSearchEngine(
                api_key="test-key", include_full_content=False
            )

            with pytest.raises(RateLimitError):
                engine._get_search_results("test")


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

    def test_returns_formatted_results(self):
        """Get previews returns formatted results."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-key", include_full_content=False
        )

        mock_results = [
            {
                "title": "Test Title",
                "url": "https://example.com",
                "content": "Test snippet",
                "engine": "mojeek",
                "category": "Tech",
            }
        ]

        with patch.object(
            engine, "_get_search_results", return_value=mock_results
        ):
            previews = engine._get_previews("test query")

        assert len(previews) == 1
        assert previews[0]["id"] == "https://example.com"
        assert previews[0]["title"] == "Test Title"
        assert previews[0]["link"] == "https://example.com"
        assert previews[0]["snippet"] == "Test snippet"
        assert previews[0]["engine"] == "mojeek"
        assert previews[0]["category"] == "Tech"

    def test_empty_results(self):
        """Get previews handles empty results."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-key", include_full_content=False
        )

        with patch.object(engine, "_get_search_results", return_value=[]):
            previews = engine._get_previews("test query")

        assert previews == []

    def test_preserves_metadata(self):
        """Get previews preserves engine and category metadata."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-key", include_full_content=False
        )

        mock_results = [
            {
                "title": "Result",
                "url": "https://example.com",
                "content": "Snippet",
                "engine": "mojeek",
                "category": "Science",
            }
        ]

        with patch.object(
            engine, "_get_search_results", return_value=mock_results
        ):
            previews = engine._get_previews("test")

        assert previews[0]["engine"] == "mojeek"
        assert previews[0]["category"] == "Science"


class TestGetFullContent:
    """Tests for _get_full_content method."""

    def test_with_full_search_attr(self):
        """Test delegation to full_search._get_full_content."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-key", include_full_content=False
        )
        engine.include_full_content = True
        engine.full_search = Mock()
        engine.full_search._get_full_content.return_value = [
            {"link": "https://example.com", "content": "Full content"}
        ]

        items = [{"link": "https://example.com"}]
        results = engine._get_full_content(items)

        assert len(results) == 1
        assert results[0]["content"] == "Full content"
        engine.full_search._get_full_content.assert_called_once_with(items)

    def test_without_full_search_attr(self):
        """Test returns items as-is when no full_search."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        engine = MojeekSearchEngine(
            api_key="test-key", include_full_content=False
        )

        items = [{"link": "https://example.com", "snippet": "test"}]
        results = engine._get_full_content(items)

        assert results == items


class TestClassAttributes:
    """Tests for class attributes."""

    def test_is_public(self):
        """MojeekSearchEngine is marked as public."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        assert MojeekSearchEngine.is_public is True

    def test_is_generic(self):
        """MojeekSearchEngine is marked as generic."""
        from local_deep_research.web_search_engines.engines.search_engine_mojeek import (
            MojeekSearchEngine,
        )

        assert MojeekSearchEngine.is_generic is True
