"""Unit tests for OpenAPI models."""

import pytest
from inline_snapshot import snapshot

from fastmcp.utilities.openapi.models import (
    HttpMethod,
    HTTPRoute,
    ParameterInfo,
    ParameterLocation,
    RequestBodyInfo,
    ResponseInfo,
)


class TestParameterInfo:
    """Test ParameterInfo model."""

    def test_basic_parameter_creation(self):
        """Test creating a basic parameter."""
        param = ParameterInfo(
            name="id",
            location="path",
            required=True,
            schema={"type": "integer"},
        )

        assert param.name == "id"
        assert param.location == "path"
        assert param.required is True
        assert param.schema_ == {"type": "integer"}
        assert param.description is None
        assert param.explode is None
        assert param.style is None

    def test_parameter_with_all_fields(self):
        """Test creating parameter with all optional fields."""
        param = ParameterInfo(
            name="filter",
            location="query",
            required=False,
            schema={"type": "object", "properties": {"name": {"type": "string"}}},
            description="Filter criteria",
            explode=True,
            style="deepObject",
        )

        assert param.name == "filter"
        assert param.location == "query"
        assert param.required is False
        assert param.description == "Filter criteria"
        assert param.explode is True
        assert param.style == "deepObject"

    @pytest.mark.parametrize("location", ["path", "query", "header", "cookie"])
    def test_valid_parameter_locations(self, location: ParameterLocation):
        """Test that all valid parameter locations are accepted."""
        param = ParameterInfo(
            name="test",
            location=location,
            required=False,
            schema={"type": "string"},
        )
        assert param.location == location

    def test_parameter_defaults(self):
        """Test parameter default values."""
        param = ParameterInfo(
            name="test",
            location="query",
            schema={"type": "string"},
        )

        # required should default to False for non-path parameters
        assert param.required is False
        assert param.description is None
        assert param.explode is None
        assert param.style is None

    def test_parameter_with_empty_schema(self):
        """Test parameter with empty schema."""
        param = ParameterInfo(
            name="test",
            location="query",
            schema={},
        )

        assert param.schema_ == {}


class TestRequestBodyInfo:
    """Test RequestBodyInfo model."""

    def test_basic_request_body(self):
        """Test creating a basic request body."""
        request_body = RequestBodyInfo(
            required=True,
            description="User data",
        )

        assert request_body.required is True
        assert request_body.description == "User data"
        assert request_body.content_schema == {}

    def test_request_body_with_content_schema(self):
        """Test request body with content schema."""
        content_schema = {
            "application/json": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                },
                "required": ["name"],
            }
        }

        request_body = RequestBodyInfo(
            required=True,
            content_schema=content_schema,
        )

        assert request_body.content_schema == content_schema

    def test_request_body_defaults(self):
        """Test request body default values."""
        request_body = RequestBodyInfo()

        assert request_body.required is False
        assert request_body.description is None
        assert request_body.content_schema == {}

    def test_request_body_multiple_content_types(self):
        """Test request body with multiple content types."""
        content_schema = {
            "application/json": {
                "type": "object",
                "properties": {"name": {"type": "string"}},
            },
            "application/xml": {
                "type": "object",
                "properties": {"name": {"type": "string"}},
            },
        }

        request_body = RequestBodyInfo(content_schema=content_schema)

        assert len(request_body.content_schema) == 2
        assert "application/json" in request_body.content_schema
        assert "application/xml" in request_body.content_schema


class TestResponseInfo:
    """Test ResponseInfo model."""

    def test_basic_response(self):
        """Test creating a basic response."""
        response = ResponseInfo(description="Success response")

        assert response.description == "Success response"
        assert response.content_schema == {}

    def test_response_with_content_schema(self):
        """Test response with content schema."""
        content_schema = {
            "application/json": {
                "type": "object",
                "properties": {
                    "id": {"type": "integer"},
                    "message": {"type": "string"},
                },
            }
        }

        response = ResponseInfo(
            description="User created",
            content_schema=content_schema,
        )

        assert response.description == "User created"
        assert response.content_schema == content_schema

    def test_response_required_description(self):
        """Test that response description is required."""
        # Should not raise an error - description has a default
        response = ResponseInfo()
        assert response.description is None


class TestHTTPRoute:
    """Test HTTPRoute model."""

    def test_basic_route_creation(self):
        """Test creating a basic HTTP route."""
        route = HTTPRoute(
            path="/users/{id}",
            method="GET",
            operation_id="get_user",
        )

        assert route.path == "/users/{id}"
        assert route.method == "GET"
        assert route.operation_id == "get_user"
        assert route.summary is None
        assert route.description is None
        assert route.tags == []
        assert route.parameters == []
        assert route.request_body is None
        assert route.responses == {}

    def test_route_with_all_fields(self):
        """Test creating route with all fields."""
        parameters = [
            ParameterInfo(
                name="id",
                location="path",
                required=True,
                schema={"type": "integer"},
            )
        ]

        request_body = RequestBodyInfo(
            required=True,
            content_schema={
                "application/json": {
                    "type": "object",
                    "properties": {"name": {"type": "string"}},
                }
            },
        )

        responses = {
            "200": ResponseInfo(
                description="Success",
                content_schema={
                    "application/json": {
                        "type": "object",
                        "properties": {"id": {"type": "integer"}},
                    }
                },
            )
        }

        route = HTTPRoute(
            path="/users/{id}",
            method="PUT",
            operation_id="update_user",
            summary="Update user",
            description="Update user by ID",
            tags=["users"],
            parameters=parameters,
            request_body=request_body,
            responses=responses,
            request_schemas={"User": {"type": "object"}},
            extensions={"x-custom": "value"},
        )

        assert route.path == "/users/{id}"
        assert route.method == "PUT"
        assert route.operation_id == "update_user"
        assert route.summary == "Update user"
        assert route.description == "Update user by ID"
        assert route.tags == ["users"]
        assert len(route.parameters) == 1
        assert route.request_body is not None
        assert "200" in route.responses
        assert "User" in route.request_schemas
        assert route.extensions["x-custom"] == "value"

    def test_route_pre_calculated_fields(self):
        """Test route with pre-calculated fields."""
        route = HTTPRoute(
            path="/test",
            method="GET",
            operation_id="test",
            flat_param_schema={
                "type": "object",
                "properties": {"id": {"type": "integer"}},
            },
            parameter_map={"id": {"location": "path", "openapi_name": "id"}},
        )

        assert route.flat_param_schema["type"] == "object"
        assert "id" in route.flat_param_schema["properties"]
        assert "id" in route.parameter_map
        assert route.parameter_map["id"]["location"] == "path"

    @pytest.mark.parametrize(
        "method", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
    )
    def test_valid_http_methods(self, method: HttpMethod):
        """Test that all valid HTTP methods are accepted."""
        route = HTTPRoute(
            path="/test",
            method=method,
            operation_id="test",
        )
        assert route.method == method

    def test_route_with_empty_collections(self):
        """Test route with empty collections."""
        route = HTTPRoute(
            path="/test",
            method="GET",
            operation_id="test",
            tags=[],
            parameters=[],
            responses={},
            request_schemas={},
            extensions={},
        )

        assert route.tags == []
        assert route.parameters == []
        assert route.responses == {}
        assert route.request_schemas == {}
        assert route.response_schemas == {}
        assert route.extensions == {}

    def test_route_defaults(self):
        """Test route default values."""
        route = HTTPRoute(
            path="/test",
            method="GET",
            operation_id="test",
        )

        assert route.summary is None
        assert route.description is None
        assert route.tags == []
        assert route.parameters == []
        assert route.request_body is None
        assert route.responses == {}
        assert route.request_schemas == {}
        assert route.response_schemas == {}
        assert route.extensions == {}
        assert route.flat_param_schema == {}
        assert route.parameter_map == {}


class TestModelValidation:
    """Test model validation and error cases."""

    def test_parameter_info_validation(self):
        """Test ParameterInfo validation."""
        # Valid parameter
        param = ParameterInfo(
            name="test",
            location="query",
            schema={"type": "string"},
        )
        assert param.name == "test"

    def test_route_validation(self):
        """Test HTTPRoute validation."""
        # Valid route
        route = HTTPRoute(
            path="/test",
            method="GET",
            operation_id="test",
        )
        assert route.path == "/test"

    def test_nested_model_validation(self):
        """Test validation of nested models."""
        # Create route with nested models
        param = ParameterInfo(
            name="id",
            location="path",
            required=True,
            schema={"type": "integer"},
        )

        request_body = RequestBodyInfo(required=True)

        route = HTTPRoute(
            path="/test/{id}",
            method="POST",
            operation_id="test",
            parameters=[param],
            request_body=request_body,
        )

        assert len(route.parameters) == 1
        assert route.parameters[0].name == "id"
        assert route.request_body is not None
        assert route.request_body.required is True


class TestModelSerialization:
    """Test model serialization and deserialization."""

    def test_parameter_info_serialization(self):
        """Test ParameterInfo serialization."""
        param = ParameterInfo(
            name="filter",
            location="query",
            required=False,
            schema={"type": "object"},
            description="Filter criteria",
            explode=True,
            style="deepObject",
        )

        # Test model_dump with alias
        data = param.model_dump(by_alias=True)

        assert data["name"] == "filter"
        assert data["location"] == "query"
        assert data["required"] is False
        assert data["schema"] == {"type": "object"}  # Using alias
        assert data["description"] == "Filter criteria"
        assert data["explode"] is True
        assert data["style"] == "deepObject"

    def test_route_serialization(self):
        """Test HTTPRoute serialization."""
        param = ParameterInfo(
            name="id",
            location="path",
            required=True,
            schema={"type": "integer"},
        )

        route = HTTPRoute(
            path="/users/{id}",
            method="GET",
            operation_id="get_user",
            parameters=[param],
        )

        # Test model_dump
        data = route.model_dump()

        assert data["path"] == "/users/{id}"
        assert data["method"] == "GET"
        assert data["operation_id"] == "get_user"
        assert len(data["parameters"]) == 1
        assert data["parameters"][0]["name"] == "id"

    def test_model_reconstruction(self):
        """Test reconstructing models from serialized data."""
        # Create original parameter
        original_param = ParameterInfo(
            name="test",
            location="query",
            schema={"type": "string"},
            description="Test parameter",
        )

        # Serialize and reconstruct using by_alias
        data = original_param.model_dump(by_alias=True)

        data == snapshot(
            {
                "name": "test",
                "location": "query",
                "required": False,
                "schema": {"type": "string"},
                "description": "Test parameter",
                "explode": None,
                "style": None,
            }
        )

        reconstructed_param = ParameterInfo(
            name=data["name"],
            location=data["location"],
            schema=data["schema"],
            description=data["description"],
            explode=data["explode"],
            style=data["style"],
        )

        assert reconstructed_param.name == original_param.name
        assert reconstructed_param.location == original_param.location
        assert reconstructed_param.schema_ == original_param.schema_
        assert reconstructed_param.description == original_param.description
