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

Property-based fuzz testing for Pydantic schema validation.
"""

# Standard
import json

# Third-Party
from hypothesis import given
from hypothesis import strategies as st
from pydantic import ValidationError
import pytest

# First-Party
from mcpgateway.schemas import AuthenticationValues, GatewayCreate, PromptCreate, ResourceCreate, ToolCreate


class TestToolCreateSchemaFuzzing:
    """Fuzz testing for ToolCreate schema validation."""

    @given(
        st.dictionaries(
            keys=st.text(min_size=1, max_size=50),
            values=st.one_of(st.none(), st.booleans(), st.integers(), st.floats(), st.text(max_size=100), st.lists(st.text(max_size=20), max_size=5)),
            max_size=20,
        )
    )
    def test_tool_create_schema_robust(self, data):
        """Test ToolCreate schema with arbitrary data."""
        try:
            tool = ToolCreate(**data)
            # If validation succeeds, basic required fields should be present
            assert hasattr(tool, "name")
            if hasattr(tool, "url") and tool.url:
                assert isinstance(tool.url, (str, type(tool.url)))
        except (ValidationError, TypeError, ValueError):
            # Expected for invalid data
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=0, max_size=1000))
    def test_tool_create_name_field(self, name):
        """Test tool name field with various string inputs."""
        try:
            tool = ToolCreate(name=name, url="http://example.com")
            # If validation succeeds, name should not be empty after stripping
            assert len(tool.name.strip()) > 0
        except ValidationError:
            # Expected for invalid names (empty after stripping)
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.one_of(st.text(max_size=200), st.integers(), st.booleans(), st.none(), st.lists(st.text(max_size=20)), st.dictionaries(st.text(max_size=10), st.text(max_size=10))))
    def test_tool_create_url_field(self, url):
        """Test tool URL field with various data types."""
        try:
            tool = ToolCreate(name="test", url=url)
            # If validation succeeds, URL should be string or AnyHttpUrl
            assert isinstance(tool.url, (str, type(tool.url)))
        except ValidationError:
            # Expected for invalid URLs
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.one_of(st.sampled_from(["REST", "MCP"]), st.text(max_size=50), st.integers(), st.booleans(), st.none()))
    def test_tool_create_integration_type(self, integration_type):
        """Test integration_type field with various inputs."""
        try:
            tool = ToolCreate(name="test", url="http://example.com", integration_type=integration_type)
            # If validation succeeds, should be one of the allowed values
            assert tool.integration_type in ["REST", "MCP"]
        except ValidationError:
            # Expected for invalid integration types
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.one_of(st.sampled_from(["GET", "POST", "PUT", "DELETE", "PATCH", "SSE", "STDIO", "STREAMABLEHTTP"]), st.text(max_size=50), st.integers(), st.booleans()))
    def test_tool_create_request_type(self, request_type):
        """Test request_type field with various inputs."""
        try:
            tool = ToolCreate(name="test", url="http://example.com", request_type=request_type)
            # If validation succeeds, should be one of the allowed values
            assert tool.request_type in ["GET", "POST", "PUT", "DELETE", "PATCH", "SSE", "STDIO", "STREAMABLEHTTP"]
        except ValidationError:
            # Expected for invalid request types
            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.text(max_size=100), max_size=10), st.text(max_size=100), st.integers(), st.booleans(), st.none()))
    def test_tool_create_headers_field(self, headers):
        """Test headers field with various data types."""
        try:
            tool = ToolCreate(name="test", url="http://example.com", headers=headers)
            # If validation succeeds, headers should be dict or None
            assert tool.headers is None or isinstance(tool.headers, dict)
        except ValidationError:
            # Expected for invalid header types
            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.one_of(st.text(max_size=50), st.integers(), st.booleans()), max_size=10),
            st.text(max_size=100),
            st.integers(),
            st.booleans(),
            st.none(),
        )
    )
    def test_tool_create_input_schema_field(self, input_schema):
        """Test input_schema field with various structures."""
        try:
            tool = ToolCreate(name="test", url="http://example.com", input_schema=input_schema)
            # If validation succeeds, input_schema should be dict or None
            assert isinstance(tool.input_schema, (dict, type(None)))
        except ValidationError:
            # Expected for invalid schema types
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.lists(st.text(min_size=1, max_size=50), min_size=0, max_size=20))
    def test_tool_create_tags_field(self, tags):
        """Test tags field with various lists."""
        try:
            tool = ToolCreate(name="test", url="http://example.com", tags=tags)
            # If validation succeeds, tags should be list of dicts with id/label keys
            assert isinstance(tool.tags, list)
            assert all(isinstance(tag, dict) and "id" in tag and "label" in tag for tag in tool.tags)
        except ValidationError:
            # Expected for invalid tag structures
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestResourceCreateSchemaFuzzing:
    """Fuzz testing for ResourceCreate schema validation."""

    @given(st.dictionaries(keys=st.text(min_size=1, max_size=50), values=st.one_of(st.none(), st.booleans(), st.integers(), st.floats(), st.text(max_size=100)), max_size=15))
    def test_resource_create_schema_robust(self, data):
        """Test ResourceCreate schema with arbitrary data."""
        try:
            resource = ResourceCreate(**data)
            # If validation succeeds, basic fields should be present
            assert hasattr(resource, "uri")
            assert hasattr(resource, "name")
        except (ValidationError, TypeError, ValueError):
            # Expected for invalid data
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=0, max_size=500))
    def test_resource_create_uri_field(self, uri):
        """Test resource URI field with various inputs."""
        try:
            resource = ResourceCreate(uri=uri, name="test")
            # If validation succeeds, URI should be string
            assert isinstance(resource.uri, str)
        except ValidationError:
            # Expected for invalid URIs
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestPromptCreateSchemaFuzzing:
    """Fuzz testing for PromptCreate schema validation."""

    @given(
        st.dictionaries(
            keys=st.text(min_size=1, max_size=50), values=st.one_of(st.none(), st.booleans(), st.integers(), st.text(max_size=100), st.lists(st.text(max_size=20), max_size=5)), max_size=15
        )
    )
    def test_prompt_create_schema_robust(self, data):
        """Test PromptCreate schema with arbitrary data."""
        try:
            prompt = PromptCreate(**data)
            # If validation succeeds, basic fields should be present
            assert hasattr(prompt, "name")
        except (ValidationError, TypeError, ValueError):
            # Expected for invalid data
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestGatewayCreateSchemaFuzzing:
    """Fuzz testing for GatewayCreate schema validation."""

    @given(st.dictionaries(keys=st.text(min_size=1, max_size=50), values=st.one_of(st.none(), st.booleans(), st.integers(), st.text(max_size=100)), max_size=15))
    def test_gateway_create_schema_robust(self, data):
        """Test GatewayCreate schema with arbitrary data."""
        try:
            gateway = GatewayCreate(**data)
            # If validation succeeds, basic fields should be present
            assert hasattr(gateway, "name")
            assert hasattr(gateway, "url")
        except (ValidationError, TypeError, ValueError):
            # Expected for invalid data
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text(min_size=0, max_size=500))
    def test_gateway_create_url_field(self, url):
        """Test gateway URL field with various inputs."""
        try:
            gateway = GatewayCreate(name="test", url=url)
            # If validation succeeds, URL should be valid
            assert isinstance(gateway.url, (str, type(gateway.url)))
        except ValidationError:
            # Expected for invalid URLs
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestAuthenticationValuesSchemaFuzzing:
    """Fuzz testing for AuthenticationValues schema validation."""

    @given(
        st.dictionaries(keys=st.sampled_from(["username", "password", "token", "auth_type", "custom_header_name", "auth_header_value"]), values=st.one_of(st.text(max_size=100), st.none()), max_size=6)
    )
    def test_auth_values_schema_robust(self, data):
        """Test AuthenticationValues schema with arbitrary data."""
        try:
            auth = AuthenticationValues(**data)
            # If validation succeeds, should have auth_type
            assert hasattr(auth, "auth_type")
        except (ValidationError, TypeError, ValueError):
            # Expected for invalid data
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.one_of(st.sampled_from(["basic", "bearer", "custom"]), st.text(max_size=50), st.integers(), st.booleans(), st.none()))
    def test_auth_type_field(self, auth_type):
        """Test auth_type field with various inputs."""
        try:
            auth = AuthenticationValues(auth_type=auth_type)
            # If validation succeeds, auth_type can be any string, None
            assert isinstance(auth.auth_type, (str, type(None)))
        except ValidationError:
            # Expected for invalid auth types
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")


class TestComplexSchemaFuzzing:
    """Fuzz testing for complex schema interactions."""

    @given(
        st.dictionaries(
            keys=st.text(min_size=1, max_size=30),
            values=st.recursive(
                st.one_of(st.none(), st.booleans(), st.integers(min_value=-1000, max_value=1000), st.floats(allow_nan=False, allow_infinity=False), st.text(max_size=100)),
                lambda children: st.lists(children, max_size=5) | st.dictionaries(st.text(max_size=20), children, max_size=5),
                max_leaves=20,
            ),
            max_size=15,
        )
    )
    def test_nested_schema_structures(self, data):
        """Test schemas with deeply nested data structures."""
        schemas = [ToolCreate, ResourceCreate, PromptCreate, GatewayCreate]

        for schema_class in schemas:
            try:
                instance = schema_class(**data)
                # If validation succeeds, should be proper instance
                assert isinstance(instance, schema_class)
            except (ValidationError, TypeError, ValueError):
                # Expected for invalid nested structures
                pass
            except Exception as e:
                pytest.fail(f"Unexpected exception with {schema_class.__name__}: {type(e).__name__}: {e}")

    @given(st.text(min_size=0, max_size=10000))
    def test_very_large_text_fields(self, large_text):
        """Test schema validation with very large text inputs."""
        test_cases = [
            ("ToolCreate", {"name": large_text, "url": "http://example.com"}),
            ("ResourceCreate", {"uri": large_text, "name": "test"}),
            ("PromptCreate", {"name": large_text}),
            ("GatewayCreate", {"name": large_text, "url": "http://example.com"}),
        ]

        for schema_name, data in test_cases:
            schema_class = globals()[schema_name]
            try:
                instance = schema_class(**data)
                # If validation succeeds, text should be handled properly
                assert isinstance(instance, schema_class)
            except (ValidationError, TypeError, ValueError, MemoryError):
                # Expected for very large inputs
                pass
            except Exception as e:
                pytest.fail(f"Unexpected exception with {schema_name}: {type(e).__name__}: {e}")

    def test_schema_with_json_serialization(self):
        """Test schema validation after JSON round-trip."""
        test_data = {
            "name": "test_tool",
            "url": "http://example.com",
            "description": "Test tool",
            "integration_type": "REST",
            "request_type": "POST",
            "headers": {"Content-Type": "application/json"},
            "tags": ["test", "api"],
        }

        try:
            # Create instance
            tool = ToolCreate(**test_data)

            # Serialize to JSON and back
            json_str = tool.model_dump_json()
            parsed_data = json.loads(json_str)

            # Create new instance from parsed data
            tool2 = ToolCreate(**parsed_data)

            # Should be equivalent
            assert tool.name == tool2.name
            assert tool.url == tool2.url

        except (ValidationError, json.JSONDecodeError, TypeError, ValueError):
            # Expected for serialization issues
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception in JSON round-trip: {type(e).__name__}: {e}")

    @given(st.integers(min_value=-(2**31), max_value=2**31))
    def test_schema_with_extreme_integers(self, extreme_int):
        """Test schema validation with extreme integer values."""
        # Test with fields that might accept integers
        test_cases = [
            {"name": "test", "url": "http://example.com", "some_field": extreme_int},
            {"name": str(extreme_int), "url": "http://example.com"},
        ]

        for data in test_cases:
            try:
                tool = ToolCreate(**data)
                assert isinstance(tool, ToolCreate)
            except (ValidationError, TypeError, ValueError, OverflowError):
                # Expected for extreme values
                pass
            except Exception as e:
                pytest.fail(f"Unexpected exception with extreme int {extreme_int}: {type(e).__name__}: {e}")
