from chalice import Chalice, CORSConfig
from chalicelib.blueprints import bp_authorizers
from chalicelib.core import authorizers

import sched
import threading
import time
from datetime import datetime
import pytz
from croniter import croniter

base_time = datetime.now(pytz.utc)

cors_config = CORSConfig(
    allow_origin='*',
    allow_headers=['vnd.openreplay.com.sid', 'vnd.asayer.io.sid'],
    # max_age=600,
    # expose_headers=['X-Special-Header'],
    allow_credentials=True
)


def chalice_app(app):
    def app_route(self, path, **kwargs):
        kwargs.setdefault('cors', cors_config)
        kwargs.setdefault('authorizer', bp_authorizers.jwt_authorizer)
        handler_type = 'route'
        name = kwargs.pop('name', None)
        registration_kwargs = {'path': path, 'kwargs': kwargs, 'authorizer': kwargs.get("authorizer")}

        def _register_handler(user_handler):
            handler_name = name
            if handler_name is None:
                handler_name = user_handler.__name__
            if registration_kwargs is not None:
                kwargs = registration_kwargs
            else:
                kwargs = {}

            if kwargs['authorizer'] == bp_authorizers.jwt_authorizer \
                    or kwargs['authorizer'] == bp_authorizers.api_key_authorizer:
                def _user_handler(context=None, **args):
                    if context is not None:
                        args['context'] = context
                    else:
                        authorizer_context = app.current_request.context['authorizer']
                        if kwargs['authorizer'] == bp_authorizers.jwt_authorizer:
                            args['context'] = authorizers.jwt_context(authorizer_context)
                        else:
                            args['context'] = authorizer_context
                    return user_handler(**args)

                wrapped = self._wrap_handler(handler_type, handler_name, _user_handler)
                self._register_handler(handler_type, handler_name, _user_handler, wrapped, kwargs)
            else:
                wrapped = self._wrap_handler(handler_type, handler_name, user_handler)
                self._register_handler(handler_type, handler_name, user_handler, wrapped, kwargs)
            return wrapped

        return _register_handler

    app.route = app_route.__get__(app, Chalice)

    def app_schedule(self, expression, name=None, description=''):
        handler_type = 'schedule'
        registration_kwargs = {'expression': expression,
                               'description': description}

        def _register_handler(user_handler):
            handler_name = name
            if handler_name is None:
                handler_name = user_handler.__name__
            kwargs = registration_kwargs
            cron_expression = kwargs["expression"].to_string()[len("cron("):-1]
            if len(cron_expression.split(" ")) > 5:
                cron_expression = " ".join(cron_expression.split(" ")[:-1])
                cron_expression = cron_expression.replace("?", "*")
            cron_shell(user_handler, cron_expression)

            wrapped = self._wrap_handler(handler_type, handler_name, user_handler)
            self._register_handler(handler_type, handler_name, user_handler, wrapped, kwargs)
            return wrapped

        return _register_handler

    app.schedule = app_schedule.__get__(app, Chalice)

    def spawn(function, args):
        th = threading.Thread(target=function, kwargs=args)
        th.setDaemon(True)
        th.start()

    def cron_shell(function, cron_expression):
        def to_start():
            scheduler = sched.scheduler(time.time, time.sleep)
            citer = croniter(cron_expression, base_time)
            while True:
                next_execution = citer.get_next(datetime)
                print(f"{function.__name__} next execution: {next_execution}")
                scheduler.enterabs(next_execution.timestamp(), 1, function, argument=(None,))
                scheduler.run()
                print(f"{function.__name__} executed: {next_execution}")

        spawn(to_start, None)
