# -*- coding: utf-8 -*-
"""Location: ./tests/fuzz/test_jsonpath_fuzz.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Property-based fuzz testing for JSONPath processing.
"""

# Third-Party
from fastapi import HTTPException
from hypothesis import given
from hypothesis import strategies as st
import pytest

# First-Party
from mcpgateway.main import jsonpath_modifier


class TestJSONPathFuzzing:
    """Fuzz testing for JSONPath expression processing."""

    @given(st.text(min_size=1, max_size=200))
    def test_jsonpath_modifier_never_crashes(self, path_expression):
        """Test that arbitrary JSONPath expressions never crash the system."""
        test_data = {"a": 1, "b": [1, 2, 3], "c": {"d": "test"}}

        try:
            result = jsonpath_modifier(test_data, path_expression)
            # If it succeeds, result should be a list or dict
            assert isinstance(result, (list, dict))
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError, ZeroDivisionError):
            # These are acceptable exceptions for invalid paths
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=1, max_size=100))
    def test_jsonpath_with_dollar_expressions(self, expression):
        """Test JSONPath expressions containing $ operators."""
        # Only test if expression contains $, otherwise skip
        if "$" not in expression:
            return
        test_data = {"root": {"items": [{"id": 1}, {"id": 2}]}}

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=1, max_size=100))
    def test_jsonpath_with_brackets(self, expression):
        """Test JSONPath expressions with array notation."""
        # Only test if expression contains brackets, otherwise skip
        if "[" not in expression and "]" not in expression:
            return
        test_data = {"items": [{"a": 1}, {"a": 2}, {"a": 3}]}

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid bracket expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=1, max_size=100))
    def test_jsonpath_with_dots(self, expression):
        """Test JSONPath expressions with property access."""
        # Only test if expression contains dots, otherwise skip
        if "." not in expression:
            return
        test_data = {"a": {"b": {"c": "value"}}, "x": {"y": [1, 2, 3]}}

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid property paths
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(
        st.one_of(
            st.dictionaries(
                keys=st.text(min_size=1, max_size=20),
                values=st.recursive(
                    st.one_of(st.integers(), st.text(max_size=50), st.booleans(), st.none()), lambda children: st.lists(children) | st.dictionaries(st.text(max_size=10), children), max_leaves=10
                ),
                max_size=10,
            ),
            st.lists(st.dictionaries(keys=st.text(min_size=1, max_size=10), values=st.one_of(st.integers(), st.text(max_size=20)), max_size=5), max_size=5),
            st.integers(),
            st.text(max_size=100),
            st.booleans(),
            st.none(),
        )
    )
    def test_jsonpath_with_arbitrary_data(self, data):
        """Test JSONPath processing with arbitrary data structures."""
        expressions = ["$", "$.*", "$[*]", "$..*", "$..name", "$[0]"]

        for expr in expressions:
            try:
                result = jsonpath_modifier(data, expr)
                assert isinstance(result, (list, dict))
            except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
                # Expected for data/expression mismatches
                pass
            except Exception as e:
                pytest.fail(f"Unexpected exception with expr '{expr}' and data {type(data)}: {type(e).__name__}: {e}")

    @given(st.dictionaries(keys=st.text(min_size=1, max_size=20), values=st.text(min_size=1, max_size=50), min_size=1, max_size=5))
    def test_jsonpath_with_mappings(self, mappings):
        """Test JSONPath processing with arbitrary mappings."""
        test_data = {"items": [{"name": "test1", "value": 1}, {"name": "test2", "value": 2}]}
        base_expression = "$[*]"

        try:
            result = jsonpath_modifier(test_data, base_expression, mappings)
            assert isinstance(result, (list, dict))
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid mapping expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=0, max_size=200))
    def test_jsonpath_empty_and_whitespace_expressions(self, expression):
        """Test JSONPath with empty, whitespace, and unusual characters."""
        test_data = {"a": 1}

        try:
            result = jsonpath_modifier(test_data, expression)
            if not expression.strip():
                # Empty expressions should use default "$[*]"
                assert isinstance(result, (list, dict))
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=1, max_size=100))
    def test_jsonpath_with_special_characters(self, expression):
        """Test JSONPath with special characters that might break parsing."""
        # Only test if expression contains special characters, otherwise skip
        if not any(char in expression for char in '!@#%^&*()=+{}|;:",<>?/~`'):
            return
        test_data = {"field": "value", "array": [1, 2, 3]}

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for expressions with invalid special chars
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=100, max_size=1000))
    def test_jsonpath_very_long_expressions(self, expression):
        """Test JSONPath with very long expressions."""
        test_data = {"data": "test"}

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError, RecursionError):
            # Expected for very long/complex expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    def test_jsonpath_recursive_expressions(self):
        """Test deeply recursive JSONPath expressions."""
        test_data = {"a": {"b": {"c": {"d": {"e": "deep"}}}}}
        expressions = [
            "$.." + ".".join(["a"] * 50),  # Very deep property access
            "$" + "[0]" * 20,  # Deep array access
            "$..*" * 10,  # Repeated recursive descent
        ]

        for expr in expressions:
            try:
                jsonpath_modifier(test_data, expr)
            except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError, RecursionError):
                # Expected for complex recursive expressions
                pass
            except Exception as e:
                pytest.fail(f"Unexpected exception with expr '{expr}': {type(e).__name__}: {e}")

    @given(st.lists(st.text(min_size=1, max_size=20), min_size=1, max_size=10))
    def test_jsonpath_chained_expressions(self, parts):
        """Test chained JSONPath expressions."""
        test_data = {"root": {"items": [{"name": "test"}]}}

        # Create chained expression
        expression = "$" + "".join(f".{part}" for part in parts)

        try:
            jsonpath_modifier(test_data, expression)
        except (HTTPException, ValueError, TypeError, AttributeError, KeyError, IndexError):
            # Expected for invalid chained expressions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestJSONPathEdgeCases:
    """Edge case testing for JSONPath processing."""

    def test_jsonpath_null_data(self):
        """Test JSONPath with null data."""
        try:
            result = jsonpath_modifier(None, "$")
            assert isinstance(result, list)
        except (HTTPException, ValueError, TypeError):
            # Expected for null data
            pass

    def test_jsonpath_empty_data(self):
        """Test JSONPath with empty data structures."""
        test_cases = [{}, [], "", 0, False]

        for data in test_cases:
            try:
                result = jsonpath_modifier(data, "$")
                assert isinstance(result, (list, dict))
            except (HTTPException, ValueError, TypeError):
                # Expected for some empty data types
                pass

    def test_jsonpath_circular_data(self):
        """Test JSONPath with circular references (if supported)."""
        data = {"a": 1}
        data["self"] = data  # Create circular reference

        try:
            result = jsonpath_modifier(data, "$.a")
            assert isinstance(result, list)
        except (HTTPException, ValueError, TypeError, RecursionError):
            # Expected for circular data
            pass

    @given(st.integers(min_value=-1000, max_value=1000))
    def test_jsonpath_numeric_indices(self, index):
        """Test JSONPath with various numeric indices."""
        test_data = {"items": list(range(10))}
        expression = f"$.items[{index}]"

        try:
            result = jsonpath_modifier(test_data, expression)
            assert isinstance(result, list)
        except (HTTPException, ValueError, TypeError, IndexError):
            # Expected for out-of-bounds indices
            pass

    def test_jsonpath_unicode_expressions(self):
        """Test JSONPath with unicode characters."""
        test_data = {"ñamé": "tést", "数据": [1, 2, 3]}
        expressions = ["$.ñamé", "$.数据[*]", "$..tést"]

        for expr in expressions:
            try:
                result = jsonpath_modifier(test_data, expr)
                assert isinstance(result, (list, dict))
            except (HTTPException, ValueError, TypeError, UnicodeError):
                # Expected for unicode handling issues
                pass
