"""
Pure-logic tests for MCPSearchStrategy helper methods.

Tests _to_bool, _build_tool_schemas, _get_current_engine_name,
_format_tool_descriptions, _format_history, _parse_response fallback
branches, and _validate_tool_arguments — no LLM or search calls.
"""

from unittest.mock import Mock

from local_deep_research.advanced_search_system.strategies.mcp_strategy import (
    MCPSearchStrategy,
    _to_bool,
)


# ---------------------------------------------------------------------------
# _to_bool  (module-level helper)
# ---------------------------------------------------------------------------


class TestToBool:
    """Verify string-to-bool conversion handling."""

    def test_string_false(self):
        assert _to_bool("false") is False

    def test_string_FALSE_case_insensitive(self):
        assert _to_bool("FALSE") is False

    def test_string_zero(self):
        assert _to_bool("0") is False

    def test_string_no(self):
        assert _to_bool("no") is False

    def test_string_empty(self):
        assert _to_bool("") is False

    def test_string_whitespace_false(self):
        assert _to_bool("  false  ") is False

    def test_string_true(self):
        assert _to_bool("true") is True

    def test_string_yes(self):
        assert _to_bool("yes") is True

    def test_string_one(self):
        assert _to_bool("1") is True

    def test_string_arbitrary(self):
        assert _to_bool("anything") is True

    def test_bool_true(self):
        assert _to_bool(True) is True

    def test_bool_false(self):
        assert _to_bool(False) is False

    def test_int_zero(self):
        assert _to_bool(0) is False

    def test_int_nonzero(self):
        assert _to_bool(42) is True

    def test_none(self):
        assert _to_bool(None) is False


# ---------------------------------------------------------------------------
# _build_tool_schemas
# ---------------------------------------------------------------------------


def _make_strategy():
    """Build a minimal Mock for MCPSearchStrategy."""
    s = Mock(spec=[])
    s.OBSERVATION_MAX_LENGTH = 5000
    s.MAX_ARG_LENGTH = 10000
    s.HISTORY_OBSERVATION_MAX_LENGTH = 1000
    s.DIRECT_ANSWER_MIN_LENGTH = 300
    s.THOUGHT_PREVIEW_LENGTH = 200
    s.OBSERVATION_PREVIEW_LENGTH = 500
    s.RAW_RESPONSE_PREVIEW_LENGTH = 500
    s._history = []
    s._sources = []
    return s


class TestBuildToolSchemas:
    """Verify OpenAI-style schema generation from tool dicts."""

    def test_empty_tools(self):
        s = _make_strategy()
        result = MCPSearchStrategy._build_tool_schemas(s, [])
        assert result == []

    def test_single_tool_no_params(self):
        s = _make_strategy()
        tools = [
            {
                "name": "search",
                "description": "Search the web",
                "parameters": {},
            }
        ]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        assert len(result) == 1
        schema = result[0]
        assert schema["type"] == "function"
        assert schema["function"]["name"] == "search"
        assert schema["function"]["description"] == "Search the web"
        assert schema["function"]["parameters"]["properties"] == {}
        assert schema["function"]["parameters"]["required"] == []

    def test_tool_with_required_param(self):
        s = _make_strategy()
        tools = [
            {
                "name": "lookup",
                "description": "Look up a term",
                "parameters": {
                    "query": {
                        "type": "string",
                        "description": "search query",
                        "required": True,
                    }
                },
            }
        ]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        func = result[0]["function"]
        assert "query" in func["parameters"]["properties"]
        assert func["parameters"]["required"] == ["query"]

    def test_tool_with_optional_param(self):
        s = _make_strategy()
        tools = [
            {
                "name": "fetch",
                "description": "Fetch URL",
                "parameters": {
                    "url": {
                        "type": "string",
                        "description": "target URL",
                        "required": False,
                    }
                },
            }
        ]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        assert result[0]["function"]["parameters"]["required"] == []

    def test_multiple_tools(self):
        s = _make_strategy()
        tools = [
            {"name": "a", "description": "A", "parameters": {}},
            {"name": "b", "description": "B", "parameters": {}},
        ]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        assert len(result) == 2
        assert result[0]["function"]["name"] == "a"
        assert result[1]["function"]["name"] == "b"

    def test_param_type_defaults_to_string(self):
        s = _make_strategy()
        tools = [
            {
                "name": "t",
                "description": "T",
                "parameters": {
                    "x": {"description": "val"}  # no "type" key
                },
            }
        ]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        props = result[0]["function"]["parameters"]["properties"]
        assert props["x"]["type"] == "string"

    def test_missing_parameters_key(self):
        s = _make_strategy()
        tools = [{"name": "t", "description": "T"}]
        result = MCPSearchStrategy._build_tool_schemas(s, tools)
        assert result[0]["function"]["parameters"]["properties"] == {}


# ---------------------------------------------------------------------------
# _get_current_engine_name
# ---------------------------------------------------------------------------


class TestGetCurrentEngineName:
    """Verify engine name extraction from class name."""

    def test_typical_engine_name(self):
        s = _make_strategy()

        class ArXivSearchEngine:
            pass

        s.search = ArXivSearchEngine()
        result = MCPSearchStrategy._get_current_engine_name(s)
        assert result == "arxiv"

    def test_google_engine(self):
        s = _make_strategy()

        class GoogleSearchEngine:
            pass

        s.search = GoogleSearchEngine()
        assert MCPSearchStrategy._get_current_engine_name(s) == "google"

    def test_no_class_attr(self):
        s = _make_strategy()
        s.search = 42  # int has __class__ but no "SearchEngine" in name
        result = MCPSearchStrategy._get_current_engine_name(s)
        # Should return the lowered class name with SearchEngine removed
        assert isinstance(result, str)

    def test_plain_class(self):
        s = _make_strategy()

        class CustomEngine:
            pass

        s.search = CustomEngine()
        assert MCPSearchStrategy._get_current_engine_name(s) == "customengine"


# ---------------------------------------------------------------------------
# _validate_tool_arguments
# ---------------------------------------------------------------------------


class TestValidateToolArguments:
    """Verify argument sanitization logic."""

    def test_non_dict_returns_empty(self):
        s = _make_strategy()
        assert MCPSearchStrategy._validate_tool_arguments(s, "not a dict") == {}

    def test_none_returns_empty(self):
        s = _make_strategy()
        assert MCPSearchStrategy._validate_tool_arguments(s, None) == {}

    def test_passthrough_normal_args(self):
        s = _make_strategy()
        args = {"query": "test", "count": 5}
        result = MCPSearchStrategy._validate_tool_arguments(s, args)
        assert result == args

    def test_non_string_keys_filtered(self):
        s = _make_strategy()
        args = {"valid": "yes", 123: "no"}
        result = MCPSearchStrategy._validate_tool_arguments(s, args)
        assert "valid" in result
        assert 123 not in result

    def test_long_string_truncated(self):
        s = _make_strategy()
        long_val = "x" * (s.MAX_ARG_LENGTH + 100)
        args = {"text": long_val}
        result = MCPSearchStrategy._validate_tool_arguments(s, args)
        assert len(result["text"]) == s.MAX_ARG_LENGTH

    def test_short_string_not_truncated(self):
        s = _make_strategy()
        args = {"text": "short"}
        result = MCPSearchStrategy._validate_tool_arguments(s, args)
        assert result["text"] == "short"

    def test_non_string_values_preserved(self):
        s = _make_strategy()
        args = {"count": 42, "flag": True, "items": [1, 2]}
        result = MCPSearchStrategy._validate_tool_arguments(s, args)
        assert result == args
