# retoor <retoor@molodetz.nl>
import json
import logging
from typing import Any, TypeVar, get_origin

from fastapi import HTTPException, Request
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, ValidationError
from starlette.datastructures import FormData

logger = logging.getLogger(__name__)

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

_SEQUENCE_ORIGINS = frozenset({list, set, tuple, frozenset})


def _formdata_to_dict(form: FormData, model: type[BaseModel]) -> dict[str, Any]:
    body: dict[str, Any] = {}
    for field_name, field_info in model.model_fields.items():
        origin = get_origin(field_info.annotation)
        if origin in _SEQUENCE_ORIGINS:
            values = form.getlist(field_name)
            if not values:
                continue
            if values == [""]:
                continue
            body[field_name] = [v for v in values if v != ""] or []
        else:
            value = form.get(field_name)
            if value is not None:
                body[field_name] = value
    return body


class _JsonOrForm:
    def __init__(self, model: type[BaseModel]):
        self.model = model

    async def __call__(self, request: Request) -> Any:
        content_type = request.headers.get("content-type", "")
        body: Any = None
        try:
            if "application/json" in content_type:
                try:
                    body = await request.json()
                except (json.JSONDecodeError, UnicodeDecodeError, ValueError) as exc:
                    logger.debug("JSON parse failed: %s", exc)
                    raise HTTPException(status_code=400, detail="Invalid JSON body")
                if not isinstance(body, dict):
                    raise HTTPException(
                        status_code=400, detail="JSON body must be an object"
                    )
                return self.model.model_validate(body)
            try:
                form = await request.form()
            except Exception as exc:
                logger.debug("Form parse failed: %s", exc)
                raise HTTPException(
                    status_code=400, detail="Could not parse form data"
                )
            body = _formdata_to_dict(form, self.model)
            return self.model.model_validate(body)
        except ValidationError as exc:
            raise RequestValidationError(errors=exc.errors(), body=body)


def json_or_form(model: type[_TModel]) -> _JsonOrForm:
    return _JsonOrForm(model)
