import inspect
from functools import wraps

from sanic import Blueprint, Sanic
from sanic.blueprints import FutureMiddleware


class _Sanic(Sanic):

    def blueprint(self, blueprint, **options):
        super().blueprint(blueprint, **options)
        for bp in blueprint.nested:
            super().blueprint(bp, **options)


class _Blueprint(Blueprint):

    def __init__(self,
                 name,
                 url_prefix,
                 *,
                 host=None,
                 version=None,
                 strict_slashes=False):
        super().__init__(
            name,
            url_prefix=url_prefix,
            host=host,
            version=version,
            strict_slashes=strict_slashes,
        )

        self._blueprints = []

    @property
    def nested(self):
        for bp in self._blueprints:
            yield bp

    def register(self, app, options):
        if 'url_prefix' in options:
            options.pop('url_prefix')
        super().register(app, options)

    def bp(self, name, url_prefix):
        new_url_prefix = self.url_prefix + url_prefix
        new_name = '.'.join([self.name, name])
        bp = _Blueprint(
            new_name,
            url_prefix=new_url_prefix,
            host=self.host,
            version=self.version,
            strict_slashes=self.strict_slashes,
        )
        self._blueprints.append(bp)
        return bp

    def strict_middleware(self, attach_to='request'):

        def middleware(f):

            @wraps(f)
            async def inner_middleware(request, *args):
                if request.path.startswith(self.url_prefix):
                    if attach_to == 'request':
                        response = f(request)
                    elif attach_to == 'response':
                        response = f(request, *args)
                    else:
                        # TODO see what fits best here, an custom exception or
                        # just return None?
                        raise Exception('or return None?')  # noqa

                    if inspect.isawaitable(response):
                        response = await response  # noqa
                    return response

            future_middleware = FutureMiddleware(inner_middleware, [attach_to],
                                                 {})
            self.middlewares.append(future_middleware)
            return future_middleware

        return middleware


__all__ = ['_Blueprint', '_Sanic']
