from typing import Any, Callable, List, Optional

from .globals import request
from .typing import ResponseReturnValue

http_method_funcs = frozenset([
    'get', 'post', 'head', 'options', 'delete', 'put', 'trace', 'patch',
])


class View:
    """Use to define routes within a class structure.

    A View subclass must implement the :meth:`dispatch_request` in
    order to respond to requets. For automatic method finding based on
    the request HTTP Verb see :class:`MethodView`.

    An example usage is,

    .. code-block:: python

          class SimpleView:
              methods = ['GET']

              async def dispatch_request(id):
                  return f"ID is {id}"

          app.add_url_rule('/<id>', view_func=SimpleView.as_view('simple'))

    Note that class

    Attributes:
        decorators: A list of decorators to apply to a view
            method. The decorators are applied in the order of
            the list.
        methods: List of methods this view allows.
        provide_automatic_options: Override automatic OPTIONS
            if set, to either True or False.
    """
    decorators: List[Callable] = []
    methods: Optional[List[str]] = None
    provide_automatic_options: Optional[bool] = None

    async def dispatch_request(self, *args: Any, **kwargs: Any) -> ResponseReturnValue:
        """Override and return a Response.

        This will be called with the request view_args, i.e. any url
        parameters.
        """
        raise NotImplementedError()

    @classmethod
    def as_view(cls, name: str, *class_args: Any, **class_kwargs: Any) -> Callable:

        async def view(*args: Any, **kwargs: Any) -> Callable:
            self = view.view_class(*class_args, **class_kwargs)  # type: ignore
            return await self.dispatch_request(*args, **kwargs)

        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            for decorator in cls.decorators:
                view = decorator(view)

        view.view_class: View = cls  # type: ignore
        view.__name__ = name
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.methods = cls.methods  # type: ignore
        view.provide_automatic_options = cls.provide_automatic_options  # type: ignore
        return view


class MethodViewType(type):

    def __init__(cls, name, bases, attributes):  # type: ignore # noqa
        super().__init__(name, bases, attributes)

        if 'methods' not in attributes:
            methods = []

            for key in http_method_funcs:
                if hasattr(cls, key):
                    methods.append(key.upper())

            if methods:
                cls.methods = methods


class MethodView(View, metaclass=MethodViewType):
    """A HTTP Method (verb) specific view class.

    This has an implementation of :meth:`dispathc_request` such that
    it calls a method based on the verb i.e. GET requests are handled
    by a `get` method. For example,

    .. code-block:: python

        class SimpleView(MethodView):
            async def get(id):
                return f"Get {id}"

            async def post(id):
                return f"Post {id}"

          app.add_url_rule('/<id>', view_func=SimpleView.as_view('simple'))
    """

    async def dispatch_request(self, *args: Any, **kwargs: Any) -> ResponseReturnValue:
        handler = getattr(self, request.method.lower(), None)

        if handler is None and request.method == 'HEAD':
            handler = getattr(self, 'get', None)

        return await handler(*args, **kwargs)
