from goblet.resources.pubsub import PubSub
from goblet.resources.routes import ApiGateway
from goblet.resources.scheduler import Scheduler
from goblet.resources.storage import Storage
from goblet.resources.http import HTTP

import logging

log = logging.getLogger(__name__)

EVENT_TYPES = ['all', 'http', 'schedule', 'pubsub', 'storage', 'route']


class DecoratorAPI:
    """Decorator endpoints that are called by the user. Returns _create_registration_function which will trigger the corresponding
    registration function in the Register_Handlers class. For example _create_registration_function with type route will call
    _register_route"""

    def middleware(self, event_type='all'):
        """Middleware functions that are called before events for preprocessing"""
        if event_type not in EVENT_TYPES:
            raise ValueError(f"{event_type} not in {EVENT_TYPES}")

        def _middleware_wrapper(func):
            self.register_middleware(func, event_type)
            return func
        return _middleware_wrapper

    def route(self, path, methods=['GET'], **kwargs):
        """Api Gateway route"""
        return self._create_registration_function(
            handler_type='route',
            registration_kwargs={'path': path, 'methods': methods, 'kwargs': kwargs},
        )

    def schedule(self, schedule, **kwargs):
        """Scheduler job Http trigger"""
        return self._create_registration_function(
            handler_type='schedule',
            registration_kwargs={'schedule': schedule, 'kwargs': kwargs},
        )

    def topic(self, topic, **kwargs):
        """Pubsub topic trigger"""
        return self._create_registration_function(
            handler_type='pubsub',
            registration_kwargs={'topic': topic, 'kwargs': kwargs},
        )

    def storage(self, bucket, event_type):
        """Storage event trigger"""
        return self._create_registration_function(
            handler_type='storage',
            registration_kwargs={'bucket': bucket, 'event_type': event_type},
        )

    def http(self, headers={}):
        """Base http trigger"""
        return self._create_registration_function(
            handler_type='http',
            registration_kwargs={'headers': headers},
        )

    def _create_registration_function(self, handler_type,
                                      registration_kwargs=None):
        def _register_handler(user_handler):
            handler_name = user_handler.__name__
            kwargs = registration_kwargs or {}
            self._register_handler(handler_type, handler_name,
                                   user_handler, kwargs)
            return user_handler
        return _register_handler

    def _register_handler(self, handler_type, name,
                          func, kwargs, options=None):
        raise NotImplementedError("_register_handler")

    def register_middleware(self, func, event_type='all'):
        raise NotImplementedError("register_middleware")


class Register_Handlers(DecoratorAPI):
    """Core Goblet logic. App entrypoint is the __call__ function which routes the request to the corresonding handler class"""

    def __init__(self, function_name, cors=None):
        self.handlers = {
            "route": ApiGateway(function_name, cors=cors),
            "schedule": Scheduler(function_name),
            "pubsub": PubSub(function_name),
            "storage": Storage(function_name),
            "http": HTTP()
        }
        self.middleware_handlers = {}
        self.current_request = None

    def __call__(self, request, context=None):
        """Goblet entrypoint"""
        self.current_request = request
        event_type = self.get_event_type(request, context)

        # call middleware
        request = self._call_middleware(request, event_type)

        if event_type == "schedule":
            return self.handlers['schedule'](request)
        if event_type == "pubsub":
            return self.handlers['pubsub'](request, context)
        if event_type == "storage":
            return self.handlers['storage'](request, context)
        if event_type == "route":
            return self.handlers["route"](request)
        if event_type == "http":
            return self.handlers["http"](request)

        raise ValueError(f"{event_type} not a valid event type")

    def __add__(self, other):
        for handler in self.handlers:
            self.handlers[handler] += other.handlers[handler]
        return self

    def combine(self, other):
        return self + other

    def get_event_type(self, request, context=None):
        """Parse event type from the event request and context"""
        if context and context.event_type:
            return context.event_type.split('.')[1].split('/')[0]
        if request.headers.get("X-Goblet-Type") == 'schedule':
            return "schedule"
        if request.path and request.path == '/' and not request.headers.get('X-Envoy-Original-Path'):
            return "http"
        if request.path:
            return "route"
        return None

    def _call_middleware(self, event, event_type):
        middleware = self.middleware_handlers.get('all', [])
        middleware.extend(self.middleware_handlers.get(event_type, []))
        for m in middleware:
            event = m(event)

        return event

    def _register_handler(self, handler_type, name,
                          func, kwargs, options=None):

        getattr(self, '_register_%s' % handler_type)(
            name=name,
            func=func,
            kwargs=kwargs,
        )

    def deploy(self, sourceUrl):
        """Call each handlers deploy method"""
        for k, v in self.handlers.items():
            log.info(f"deploying {k}")
            v.deploy(sourceUrl, self.entrypoint)

    def destroy(self):
        """Call each handlers destroy method"""
        for k, v in self.handlers.items():
            log.info(f"deploying {k}")
            v.destroy()

    def is_http(self):
        """Is http determines if additional cloudfunctions will be needed since triggers other than http will require their own
        function"""
        if len(self.handlers["route"].routes) > 0 or len(self.handlers["schedule"].jobs) > 0 or self.handlers["http"].http:
            return True
        return False

    def register_middleware(self, func, event_type='all'):
        middleware_list = self.middleware_handlers.get(event_type, [])
        middleware_list.append(func)
        self.middleware_handlers[event_type] = middleware_list

    def _register_http(self, name, func, kwargs):
        self.handlers["http"].register_http(func, kwargs=kwargs)

    def _register_route(self, name, func, kwargs):
        self.handlers["route"].register_route(name=name, func=func, kwargs=kwargs)

    def _register_schedule(self, name, func, kwargs):
        self.handlers["schedule"].register_job(name=name, func=func, kwargs=kwargs)

    def _register_pubsub(self, name, func, kwargs):
        self.handlers["pubsub"].register_topic(name=name, func=func, kwargs=kwargs)

    def _register_storage(self, name, func, kwargs):
        self.handlers["storage"].register_bucket(name=name, func=func, kwargs=kwargs)
