"""
Schema Generation Module

Converts DRF serializers to JSON schemas using drf-spectacular.
Handles read-only, write-only, nested, and custom field types.
"""

import logging
from typing import Any, Dict, List, Optional, Type

from rest_framework import serializers
from rest_framework.serializers import Serializer

logger = logging.getLogger(__name__)


class SchemaConverter:
    """
    Converts DRF Serializer instances to JSON schemas suitable for MCP tool parameters.
    Uses drf-spectacular as primary schema source.
    """

    def __init__(self):
        """Initialize schema converter with drf-spectacular support"""
        self._schema_cache: Dict[int, Dict[str, Any]] = {}

    def serialize_to_schema(
        self,
        serializer_instance: Serializer,
        for_write: bool = True,
    ) -> Dict[str, Any]:
        """
        Convert a DRF Serializer to a JSON Schema.
        
        Args:
            serializer_instance: Instance of a DRF Serializer
            for_write: If True, include write-only fields. If False, include read-only fields.
            
        Returns:
            JSON Schema dict with properties, required fields, etc.
        """
        serializer_id = id(serializer_instance)
        cache_key = f"{serializer_id}_{for_write}"

        # Try drf-spectacular first
        try:
            schema = self._get_spectacular_schema(
                serializer_instance, for_write=for_write
            )
            if schema:
                return schema
        except Exception as e:
            logger.warning(
                f"Failed to generate schema with drf-spectacular: {e}",
                extra={"serializer": serializer_instance.__class__.__name__},
            )

        # Fallback to DRF's internal schema
        try:
            schema = self._get_drf_schema(serializer_instance, for_write=for_write)
            return schema
        except Exception as e:
            logger.error(
                f"Failed to generate schema for {serializer_instance.__class__.__name__}: {e}"
            )
            return self._empty_schema()

    def _get_spectacular_schema(
        self,
        serializer_instance: Serializer,
        for_write: bool = True,
    ) -> Optional[Dict[str, Any]]:
        """
        Get schema using drf-spectacular's AutoSchema.
        
        Args:
            serializer_instance: Serializer instance
            for_write: Whether to include write-only fields
            
        Returns:
            JSON Schema dict or None if drf-spectacular is unavailable
        """
        try:
            from drf_spectacular.openapi import AutoSchema
        except ImportError:
            return None

        try:
            schema_generator = AutoSchema()
            # Use drf-spectacular's internal methods to generate schema
            schema = self._map_serializer_fields(
                serializer_instance, for_write=for_write
            )
            return schema
        except Exception as e:
            logger.debug(f"drf-spectacular schema generation failed: {e}")
            return None

    def _get_drf_schema(
        self,
        serializer_instance: Serializer,
        for_write: bool = True,
    ) -> Dict[str, Any]:
        """
        Fallback schema generation using DRF's built-in serializer introspection.
        
        Args:
            serializer_instance: Serializer instance
            for_write: Whether to include write-only fields
            
        Returns:
            JSON Schema dict
        """
        properties: Dict[str, Any] = {}
        required: List[str] = []

        for field_name, field in serializer_instance.fields.items():
            # Skip read-only fields if writing, skip write-only if reading
            if for_write and field.read_only:
                continue
            if not for_write and field.write_only:
                continue

            # Convert field to JSON schema property
            prop_schema = self._field_to_schema(field)
            properties[field_name] = prop_schema

            # Track required fields
            if field.required and not field.read_only:
                required.append(field_name)

        return {
            "type": "object",
            "properties": properties,
            "required": required,
            "additionalProperties": False,
        }

    def _map_serializer_fields(
        self,
        serializer_instance: Serializer,
        for_write: bool = True,
    ) -> Dict[str, Any]:
        """
        Map serializer fields to JSON schema properties.
        """
        return self._get_drf_schema(serializer_instance, for_write=for_write)

    @staticmethod
    def _field_to_schema(field: serializers.Field) -> Dict[str, Any]:
        """
        Convert a single DRF field to JSON schema property.
        
        Args:
            field: DRF Serializer field instance
            
        Returns:
            JSON Schema property dict
        """
        schema: Dict[str, Any] = {}

        # Map DRF field types to JSON schema types
        if isinstance(field, serializers.BooleanField):
            schema["type"] = "boolean"
        elif isinstance(field, serializers.IntegerField):
            schema["type"] = "integer"
        elif isinstance(field, serializers.FloatField):
            schema["type"] = "number"
        elif isinstance(field, serializers.DecimalField):
            schema["type"] = "number"
        elif isinstance(field, serializers.CharField):
            schema["type"] = "string"
        elif isinstance(field, serializers.EmailField):
            schema["type"] = "string"
            schema["format"] = "email"
        elif isinstance(field, serializers.URLField):
            schema["type"] = "string"
            schema["format"] = "uri"
        elif isinstance(field, serializers.UUIDField):
            schema["type"] = "string"
            schema["format"] = "uuid"
        elif isinstance(field, serializers.DateField):
            schema["type"] = "string"
            schema["format"] = "date"
        elif isinstance(field, serializers.DateTimeField):
            schema["type"] = "string"
            schema["format"] = "date-time"
        elif isinstance(field, serializers.TimeField):
            schema["type"] = "string"
            schema["format"] = "time"
        elif isinstance(field, serializers.DurationField):
            schema["type"] = "string"
        elif isinstance(field, serializers.ListField):
            schema["type"] = "array"
            if hasattr(field, "child"):
                schema["items"] = SchemaConverter._field_to_schema(field.child)
            else:
                schema["items"] = {}
        elif isinstance(field, serializers.DictField):
            schema["type"] = "object"
        elif isinstance(field, serializers.Serializer):
            # Nested serializer
            nested_schema = SchemaConverter()._get_drf_schema(
                field, for_write=True
            )
            return nested_schema
        elif isinstance(field, serializers.PrimaryKeyRelatedField):
            # PrimaryKeyRelatedField maps to integer or string based on usage
            schema["type"] = "integer"
        elif isinstance(field, serializers.RelatedField):
            schema["type"] = "string"
        elif isinstance(field, serializers.SerializerMethodField):
            # SerializerMethodField is typically read-only
            schema["type"] = "string"
            schema["readOnly"] = True
        elif isinstance(field, serializers.ChoiceField):
            schema["type"] = "string"
            if hasattr(field, "choices"):
                schema["enum"] = list(field.choices.keys())
        else:
            # Default to string for unknown types
            schema["type"] = "string"

        # Add field description/help text
        if field.help_text:
            schema["description"] = str(field.help_text)

        # Add default if present
        if field.default is not None and field.default != serializers.empty:
            schema["default"] = field.default

        return schema

    @staticmethod
    def _empty_schema() -> Dict[str, Any]:
        """Return an empty schema"""
        return {
            "type": "object",
            "properties": {},
            "required": [],
            "additionalProperties": True,
        }

    def clear_cache(self) -> None:
        """Clear cached schemas"""
        self._schema_cache.clear()
