import importlib
import inspect

from apispec import APISpec
from apispec.ext.bottle import BottlePlugin
from apispec.ext.marshmallow import MarshmallowPlugin
from marshmallow import Schema

IGNORED_TYPES = ['Schema']


def disable_swagger(callback):
    """
    Decorator for removing endpoint from OpenAPI Swagger JSON
    """
    callback.enable_swagger = False
    return callback


class APISpecPlugin(object):
    """
    Bottle APISpec Plugin

    Used for automagically generating OpenAPI Swagger schema based on Marshmallow models
    """
    name = 'apispec'
    api = 2

    def __init__(self, path='/schema.json', scan_package=None, *args, **kwargs):
        default_plugins = [BottlePlugin()]
        if scan_package:
            default_plugins.append(MarshmallowPlugin())
        kwargs['plugins'] = kwargs.get('plugins', ()) + tuple(default_plugins)
        self.apispec = APISpec(*args, **kwargs)
        self.scan_package = scan_package
        self.path = path

    def setup(self, app):
        if not app.routes:
            raise Exception('No routes found. Please be sure to install APISpecPlugin after declaring *all* your routes!')

        if self.scan_package:
            self._scan_marshmallow_models(self.scan_package)

        for route in app.routes:
            if hasattr(route.callback, 'enable_swagger') and not route.callback.enable_swagger:
                continue
            self.apispec.add_path(view=route.callback)

        @app.get(self.path)
        def schema():
            return self.apispec.to_dict()

    def apply(self, callback, route):
        return callback

    def _scan_marshmallow_models(self, base_package):
        base_module = importlib.import_module(base_package)
        for name, obj in inspect.getmembers(base_module):
            if inspect.ismodule(obj) and obj.__package__ == base_package:
                self._scan_marshmallow_models('%s.%s' % (base_package, name))
            elif name not in IGNORED_TYPES and inspect.isclass(obj) and issubclass(obj, Schema):
                self.apispec.definition(name, schema=obj)
