"""
Pydantic integration for payload-guard.
Provides seamless integration with Pydantic models.
"""

from __future__ import annotations
from typing import Any, Callable, Optional, Type, TypeVar, Generic
from pydantic import BaseModel, ValidationError, field_validator, model_validator
from pydantic_core import core_schema

from ..core.filter import build_shape
from ..core.types import ShapeDescriptor
from ..core.compiler import CompileOptions, compile_shape
from ..core.security import DEFAULT_SENSITIVE_FIELDS


T = TypeVar("T", bound=BaseModel)


class GuardedModel(BaseModel):
    """
    Base model with payload-guard integration.

    Example:
        from payloadguard.pydantic import GuardedModel

        class UserCreate(GuardedModel):
            name: str
            email: str
            age: int

            class Config:
                guard_shape = {
                    'name': 'string',
                    'email': 'string',
                    'age': 'number',
                }
    """

    class Config:
        guard_shape: Optional[ShapeDescriptor] = None
        guard_sensitive_fields: Optional[list[str]] = None
        guard_dev_mode: bool = False
        guard_strict: bool = False

    @classmethod
    def _get_guard_filter(cls) -> Optional[Callable]:
        """Get compiled guard filter."""
        guard_shape = getattr(cls.Config, "guard_shape", None)
        if not guard_shape:
            return None

        sensitive_fields = getattr(cls.Config, "guard_sensitive_fields", None) or []

        return compile_shape(
            guard_shape,
            CompileOptions(
                sensitive=sensitive_fields + DEFAULT_SENSITIVE_FIELDS,
                dev=getattr(cls.Config, "guard_dev_mode", False),
                strict=getattr(cls.Config, "guard_strict", False),
            ),
        )

    @classmethod
    def from_raw(cls: Type[T], data: dict[str, Any]) -> T:
        """
        Create model from raw data with guard filtering.

        Args:
            data: Raw unvalidated data

        Returns:
            Validated model instance
        """
        guard_filter = cls._get_guard_filter()
        if guard_filter:
            data = guard_filter(data)
        return cls.model_validate(data)

    @classmethod
    def validate_raw(cls: Type[T], data: dict[str, Any]) -> dict[str, Any]:
        """
        Validate and filter raw data without creating model instance.

        Args:
            data: Raw unvalidated data

        Returns:
            Filtered and validated data dict
        """
        guard_filter = cls._get_guard_filter()
        if guard_filter:
            data = guard_filter(data)
        return data


def guard_model(
    shape: Optional[ShapeDescriptor] = None,
    sensitive_fields: Optional[list[str]] = None,
    dev_mode: bool = False,
    strict: bool = False,
) -> Callable[[Type[T]], Type[T]]:
    """
    Decorator to add payload-guard to Pydantic model.

    Example:
        @guard_model({
            'name': 'string',
            'email': 'string',
        })
        class UserCreate(BaseModel):
            name: str
            email: str
    """

    def decorator(model_class: Type[T]) -> Type[T]:
        # Create new class with guard configuration
        class GuardConfig:
            guard_shape = shape
            guard_sensitive_fields = sensitive_fields
            guard_dev_mode = dev_mode
            guard_strict = strict

        # Add guard methods
        def _get_guard_filter() -> Optional[Callable]:
            if not shape:
                return None
            return compile_shape(
                shape,
                CompileOptions(
                    sensitive=(sensitive_fields or []) + DEFAULT_SENSITIVE_FIELDS,
                    dev=dev_mode,
                    strict=strict,
                ),
            )

        @classmethod
        def from_raw(cls: Type[T], data: dict[str, Any]) -> T:
            guard_filter = _get_guard_filter()
            if guard_filter:
                data = guard_filter(data)
            return cls.model_validate(data)

        @classmethod
        def validate_raw(cls: Type[T], data: dict[str, Any]) -> dict[str, Any]:
            guard_filter = _get_guard_filter()
            if guard_filter:
                data = guard_filter(data)
            return data

        # Create new class
        new_class = type(
            model_class.__name__,
            (model_class,),
            {
                "Config": GuardConfig,
                "_get_guard_filter": staticmethod(_get_guard_filter),
                "from_raw": from_raw,
                "validate_raw": validate_raw,
            },
        )

        return new_class  # type: ignore

    return decorator


class GuardedRequest(BaseModel, Generic[T]):
    """
    Wrapper for guarded request data with Pydantic model.

    Example:
        class UserCreate(BaseModel):
            name: str
            email: str

        @app.post("/users")
        def create_user(request: GuardedRequest[UserCreate]):
            user = request.data
            return user
    """

    data: T
    raw_data: dict[str, Any]
    is_sanitized: bool = True


def create_guarded_model(
    model: Type[BaseModel],
    shape: Optional[ShapeDescriptor] = None,
    sensitive_fields: Optional[list[str]] = None,
    dev_mode: bool = False,
    strict: bool = False,
) -> Type[BaseModel]:
    """
    Create a guarded version of a Pydantic model.

    Args:
        model: Original Pydantic model class
        shape: Guard shape descriptor
        sensitive_fields: Additional sensitive fields
        dev_mode: Enable development mode
        strict: Enable strict validation

    Returns:
        New model class with guard integration
    """

    class GuardedPydanticModel(model):
        """Guarded version of the model."""

        @classmethod
        def from_raw(cls, data: dict[str, Any]) -> "GuardedPydanticModel":
            """Create from raw data with guard filtering."""
            if shape:
                guard_filter = compile_shape(
                    shape,
                    CompileOptions(
                        sensitive=(sensitive_fields or []) + DEFAULT_SENSITIVE_FIELDS,
                        dev=dev_mode,
                        strict=strict,
                    ),
                )
                data = guard_filter(data)
            return cls.model_validate(data)

        @classmethod
        def validate_raw(cls, data: dict[str, Any]) -> dict[str, Any]:
            """Validate raw data without creating instance."""
            if shape:
                guard_filter = compile_shape(
                    shape,
                    CompileOptions(
                        sensitive=(sensitive_fields or []) + DEFAULT_SENSITIVE_FIELDS,
                        dev=dev_mode,
                        strict=strict,
                    ),
                )
                data = guard_filter(data)
            # Validate against model fields
            cls.model_validate(data)
            return data

    GuardedPydanticModel.__name__ = f"Guarded{model.__name__}"
    return GuardedPydanticModel


# Pydantic v1 compatibility
try:
    from pydantic import BaseSettings

    class GuardedSettings(BaseSettings):
        """Base settings class with guard integration."""

        class Config:
            guard_shape: Optional[ShapeDescriptor] = None
            guard_sensitive_fields: Optional[list[str]] = None

        @classmethod
        def from_raw(cls, data: dict[str, Any]):
            """Create settings from raw data with guard filtering."""
            guard_shape = getattr(cls.Config, "guard_shape", None)
            if guard_shape:
                sensitive_fields = getattr(cls.Config, "guard_sensitive_fields", None) or []
                guard_filter = compile_shape(
                    guard_shape,
                    CompileOptions(
                        sensitive=sensitive_fields + DEFAULT_SENSITIVE_FIELDS,
                    ),
                )
                data = guard_filter(data)
            return cls(**data)

except ImportError:
    # Pydantic v2 - use SettingsConfigDict
    pass
