"""Tests for SimpleJSONLoader."""

from pathlib import Path

import pytest

from local_deep_research.document_loaders.json_loader import (
    SimpleJSONLoader,
    extract_strings_from_json,
    extract_text_from_json,
)


class TestExtractStringsFromJson:
    """Tests for extract_strings_from_json helper function."""

    def test_extract_simple_string(self) -> None:
        """Test extracting a simple string value."""
        data = "hello"
        result = extract_strings_from_json(data)

        assert result == [("", "hello")]

    def test_extract_from_dict(self) -> None:
        """Test extracting strings from a dictionary."""
        data = {"name": "Alice", "city": "Boston"}
        result = extract_strings_from_json(data)

        assert ("name", "Alice") in result
        assert ("city", "Boston") in result

    def test_extract_from_nested_dict(self) -> None:
        """Test extracting strings from nested dictionary."""
        data = {"user": {"name": "Bob", "email": "bob@example.com"}}
        result = extract_strings_from_json(data)

        assert ("user.name", "Bob") in result
        assert ("user.email", "bob@example.com") in result

    def test_extract_from_list(self) -> None:
        """Test extracting strings from a list."""
        data = ["apple", "banana", "cherry"]
        result = extract_strings_from_json(data)

        assert ("[0]", "apple") in result
        assert ("[1]", "banana") in result
        assert ("[2]", "cherry") in result

    def test_extract_from_nested_list(self) -> None:
        """Test extracting strings from nested list in dict."""
        data = {"fruits": ["apple", "banana"]}
        result = extract_strings_from_json(data)

        assert ("fruits[0]", "apple") in result
        assert ("fruits[1]", "banana") in result

    def test_skip_numbers_and_booleans(self) -> None:
        """Test that numbers and booleans are skipped."""
        data = {"count": 42, "active": True, "name": "test"}
        result = extract_strings_from_json(data)

        # Only name should be extracted
        assert len(result) == 1
        assert ("name", "test") in result

    def test_skip_none(self) -> None:
        """Test that None values are skipped."""
        data = {"value": None, "name": "test"}
        result = extract_strings_from_json(data)

        assert len(result) == 1
        assert ("name", "test") in result

    def test_empty_dict(self) -> None:
        """Test with empty dictionary."""
        data = {}
        result = extract_strings_from_json(data)

        assert result == []

    def test_empty_list(self) -> None:
        """Test with empty list."""
        data = []
        result = extract_strings_from_json(data)

        assert result == []

    def test_deeply_nested(self) -> None:
        """Test deeply nested structure."""
        data = {"a": {"b": {"c": {"d": "deep"}}}}
        result = extract_strings_from_json(data)

        assert ("a.b.c.d", "deep") in result


class TestSimpleJSONLoader:
    """Tests for SimpleJSONLoader class."""

    def test_load_simple_json(self, tmp_path: Path) -> None:
        """Test loading a simple JSON file."""
        json_file = tmp_path / "test.json"
        json_file.write_text('{"name": "test", "value": "hello"}')

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        assert "name: test" in docs[0].page_content
        assert "value: hello" in docs[0].page_content
        assert docs[0].metadata["file_type"] == "json"

    def test_load_nested_json(self, tmp_path: Path) -> None:
        """Test loading nested JSON structure."""
        json_content = (
            '{"user": {"name": "Alice", "email": "alice@example.com"}}'
        )
        json_file = tmp_path / "nested.json"
        json_file.write_text(json_content)

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        assert "user.name: Alice" in docs[0].page_content
        assert "user.email: alice@example.com" in docs[0].page_content

    def test_load_json_array(self, tmp_path: Path) -> None:
        """Test loading JSON with array."""
        json_content = '{"items": ["one", "two", "three"]}'
        json_file = tmp_path / "array.json"
        json_file.write_text(json_content)

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        assert "items[0]: one" in docs[0].page_content
        assert "items[1]: two" in docs[0].page_content

    def test_load_invalid_json_returns_raw_content(
        self, tmp_path: Path
    ) -> None:
        """Test that invalid JSON returns raw content with parse_error flag."""
        json_file = tmp_path / "invalid.json"
        json_file.write_text('{"invalid": json content')

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        assert docs[0].metadata.get("parse_error") is True
        assert '{"invalid":' in docs[0].page_content

    def test_load_without_paths(self, tmp_path: Path) -> None:
        """Test loading with include_paths=False."""
        json_file = tmp_path / "test.json"
        json_file.write_text('{"name": "test", "city": "Boston"}')

        loader = SimpleJSONLoader(json_file, include_paths=False)
        docs = loader.load()

        assert len(docs) == 1
        # Should contain values but not paths
        assert "test" in docs[0].page_content
        assert "Boston" in docs[0].page_content
        # Should NOT contain "name:" prefix
        assert "name:" not in docs[0].page_content

    def test_load_json_with_unicode(self, tmp_path: Path) -> None:
        """Test loading JSON with unicode characters."""
        json_content = '{"greeting": "こんにちは", "emoji": "🎉"}'
        json_file = tmp_path / "unicode.json"
        json_file.write_text(json_content, encoding="utf-8")

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        assert "こんにちは" in docs[0].page_content
        assert "🎉" in docs[0].page_content

    def test_string_count_metadata(self, tmp_path: Path) -> None:
        """Test that string_count metadata is set correctly."""
        json_file = tmp_path / "test.json"
        json_file.write_text('{"a": "one", "b": "two", "c": "three"}')

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert docs[0].metadata["string_count"] == 3

    def test_lazy_load(self, tmp_path: Path) -> None:
        """Test lazy_load returns an iterator."""
        json_file = tmp_path / "test.json"
        json_file.write_text('{"key": "value"}')

        loader = SimpleJSONLoader(json_file)
        result = loader.lazy_load()

        assert hasattr(result, "__iter__")
        assert hasattr(result, "__next__")

        docs = list(result)
        assert len(docs) == 1

    def test_file_not_found_raises_error(self) -> None:
        """Test that missing file raises appropriate error."""
        loader = SimpleJSONLoader("/nonexistent/path/file.json")

        with pytest.raises(Exception):
            loader.load()

    def test_load_json_with_duplicate_keys(self, tmp_path: Path) -> None:
        """JSON with duplicate keys - last value wins per RFC 8259."""
        json_file = tmp_path / "duplicates.json"
        json_file.write_text('{"key": "first", "key": "second"}')

        loader = SimpleJSONLoader(json_file)
        docs = loader.load()

        assert len(docs) == 1
        # Python's json.loads keeps last value for duplicate keys
        assert "second" in docs[0].page_content


class TestExtractTextFromJson:
    """Tests for extract_text_from_json function."""

    def test_extract_simple_json(self) -> None:
        """Test extracting text from simple JSON bytes."""
        content = b'{"name": "test", "value": "hello"}'
        result = extract_text_from_json(content)

        assert "name: test" in result
        assert "value: hello" in result

    def test_extract_nested_json(self) -> None:
        """Test extracting text from nested JSON bytes."""
        content = b'{"user": {"name": "Alice"}}'
        result = extract_text_from_json(content)

        assert "user.name: Alice" in result

    def test_extract_invalid_json_returns_raw(self) -> None:
        """Test that invalid JSON returns raw content."""
        content = b'{"invalid": json'
        result = extract_text_from_json(content)

        # Should return the raw content
        assert '{"invalid":' in result

    def test_extract_with_encoding_errors(self) -> None:
        """Test handling of encoding errors."""
        # Invalid UTF-8 bytes mixed with valid JSON
        content = b'{"name": "test\xff\xfe"}'
        result = extract_text_from_json(content)

        # Should still return something
        assert "name" in result

    def test_extract_empty_object(self) -> None:
        """Test extracting from empty JSON object."""
        content = b"{}"
        result = extract_text_from_json(content)

        assert result == ""

    def test_extract_filters_empty_strings(self) -> None:
        """Test that empty strings are filtered out."""
        content = b'{"empty": "", "valid": "value"}'
        result = extract_text_from_json(content)

        assert "valid: value" in result
        # Empty string should not create a line
        lines = [line for line in result.split("\n") if line.strip()]
        assert len(lines) == 1
