from pulsar import Http404, isawaitable
from pulsar.utils.log import LocalMixin, local_method

from .utils import handle_wsgi_error
from .wrappers import WsgiResponse


class WsgiHandler:
    '''An handler for applications conforming to python WSGI_.

    .. attribute:: middleware

        List of :ref:`asynchronous WSGI middleware <wsgi-middleware>` callables
        which accept ``environ`` and ``start_response`` as arguments.
        The order matter, since the response returned by the callable
        is the non ``None`` value returned by a middleware.

    .. attribute:: response_middleware

        List of functions of the form::

            def ..(environ, response):
                ...

        where ``response`` is a :ref:`WsgiResponse <wsgi-response>`.
        Pulsar contains some
        :ref:`response middlewares <wsgi-response-middleware>`.

    '''
    def __init__(self, middleware=None, response_middleware=None, async=True):
        if middleware:
            middleware = list(middleware)
        self.middleware = middleware or []
        self.response_middleware = response_middleware or []
        self._async = async

    def __call__(self, environ, start_response):
        c = self._async_call if self._async else self._sync_call
        return c(environ, start_response)

    async def _async_call(self, environ, start_response):
        response = None
        try:
            for middleware in self.middleware:
                response = middleware(environ, start_response)
                if isawaitable(response):
                    response = await response
                if response is not None:
                    break
            if response is None:
                raise Http404

        except Exception as exc:
            response = handle_wsgi_error(environ, exc)

        if isinstance(response, WsgiResponse) and not response.started:
            for middleware in self.response_middleware:
                response = middleware(environ, response) or response
                if isawaitable(response):
                    response = await response
            response.start(start_response)
        return response

    def _sync_call(self, environ, start_response):
        response = None
        try:
            for middleware in self.middleware:
                response = middleware(environ, start_response)
                if response is not None:
                    break
            if response is None:
                raise Http404

        except Exception as exc:
            response = handle_wsgi_error(environ, exc)

        if isinstance(response, WsgiResponse) and not response.started:
            for middleware in self.response_middleware:
                response = middleware(environ, response) or response
            response.start(start_response)
        return response


class LazyWsgi(LocalMixin):
    '''A :ref:`wsgi handler <wsgi-handlers>` which loads the actual
    handler the first time it is called.

    Subclasses must implement the :meth:`setup` method.
    Useful when working in multiprocessing mode when the application
    handler must be a ``picklable`` instance. This handler can rebuild
    its wsgi :attr:`handler` every time is pickled and un-pickled without
    causing serialisation issues.
    '''
    def __call__(self, environ, start_response):
        return self.handler(environ)(environ, start_response)

    @local_method
    def handler(self, environ=None):
        '''The :ref:`wsgi application handler <wsgi-handlers>` which
        is loaded via the :meth:`setup` method, once only,
        when first accessed.
        '''
        return self.setup(environ)

    def setup(self, environ=None):
        '''The setup function for this :class:`LazyWsgi`.

        Called once only the first time this application handler is invoked.
        This **must** be implemented by subclasses and **must** return a
        :ref:`wsgi application handler <wsgi-handlers>`.
        '''
        raise NotImplementedError
