from collections.abc import Awaitable
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar

from django.test import AsyncClient, AsyncRequestFactory, Client, RequestFactory

_ThingT = TypeVar('_ThingT')


class _DMRMixin:  # noqa: WPS338
    default_content_type: ClassVar[str] = 'application/json'

    if TYPE_CHECKING:  # noqa: WPS604  # pragma: no cover

        def _encode_data(
            self,
            data: dict[str, Any],
            content_type: str,
        ) -> bytes: ...

    else:  # pragma: no cover
        # This code is autogenerated, please do not touch it.
        # It lives under `if not TYPE_CHECKING` so it would not ruin
        # typing done by `django-stubs`.
        # ALL THIS CODE DOES IS JUST A DEFAULT CONTENT_TYPE CHANGE, WTF!

        def post(self, path, data='', content_type=None, *args, **kwargs):
            if content_type is None:
                content_type = self.default_content_type
            return super().post(path, data, content_type, *args, **kwargs)

        def options(self, path, data='', content_type=None, *args, **kwargs):
            if content_type is None:
                content_type = self.default_content_type
            return super().options(path, data, content_type, *args, **kwargs)

        def put(self, path, data='', content_type=None, *args, **kwargs):
            if content_type is None:
                content_type = self.default_content_type
            return super().put(path, data, content_type, *args, **kwargs)

        def patch(self, path, data='', content_type=None, *args, **kwargs):
            if content_type is None:
                content_type = self.default_content_type
            return super().patch(path, data, content_type, *args, **kwargs)

        def delete(self, path, data='', content_type=None, *args, **kwargs):
            if content_type is None:
                content_type = self.default_content_type
            return super().delete(path, data, content_type, *args, **kwargs)

        def generic(  # noqa: WPS211
            self,
            method,
            path,
            data='',
            content_type=None,
            *args,
            **kwargs,
        ):
            if isinstance(self, AsyncRequestFactory) or content_type is None:
                content_type = (kwargs.get('headers', {}) or {}).get(
                    'Content-Type',
                    self.default_content_type,
                )
            return super().generic(
                method,
                path,
                data,
                content_type,
                *args,
                **kwargs,
            )


class DMRRequestFactory(_DMRMixin, RequestFactory):
    """
    Test utility for testing apps using ``django-modern-rest``.

    Based on :class:`django.test.RequestFactory`.
    See their docs for advanced usage:
    https://docs.djangoproject.com/en/dev/topics/testing/tools

    This type, in contrast to a regular ``RequestFactory``,
    sets ``content-type`` as ``application/json``.

    Sets WSGI environment.
    """


class DMRAsyncRequestFactory(_DMRMixin, AsyncRequestFactory):
    """
    Version of :class:`DMRRequestFactory` but for ASGI environment.

    Uses the exactly the same API.
    """

    if TYPE_CHECKING:  # noqa: WPS604

        def wrap(self, thing: _ThingT) -> Awaitable[_ThingT]:  # pyright: ignore[reportReturnType]  # pyrefly: ignore [bad-return]
            """Does nothing."""

    else:

        def wrap(self, thing):
            """
            Utility method for testing.

            Pretends to wrap async controllers into async functions for typing.
            But in reallity does nothing.

            This happens due to the fact that ``View`` is typed as sync object.
            """
            return thing


class DMRClient(_DMRMixin, Client):
    """
    Test utility for testing apps using ``django-modern-rest``.

    Based on :class:`django.test.Client`.
    See their docs for advanced usage:
    https://docs.djangoproject.com/en/dev/topics/testing/tools/

    This type, in contrast to a regular ``Client``,
    sets ``content-type`` as ``application/json``.
    """


class DMRAsyncClient(_DMRMixin, AsyncClient):
    """
    Async version of :class:`DMRClient`.

    Uses ``async`` API.
    Requires you to ``await`` calls to ``.get``, ``.post``, etc.
    """
