"""
Edge case and error handling tests for strategies.

Tests unusual inputs, error conditions, and boundary cases.
"""

import pytest
from loguru import logger
from unittest.mock import Mock


# Working strategies for testing
WORKING_STRATEGIES = [
    "source-based",
    "rapid",
    "parallel",
    "iterdrag",
    "news",
    "focused-iteration",
]


class TestEmptyInputs:
    """Test strategy behavior with empty inputs."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_empty_query(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test strategy with empty query string."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        # Empty query
        result = strategy.analyze_topic("")

        assert isinstance(result, dict)
        logger.info(f"{strategy_name} with empty query: {list(result.keys())}")

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_whitespace_only_query(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test strategy with whitespace-only query."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        result = strategy.analyze_topic("   \t\n   ")

        assert isinstance(result, dict)


class TestLongInputs:
    """Test strategy behavior with very long inputs."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_very_long_query(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test strategy with very long query."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        # Very long query (1000+ characters)
        long_query = "What is " + "very " * 200 + "important?"

        result = strategy.analyze_topic(long_query)

        assert isinstance(result, dict)
        logger.info(f"{strategy_name} handled {len(long_query)} char query")


class TestSpecialCharacters:
    """Test strategy behavior with special characters in input."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_unicode_query(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test strategy with unicode characters."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        unicode_query = "What is 日本語 and 中文 and العربية?"

        result = strategy.analyze_topic(unicode_query)

        assert isinstance(result, dict)

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_query_with_special_chars(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test strategy with special characters."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        special_query = "What about <script>alert('xss')</script> and $PATH?"

        result = strategy.analyze_topic(special_query)

        assert isinstance(result, dict)


class TestLLMErrors:
    """Test strategy behavior when LLM returns errors or unusual responses."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_llm_returns_empty_response(
        self,
        strategy_name,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test when LLM returns empty response."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        mock_llm = Mock()
        mock_llm.invoke = Mock(return_value=Mock(content=""))

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        try:
            result = strategy.analyze_topic("Test query")
            assert isinstance(result, dict)
        except Exception as e:
            logger.warning(
                f"{strategy_name} failed with empty LLM response: {e}"
            )

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_llm_returns_none(
        self,
        strategy_name,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test when LLM returns None."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        mock_llm = Mock()
        mock_llm.invoke = Mock(return_value=None)

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        try:
            result = strategy.analyze_topic("Test query")
            # May succeed or fail - we're testing it doesn't crash badly
            if result:
                assert isinstance(result, dict)
        except AttributeError:
            # Expected when trying to access .content on None
            pass
        except Exception as e:
            logger.warning(
                f"{strategy_name} with None LLM response: {type(e).__name__}"
            )

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:2])
    def test_llm_raises_exception(
        self,
        strategy_name,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test when LLM raises an exception."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        mock_llm = Mock()
        mock_llm.invoke = Mock(side_effect=Exception("LLM API Error"))

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        # Strategy should handle or propagate the exception
        try:
            result = strategy.analyze_topic("Test query")
            # If it succeeds, should return dict with error
            if isinstance(result, dict) and "error" in result:
                logger.info(
                    f"{strategy_name} returned error: {result['error']}"
                )
        except Exception as e:
            logger.info(
                f"{strategy_name} propagated exception: {type(e).__name__}"
            )


class TestSearchErrors:
    """Test strategy behavior when search returns errors."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_search_returns_empty_list(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_settings_snapshot,
    ):
        """Test when search returns empty list."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        empty_search = Mock()
        empty_search.run = Mock(return_value=[])
        empty_search.include_full_content = True

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=empty_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        result = strategy.analyze_topic("Test query")

        assert isinstance(result, dict)
        logger.info(f"{strategy_name} with empty search: {list(result.keys())}")

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_search_returns_none(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_settings_snapshot,
    ):
        """Test when search returns None."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        none_search = Mock()
        none_search.run = Mock(return_value=None)
        none_search.include_full_content = True

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=none_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        try:
            result = strategy.analyze_topic("Test query")
            assert isinstance(result, dict)
        except TypeError:
            # May fail when trying to iterate None
            pass
        except Exception as e:
            logger.warning(
                f"{strategy_name} with None search: {type(e).__name__}"
            )

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_search_raises_exception(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_settings_snapshot,
    ):
        """Test when search raises exception."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        error_search = Mock()
        error_search.run = Mock(side_effect=Exception("Search API Error"))
        error_search.include_full_content = True

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=error_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        try:
            result = strategy.analyze_topic("Test query")
            # If handled, should return dict
            assert isinstance(result, dict)
        except Exception as e:
            logger.info(
                f"{strategy_name} propagated search error: {type(e).__name__}"
            )


class TestMalformedSearchResults:
    """Test strategy behavior with malformed search results."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_search_missing_fields(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_settings_snapshot,
    ):
        """Test with search results missing expected fields."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        malformed_search = Mock()
        malformed_search.run = Mock(
            return_value=[
                {},  # Empty dict
                {"title": "Only title"},  # Missing link, snippet
                {"link": "https://example.com"},  # Missing title, snippet
            ]
        )
        malformed_search.include_full_content = True

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=malformed_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        result = strategy.analyze_topic("Test query")

        assert isinstance(result, dict)

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_search_with_none_values(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_settings_snapshot,
    ):
        """Test with search results containing None values."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        none_values_search = Mock()
        none_values_search.run = Mock(
            return_value=[
                {"title": None, "link": None, "snippet": None},
                {
                    "title": "Valid",
                    "link": "https://example.com",
                    "snippet": "Content",
                },
            ]
        )
        none_values_search.include_full_content = True

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=none_values_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        result = strategy.analyze_topic("Test query")

        assert isinstance(result, dict)


class TestProgressCallback:
    """Test progress callback behavior."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_callback_receives_valid_progress(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test that callback receives valid progress values."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        progress_values = []

        def track_progress(message, progress, data):
            progress_values.append(progress)

        strategy.set_progress_callback(track_progress)
        strategy.analyze_topic("Test query")

        # Progress values should be non-negative
        for p in progress_values:
            assert p >= 0, f"Negative progress value: {p}"

        # Document any values exceeding 100 as warnings for later analysis
        over_100 = [p for p in progress_values if p > 100]
        if over_100:
            logger.warning(
                f"{strategy_name} reported progress > 100: {over_100}. "
                "This is a strategy bug that should be investigated."
            )

        logger.info(f"{strategy_name} progress values: {progress_values}")

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_callback_exception_doesnt_crash_strategy(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test that exception in callback doesn't crash strategy."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        def bad_callback(message, progress, data):
            raise Exception("Callback error!")

        strategy.set_progress_callback(bad_callback)

        # Should not crash (strategy may catch callback exceptions)
        try:
            result = strategy.analyze_topic("Test query")
            assert isinstance(result, dict)
        except Exception as e:
            logger.warning(
                f"{strategy_name} callback exception propagated: {type(e).__name__}"
            )


class TestMultipleAnalyzeCalls:
    """Test calling analyze_topic multiple times on same strategy."""

    @pytest.mark.parametrize("strategy_name", WORKING_STRATEGIES[:3])
    def test_multiple_calls_same_strategy(
        self,
        strategy_name,
        strategy_mock_llm,
        strategy_mock_search,
        strategy_settings_snapshot,
    ):
        """Test multiple analyze_topic calls on same strategy instance."""
        from local_deep_research.search_system_factory import (
            create_strategy,
        )

        strategy = create_strategy(
            strategy_name=strategy_name,
            model=strategy_mock_llm,
            search=strategy_mock_search,
            settings_snapshot=strategy_settings_snapshot,
        )

        # First call
        result1 = strategy.analyze_topic("Query 1")
        links_after_1 = len(strategy.all_links_of_system)

        # Second call
        result2 = strategy.analyze_topic("Query 2")
        links_after_2 = len(strategy.all_links_of_system)

        # Third call
        result3 = strategy.analyze_topic("Query 3")
        links_after_3 = len(strategy.all_links_of_system)

        assert isinstance(result1, dict)
        assert isinstance(result2, dict)
        assert isinstance(result3, dict)

        logger.info(
            f"{strategy_name} links: {links_after_1} -> {links_after_2} -> {links_after_3}"
        )
