"""Test handling of direct array schemas in request bodies (FastAPI list parameters)."""

from fastmcp.experimental.utilities.openapi.models import (
    HTTPRoute,
    ParameterInfo,
    RequestBodyInfo,
)
from fastmcp.experimental.utilities.openapi.schemas import (
    _combine_schemas_and_map_params,
)


class TestDirectArraySchemas:
    """Test handling of direct array schemas like those generated by FastAPI for list[str] parameters."""

    def test_simple_direct_array_schema(self):
        """Test route with direct array request body schema."""
        route = HTTPRoute(
            path="/simple_list",
            method="POST",
            operation_id="simple_list_tool",
            summary="Simple List Tool",
            description="A simple tool that takes a list of strings.",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "items": {"type": "string"},
                        "type": "array",
                        "title": "Values",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should create a single parameter from the title
        assert combined_schema["type"] == "object"
        assert "values" in combined_schema["properties"]
        assert "values" in combined_schema["required"]

        # Check the array schema is preserved
        values_schema = combined_schema["properties"]["values"]
        assert values_schema["type"] == "array"
        assert values_schema["items"]["type"] == "string"
        assert values_schema["title"] == "Values"

        # Check parameter mapping
        assert "values" in param_map
        assert param_map["values"]["location"] == "body"
        assert param_map["values"]["openapi_name"] == "values"

    def test_int_array_schema(self):
        """Test route with integer array request body schema."""
        route = HTTPRoute(
            path="/int_list",
            method="POST",
            operation_id="int_list_tool",
            summary="Integer List Tool",
            description="A simple tool that takes a list of integers.",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "items": {"type": "integer"},
                        "type": "array",
                        "title": "Numbers",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should create a single parameter from the title
        assert combined_schema["type"] == "object"
        assert "numbers" in combined_schema["properties"]
        assert "numbers" in combined_schema["required"]

        # Check the array schema is preserved
        numbers_schema = combined_schema["properties"]["numbers"]
        assert numbers_schema["type"] == "array"
        assert numbers_schema["items"]["type"] == "integer"
        assert numbers_schema["title"] == "Numbers"

    def test_mixed_params_with_direct_array(self):
        """Test route with both URL parameters and direct array request body."""
        route = HTTPRoute(
            path="/mixed_params",
            method="POST",
            operation_id="mixed_params_tool",
            summary="Mixed Params Tool",
            description="A tool with both list and simple parameters.",
            parameters=[
                ParameterInfo(
                    name="prefix",
                    location="query",
                    required=True,
                    schema={"type": "string", "title": "Prefix"},
                )
            ],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "type": "array",
                        "items": {"type": "string"},
                        "title": "Values",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should have both parameters
        assert combined_schema["type"] == "object"
        assert "prefix" in combined_schema["properties"]  # From query param
        assert "values" in combined_schema["properties"]  # From body array
        assert "prefix" in combined_schema["required"]
        assert "values" in combined_schema["required"]

        # Check query parameter
        prefix_schema = combined_schema["properties"]["prefix"]
        assert prefix_schema["type"] == "string"

        # Check body array parameter
        values_schema = combined_schema["properties"]["values"]
        assert values_schema["type"] == "array"
        assert values_schema["items"]["type"] == "string"

        # Check parameter mappings
        assert param_map["prefix"]["location"] == "query"
        assert param_map["values"]["location"] == "body"

    def test_direct_array_no_title(self):
        """Test direct array schema without title."""
        route = HTTPRoute(
            path="/no_title",
            method="POST",
            operation_id="no_title_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "type": "array",
                        "items": {"type": "string"},
                        # No title
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should fall back to "body" (default title falls back)
        assert combined_schema["type"] == "object"
        assert "body" in combined_schema["properties"]
        assert "body" in combined_schema["required"]

        # Check parameter mapping
        assert param_map["body"]["location"] == "body"
        assert param_map["body"]["openapi_name"] == "body"

    def test_direct_primitive_schema(self):
        """Test direct primitive (non-array) request body schema."""
        route = HTTPRoute(
            path="/primitive",
            method="POST",
            operation_id="primitive_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {"type": "string", "title": "Message"}
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should create a single parameter from the title
        assert combined_schema["type"] == "object"
        assert "message" in combined_schema["properties"]
        assert "message" in combined_schema["required"]

        # Check the primitive schema is preserved
        message_schema = combined_schema["properties"]["message"]
        assert message_schema["type"] == "string"
        assert message_schema["title"] == "Message"

    def test_direct_array_optional(self):
        """Test direct array schema that's not required."""
        route = HTTPRoute(
            path="/optional_list",
            method="POST",
            operation_id="optional_list_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=False,  # Not required
                content_schema={
                    "application/json": {
                        "type": "array",
                        "items": {"type": "string"},
                        "title": "OptionalValues",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should create parameter but not mark as required
        assert combined_schema["type"] == "object"
        assert "optionalvalues" in combined_schema["properties"]
        assert "optionalvalues" not in combined_schema["required"]

    def test_direct_array_title_sanitization(self):
        """Test that titles with special characters are sanitized."""
        route = HTTPRoute(
            path="/special_chars",
            method="POST",
            operation_id="special_chars_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "type": "array",
                        "items": {"type": "string"},
                        "title": "Special-Chars & Spaces!",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should sanitize the title to a valid parameter name (& becomes multiple underscores)
        assert combined_schema["type"] == "object"
        assert "special_chars___spaces_" in combined_schema["properties"]

    def test_direct_array_title_starting_with_number(self):
        """Test that titles starting with numbers are handled."""
        route = HTTPRoute(
            path="/number_start",
            method="POST",
            operation_id="number_start_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "type": "array",
                        "items": {"type": "string"},
                        "title": "123Values",
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should fall back to "body_data" since "123values" starts with a number
        assert combined_schema["type"] == "object"
        assert "body_data" in combined_schema["properties"]

    def test_preserve_existing_behavior_object_body(self):
        """Test that existing object-based request bodies still work."""
        route = HTTPRoute(
            path="/object_body",
            method="POST",
            operation_id="object_body_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "age": {"type": "integer"},
                        },
                        "required": ["name"],
                    }
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should handle object bodies as before
        assert combined_schema["type"] == "object"
        assert "name" in combined_schema["properties"]
        assert "age" in combined_schema["properties"]
        assert "name" in combined_schema["required"]
        assert "age" not in combined_schema["required"]

    def test_preserve_existing_behavior_ref_body(self):
        """Test that $ref-based request bodies still work."""
        route = HTTPRoute(
            path="/ref_body",
            method="POST",
            operation_id="ref_body_tool",
            parameters=[],
            request_body=RequestBodyInfo(
                required=True,
                content_schema={
                    "application/json": {"$ref": "#/components/schemas/UserCreate"}
                },
            ),
        )

        combined_schema, param_map = _combine_schemas_and_map_params(route)

        # Should handle $ref bodies as before (refs get converted to $defs)
        assert combined_schema["type"] == "object"
        assert "body" in combined_schema["properties"]
        assert combined_schema["properties"]["body"]["$ref"] == "#/$defs/UserCreate"
