from functools import partial
from inspect import isawaitable, isclass

from sanic.log import logger

from sanic_ext.exceptions import ValidationError
from sanic_ext.utils.typing import is_msgspec, is_pydantic

from .schema import make_schema
from .validators import (
    _msgspec_validate_instance,
    _validate_annotations,
    _validate_instance,
    validate_body,
)


async def do_validation(
    *,
    model,
    data,
    schema,
    request,
    kwargs,
    body_argument,
    allow_multiple,
    allow_coerce,
    strict=True,
):
    try:
        logger.debug(f"Validating {request.path} using {model}")
        if model is not None:
            if isclass(model):
                validator = _get_validator(
                    model, schema, allow_multiple, allow_coerce, strict
                )
                validation = validate_body(validator, model, data)
                kwargs[body_argument] = validation
            else:
                validation = model(
                    request=request, data=data, handler_kwargs=kwargs
                )
                if isawaitable(validation):
                    await validation
    except TypeError as e:
        raise ValidationError(e)


def generate_schema(param):
    try:
        if param is None or is_msgspec(param) or is_pydantic(param):
            return param
    except TypeError:
        ...

    return make_schema({}, param) if isclass(param) else param


def _get_validator(model, schema, allow_multiple, allow_coerce, strict=True):
    if is_msgspec(model):
        return partial(
            _msgspec_validate_instance,
            allow_coerce=allow_coerce,
            strict=strict,
        )
    elif is_pydantic(model):
        return partial(
            _validate_instance, allow_coerce=allow_coerce, strict=strict
        )

    return partial(
        _validate_annotations,
        schema=schema,
        allow_multiple=allow_multiple,
        allow_coerce=allow_coerce,
    )
