"""Tests for json_utils module."""

from unittest.mock import Mock


class TestGetLlmResponseText:
    """Tests for get_llm_response_text function."""

    def test_extracts_content_attribute(self):
        """Test that .content attribute is extracted from response."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = "Hello world"
        assert get_llm_response_text(response) == "Hello world"

    def test_falls_back_to_text_attribute(self):
        """Test that .text is used when .content is not available."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock(spec=[])
        response.text = "Hello text"
        assert get_llm_response_text(response) == "Hello text"

    def test_content_takes_precedence_over_text(self):
        """Test that .content is preferred over .text."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = "from content"
        response.text = "from text"
        assert get_llm_response_text(response) == "from content"

    def test_plain_string_passthrough(self):
        """Test that a plain string is returned via str()."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        assert get_llm_response_text("plain string") == "plain string"

    def test_none_input_returns_empty_string(self):
        """Test that None input returns empty string."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        assert get_llm_response_text(None) == ""

    def test_content_none_falls_through_to_text(self):
        """Test that content=None falls through to .text."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = None
        response.text = "from text"
        assert get_llm_response_text(response) == "from text"

    def test_non_string_content_converted(self):
        """Test that non-string .content is converted to string."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = 42
        assert get_llm_response_text(response) == "42"

    def test_removes_paired_think_tags(self):
        """Test that paired <think>...</think> tags are removed."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = "<think>reasoning</think>Result here"
        assert get_llm_response_text(response) == "Result here"

    def test_removes_orphaned_think_tags(self):
        """Test that orphaned opening or closing think tags are removed."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = "</think>Result<think>"
        assert get_llm_response_text(response) == "Result"

    def test_removes_multiline_think_tags(self):
        """Test that multiline think tag content is removed."""
        from local_deep_research.utilities.json_utils import (
            get_llm_response_text,
        )

        response = Mock()
        response.content = "<think>\nline1\nline2\n</think>\nActual content"
        assert get_llm_response_text(response) == "Actual content"


class TestExtractJson:
    """Tests for extract_json function."""

    def test_pure_json_dict(self):
        """Test parsing a pure JSON dict string."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json('{"key": "value"}')
        assert result == {"key": "value"}

    def test_pure_json_list(self):
        """Test parsing a pure JSON list string."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json("[1, 2, 3]")
        assert result == [1, 2, 3]

    def test_nested_structures(self):
        """Test parsing nested JSON structures."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '{"items": [{"id": 1}, {"id": 2}]}'
        result = extract_json(text)
        assert result == {"items": [{"id": 1}, {"id": 2}]}

    def test_code_fence_json(self):
        """Test extracting JSON from ```json code fences."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '```json\n{"key": "value"}\n```'
        result = extract_json(text)
        assert result == {"key": "value"}

    def test_bare_code_fence(self):
        """Test extracting JSON from bare ``` code fences."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "```\n[1, 2, 3]\n```"
        result = extract_json(text)
        assert result == [1, 2, 3]

    def test_code_fence_with_surrounding_prose(self):
        """Test extracting JSON from code fences with text around them."""
        from local_deep_research.utilities.json_utils import extract_json

        text = (
            'Here is the result:\n```json\n{"key": "value"}\n```\n'
            "Hope that helps!"
        )
        result = extract_json(text)
        assert result == {"key": "value"}

    def test_dict_embedded_in_prose(self):
        """Test extracting JSON dict embedded in prose text."""
        from local_deep_research.utilities.json_utils import extract_json

        text = 'The answer is {"key": "value"} as shown above.'
        result = extract_json(text)
        assert result == {"key": "value"}

    def test_list_embedded_in_prose(self):
        """Test extracting JSON list embedded in prose text."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "Here are the indices: [3, 0, 7, 1] sorted by relevance."
        result = extract_json(text, expected_type=list)
        assert result == [3, 0, 7, 1]

    def test_expected_type_list_prefers_brackets(self):
        """Test that expected_type=list tries [] before {}."""
        from local_deep_research.utilities.json_utils import extract_json

        text = 'Prefix {"a": 1} and also [1, 2, 3] suffix'
        result = extract_json(text, expected_type=list)
        assert result == [1, 2, 3]

    def test_expected_type_dict_prefers_braces(self):
        """Test that expected_type=dict tries {} before []."""
        from local_deep_research.utilities.json_utils import extract_json

        text = 'Prefix [1, 2, 3] and also {"a": 1} suffix'
        result = extract_json(text, expected_type=dict)
        assert result == {"a": 1}

    def test_type_mismatch_falls_through(self):
        """Test that type mismatch on direct parse falls to extraction."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '{"items": [1, 2, 3]}'
        result = extract_json(text, expected_type=list)
        assert result == [1, 2, 3]

    def test_type_mismatch_no_alternative_returns_none(self):
        """Test that type mismatch with no matching brackets returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "just plain text no json here"
        result = extract_json(text, expected_type=list)
        assert result is None

    def test_think_tags_cleaned_before_parse(self):
        """Test that think tags are removed before JSON parsing."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '<think>reasoning here</think>{"key": "value"}'
        result = extract_json(text)
        assert result == {"key": "value"}

    def test_none_input_returns_none(self):
        """Test that None input returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json(None) is None

    def test_empty_string_returns_none(self):
        """Test that empty string returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json("") is None

    def test_whitespace_only_returns_none(self):
        """Test that whitespace-only string returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json("   \n\t  ") is None

    def test_no_json_returns_none(self):
        """Test that text without JSON returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json("Hello, I have no JSON here.") is None

    def test_invalid_json_in_brackets_returns_none(self):
        """Test that invalid JSON inside brackets returns None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json("{not valid json at all}") is None

    def test_json_scalar_returns_none(self):
        """Test that JSON scalars (number, string) return None."""
        from local_deep_research.utilities.json_utils import extract_json

        assert extract_json("42") is None
        assert extract_json('"just a string"') is None

    def test_unicode_content_preserved(self):
        """Test that unicode content in JSON is preserved."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '{"name": "café", "city": "München"}'
        result = extract_json(text)
        assert result == {"name": "café", "city": "München"}

    def test_multiline_json(self):
        """Test parsing multiline JSON with whitespace."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '{\n  "key1": "value1",\n  "key2": "value2"\n}'
        result = extract_json(text)
        assert result == {"key1": "value1", "key2": "value2"}

    def test_expected_type_none_accepts_dict(self):
        """Test that expected_type=None accepts dict."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json('{"a": 1}', expected_type=None)
        assert result == {"a": 1}

    def test_expected_type_none_accepts_list(self):
        """Test that expected_type=None accepts list."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json("[1, 2]", expected_type=None)
        assert result == [1, 2]

    def test_nested_brackets_extracted_correctly(self):
        """Test that nested brackets use outermost match."""
        from local_deep_research.utilities.json_utils import extract_json

        text = 'prefix {"a": {"b": [1, 2]}} suffix'
        result = extract_json(text)
        assert result == {"a": {"b": [1, 2]}}

    def test_list_of_dicts(self):
        """Test parsing a list of dictionaries."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '[{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]'
        result = extract_json(text, expected_type=list)
        assert result == [
            {"id": 1, "name": "a"},
            {"id": 2, "name": "b"},
        ]


class TestExtractJsonLlmArtifacts:
    """Tests for LLM artifact cleaning in extract_json."""

    def test_trailing_comma_in_array(self):
        """Test that trailing commas in arrays are handled."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json("[1, 2, 3,]")
        assert result == [1, 2, 3]

    def test_trailing_comma_in_object(self):
        """Test that trailing commas in objects are handled."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json('{"a": 1, "b": 2,}')
        assert result == {"a": 1, "b": 2}

    def test_inline_comments(self):
        """Test that // comments are cleaned from JSON."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "[1, 2, // this is best\n3]"
        result = extract_json(text, expected_type=list)
        assert result == [1, 2, 3]

    def test_ellipsis_in_array(self):
        """Test that ellipsis entries are removed from arrays."""
        from local_deep_research.utilities.json_utils import extract_json

        result = extract_json("[1, 2, ...]")
        assert result == [1, 2]

    def test_quoted_ellipsis_in_array(self):
        """Test that quoted ellipsis in valid JSON is preserved (valid JSON)."""
        from local_deep_research.utilities.json_utils import extract_json

        # This is valid JSON, so direct parse succeeds without artifact cleaning
        result = extract_json('[1, 2, "..."]')
        assert result == [1, 2, "..."]

    def test_unquoted_ellipsis_in_malformed_json(self):
        """Test that ellipsis is cleaned from malformed JSON."""
        from local_deep_research.utilities.json_utils import extract_json

        # Trailing comma + ellipsis makes this invalid, triggers cleaning
        result = extract_json("[1, 2, ...,]")
        assert result == [1, 2]

    def test_combined_artifacts(self):
        """Test cleaning multiple artifact types at once."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "[1, 2, // comment\n3, ...,]"
        result = extract_json(text, expected_type=list)
        assert result == [1, 2, 3]

    def test_clean_json_not_modified(self):
        """Test that valid JSON is not affected by artifact cleaning."""
        from local_deep_research.utilities.json_utils import extract_json

        text = '{"url": "https://example.com/path", "count": 5}'
        result = extract_json(text)
        assert result == {
            "url": "https://example.com/path",
            "count": 5,
        }

    def test_trailing_comma_in_prose(self):
        """Test trailing comma cleanup with surrounding prose."""
        from local_deep_research.utilities.json_utils import extract_json

        text = "The result is: [1, 2, 3,] as expected"
        result = extract_json(text, expected_type=list)
        assert result == [1, 2, 3]


class TestStripCodeFences:
    """Tests for _strip_code_fences private helper."""

    def test_json_code_fence(self):
        """Test stripping ```json code fences."""
        from local_deep_research.utilities.json_utils import (
            _strip_code_fences,
        )

        text = '```json\n{"key": "value"}\n```'
        assert _strip_code_fences(text) == '{"key": "value"}'

    def test_bare_code_fence(self):
        """Test stripping bare ``` code fences."""
        from local_deep_research.utilities.json_utils import (
            _strip_code_fences,
        )

        text = "```\n[1, 2, 3]\n```"
        assert _strip_code_fences(text) == "[1, 2, 3]"

    def test_no_code_fence(self):
        """Test that text without code fences is returned unchanged."""
        from local_deep_research.utilities.json_utils import (
            _strip_code_fences,
        )

        text = '{"key": "value"}'
        assert _strip_code_fences(text) == '{"key": "value"}'

    def test_stray_backticks_not_treated_as_fence(self):
        """Test that stray backticks are not treated as code fences."""
        from local_deep_research.utilities.json_utils import (
            _strip_code_fences,
        )

        text = "Use ``` for code blocks"
        assert _strip_code_fences(text) == "Use ``` for code blocks"

    def test_json_fence_with_surrounding_text(self):
        """Test code fence extraction with text before and after."""
        from local_deep_research.utilities.json_utils import (
            _strip_code_fences,
        )

        text = 'Here is the JSON:\n```json\n{"a": 1}\n```\nDone!'
        assert _strip_code_fences(text) == '{"a": 1}'


class TestExtractByBrackets:
    """Tests for _extract_by_brackets private helper."""

    def test_extract_dict_brackets(self):
        """Test extracting content between { and }."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        text = 'prefix {"key": "value"} suffix'
        result = _extract_by_brackets(text, "{", "}")
        assert result == '{"key": "value"}'

    def test_extract_list_brackets(self):
        """Test extracting content between [ and ]."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        text = "prefix [1, 2, 3] suffix"
        result = _extract_by_brackets(text, "[", "]")
        assert result == "[1, 2, 3]"

    def test_missing_open_bracket_returns_none(self):
        """Test that missing open bracket returns None."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        result = _extract_by_brackets("no brackets here}", "{", "}")
        assert result is None

    def test_missing_close_bracket_returns_none(self):
        """Test that missing close bracket returns None."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        result = _extract_by_brackets("{no close bracket", "{", "}")
        assert result is None

    def test_close_before_open_returns_none(self):
        """Test that close bracket before open returns None."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        result = _extract_by_brackets("} before {", "{", "}")
        assert result is None

    def test_nested_brackets_uses_outermost(self):
        """Test that nested brackets extract outermost pair."""
        from local_deep_research.utilities.json_utils import (
            _extract_by_brackets,
        )

        text = '{"a": {"b": 1}}'
        result = _extract_by_brackets(text, "{", "}")
        assert result == '{"a": {"b": 1}}'
