"""
Integration tests for OpenAPI schema generation and validation.

Tests that the backend generates valid OpenAPI schemas that can be used
for frontend Zod validation of search requests and responses.

These tests validate that the OpenAPI schema generated by the backend includes
sufficient information for the frontend to generate Zod validation schemas.
The tests are flexible about exact field names (snake_case vs camelCase) since
drf-spectacular handles the conversion automatically.
"""

from __future__ import annotations

import tempfile
from pathlib import Path

import pytest
import yaml
from django.core.management import call_command
from django.test import Client


@pytest.fixture
def api_client():
    """Provide Django test client for API requests."""
    return Client()


@pytest.fixture
def generated_schema():
    """
    Generate OpenAPI schema and return parsed YAML.

    This fixture generates the schema.yml file using the spectacular command
    and returns the parsed schema for validation.
    """
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".yml", delete=False, encoding="utf-8"
    ) as f:
        schema_path = f.name

    try:
        # Generate OpenAPI schema
        call_command("spectacular", "--file", schema_path, "--color")

        # Read and parse the schema with UTF-8 encoding
        with open(schema_path, "r", encoding="utf-8") as f:
            schema = yaml.safe_load(f)

        return schema
    finally:
        # Clean up temporary file
        Path(schema_path).unlink(missing_ok=True)


@pytest.mark.django_db
class TestRequestValidationWithZod:
    """
    For any search request to federated or analytics endpoints, the frontend
    should validate query parameters using generated Zod schemas. This test
    validates that the backend generates OpenAPI schemas with correct request
    parameter definitions that can be used for Zod validation.
    """

    @pytest.mark.parametrize(
        "endpoint",
        [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ],
    )
    def test_schema_includes_search_endpoints(self, generated_schema, endpoint):
        """
        Test that all search endpoints are defined in OpenAPI schema.

        This validates that the schema generation includes all search endpoints,
        which is necessary for frontend Zod schema generation.
        """
        assert "paths" in generated_schema, (
            "OpenAPI schema should include 'paths' section"
        )

        assert endpoint in generated_schema["paths"], (
            f"Endpoint '{endpoint}' should be in OpenAPI schema paths"
        )

        endpoint_def = generated_schema["paths"][endpoint]
        assert "get" in endpoint_def, (
            f"Endpoint '{endpoint}' should support GET method"
        )

    @pytest.mark.parametrize(
        "endpoint",
        [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ],
    )
    def test_endpoints_define_query_parameters(
        self, generated_schema, endpoint
    ):
        """
        Test that search endpoints define query parameters.

        This property test validates that for any search endpoint, the generated
        OpenAPI schema includes query parameter definitions, enabling frontend
        Zod schema generation for request validation.
        """
        endpoint_def = generated_schema["paths"][endpoint]
        get_def = endpoint_def["get"]

        # Verify parameters are defined
        assert "parameters" in get_def, (
            f"Endpoint '{endpoint}' should define parameters for Zod validation"
        )

        parameters = get_def["parameters"]
        assert len(parameters) > 0, (
            f"Endpoint '{endpoint}' should have at least one parameter"
        )

        # Verify each parameter has required fields for Zod generation
        for param in parameters:
            assert "name" in param, (
                f"Parameter in '{endpoint}' should have 'name' field"
            )

            assert "in" in param, (
                f"Parameter '{param.get('name')}' should have 'in' field"
            )

            assert param["in"] == "query", (
                f"Parameter '{param.get('name')}' should be in 'query'"
            )

            assert "schema" in param, (
                f"Parameter '{param.get('name')}' should have 'schema' for type definition"
            )

    def test_parameters_have_descriptions(self, generated_schema):
        """
        Test that parameters have descriptions for better validation messages.

        Descriptions help developers understand parameters and can be used
        in Zod validation error messages.
        """
        search_endpoints = [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ]

        for endpoint in search_endpoints:
            if endpoint not in generated_schema["paths"]:
                continue

            endpoint_def = generated_schema["paths"][endpoint]
            if "get" not in endpoint_def:
                continue

            get_def = endpoint_def["get"]
            if "parameters" not in get_def:
                continue

            parameters = get_def["parameters"]

            for param in parameters:
                param_name = param.get("name")
                assert "description" in param, (
                    f"Parameter '{param_name}' in '{endpoint}' should have a description"
                )

                assert param["description"], (
                    f"Parameter '{param_name}' in '{endpoint}' should have non-empty description"
                )


@pytest.mark.django_db
class TestResponseValidationWithZod:
    """
    For any search response from federated or analytics endpoints, the frontend
    should validate the response structure using generated Zod schemas. This test
    validates that the backend generates OpenAPI schemas with correct response
    definitions that can be used for Zod validation.
    """

    @pytest.mark.parametrize(
        "endpoint",
        [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ],
    )
    def test_endpoints_define_success_responses(
        self, generated_schema, endpoint
    ):
        """
        Test that search endpoints define 200 success responses.

        This validates that the schema includes response definitions for
        successful search operations, enabling Zod validation.
        """
        endpoint_def = generated_schema["paths"][endpoint]
        get_def = endpoint_def["get"]

        # Verify responses are defined
        assert "responses" in get_def, (
            f"Endpoint '{endpoint}' should define responses for Zod validation"
        )

        responses = get_def["responses"]

        # Verify 200 success response exists
        assert "200" in responses, (
            f"Endpoint '{endpoint}' should define 200 success response"
        )

        response_def = responses["200"]

        # Verify content is defined
        assert "content" in response_def, (
            f"Response 200 for '{endpoint}' should define content"
        )

        # Verify JSON content type
        assert "application/json" in response_def["content"], (
            f"Response 200 for '{endpoint}' should support application/json"
        )

        json_content = response_def["content"]["application/json"]

        # Verify schema exists
        assert "schema" in json_content, (
            f"JSON content for '{endpoint}' should have schema definition"
        )

    @pytest.mark.parametrize(
        "endpoint",
        [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ],
    )
    def test_response_schemas_are_defined_in_components(
        self, generated_schema, endpoint
    ):
        """
        Test that response schemas are properly defined in components section.

        This validates that response schemas can be resolved for Zod generation.
        """
        endpoint_def = generated_schema["paths"][endpoint]
        get_def = endpoint_def["get"]
        response_def = get_def["responses"]["200"]
        json_content = response_def["content"]["application/json"]
        schema_ref = json_content["schema"]

        # Schema should have either direct properties or a $ref
        if "$ref" in schema_ref:
            # Extract schema name from reference
            ref_path = schema_ref["$ref"]
            schema_name = ref_path.split("/")[-1]

            # Verify schema exists in components
            assert "components" in generated_schema, (
                "OpenAPI schema should have components section"
            )

            assert "schemas" in generated_schema["components"], (
                "Components should have schemas section"
            )

            assert schema_name in generated_schema["components"]["schemas"], (
                f"Schema '{schema_name}' should be defined in components"
            )

            actual_schema = generated_schema["components"]["schemas"][
                schema_name
            ]

            # Verify schema has properties
            assert "properties" in actual_schema, (
                f"Response schema '{schema_name}' for '{endpoint}' should define properties"
            )
        else:
            # Direct schema definition
            assert "properties" in schema_ref or "type" in schema_ref, (
                f"Response schema for '{endpoint}' should have properties or type"
            )

    def test_federated_search_response_has_required_fields(
        self, generated_schema
    ):
        """
        Test that federated search response includes key fields.

        This validates that the federated search response schema includes
        the essential fields needed for frontend validation.
        """
        endpoint = "/api/v1/search/federated"

        if endpoint not in generated_schema["paths"]:
            pytest.skip(f"Endpoint {endpoint} not found in schema")

        # Navigate to response schema
        endpoint_def = generated_schema["paths"][endpoint]
        get_def = endpoint_def["get"]
        response_def = get_def["responses"]["200"]
        json_content = response_def["content"]["application/json"]
        schema_ref = json_content["schema"]

        # Get the actual schema
        if "$ref" in schema_ref:
            ref_path = schema_ref["$ref"]
            schema_name = ref_path.split("/")[-1]
            response_schema = generated_schema["components"]["schemas"][
                schema_name
            ]
        else:
            response_schema = schema_ref

        # Verify key fields exist (flexible about exact names due to camelCase conversion)
        properties = response_schema["properties"]
        property_names_lower = [k.lower() for k in properties.keys()]

        # Check for essential fields (case-insensitive)
        assert any("limit" in name for name in property_names_lower), (
            "Federated response should include limit field"
        )

        assert any("offset" in name for name in property_names_lower), (
            "Federated response should include offset field"
        )

        assert any("result" in name for name in property_names_lower), (
            "Federated response should include results field"
        )

    def test_analytics_response_has_required_metrics(self, generated_schema):
        """
        Test that analytics response includes required metric fields.

        This validates Property 10 (Analytics metrics completeness) by ensuring
        the OpenAPI schema defines all required analytics fields.
        """
        endpoint = "/api/v1/search/analytics"

        if endpoint not in generated_schema["paths"]:
            pytest.skip(f"Endpoint {endpoint} not found in schema")

        # Navigate to response schema
        endpoint_def = generated_schema["paths"][endpoint]
        get_def = endpoint_def["get"]
        response_def = get_def["responses"]["200"]
        json_content = response_def["content"]["application/json"]
        schema_ref = json_content["schema"]

        # Get the actual schema
        if "$ref" in schema_ref:
            ref_path = schema_ref["$ref"]
            schema_name = ref_path.split("/")[-1]
            response_schema = generated_schema["components"]["schemas"][
                schema_name
            ]
        else:
            response_schema = schema_ref

        # Verify analytics fields exist (flexible about exact names)
        properties = response_schema["properties"]
        property_names_lower = [k.lower() for k in properties.keys()]

        # Check for essential analytics fields (case-insensitive)
        assert any(
            "query" in name or "queries" in name
            for name in property_names_lower
        ), "Analytics response should include queries field"

        assert any("volume" in name for name in property_names_lower), (
            "Analytics response should include volume field"
        )

        assert any(
            "performance" in name or "click" in name
            for name in property_names_lower
        ), "Analytics response should include performance or click metrics"

    @pytest.mark.parametrize(
        "endpoint",
        [
            "/api/v1/search/federated",
            "/api/v1/search/analytics",
            "/api/v1/search/product",
            "/api/v1/search/blog/post",
        ],
    )
    def test_error_responses_are_defined(self, generated_schema, endpoint):
        """
        Test that error responses (400) are defined in schema.

        This ensures the frontend can validate error responses with Zod schemas,
        providing type-safe error handling.
        """
        if endpoint not in generated_schema["paths"]:
            pytest.skip(f"Endpoint {endpoint} not found in schema")

        endpoint_def = generated_schema["paths"][endpoint]
        if "get" not in endpoint_def:
            pytest.skip(f"GET method not found for {endpoint}")

        get_def = endpoint_def["get"]

        # Verify responses section exists
        assert "responses" in get_def, (
            f"Endpoint '{endpoint}' should define responses"
        )

        responses = get_def["responses"]

        # Verify error response is defined (400 Bad Request)
        assert "400" in responses, (
            f"Endpoint '{endpoint}' should define 400 error response for validation errors"
        )

        # Verify error response has content
        error_response = responses["400"]
        assert "content" in error_response, (
            f"400 error response for '{endpoint}' should have content"
        )

        assert "application/json" in error_response["content"], (
            f"400 error response for '{endpoint}' should support JSON"
        )

        # Verify error schema exists
        json_content = error_response["content"]["application/json"]
        assert "schema" in json_content, (
            f"400 error response for '{endpoint}' should have schema for Zod validation"
        )
