#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# File   : application.py
# Author : Jiayuan Mao
# Email  : maojiayuan@gmail.com
# Date   : 10/23/2018
#
# Qinsheng Zhang modified based on Jacinle.
# Distributed under terms of the MIT license.
# pylint: skip-file
import time
from copy import deepcopy

from tornado.web import Application, RequestHandler

from jammy.logging import get_logger
from jammy.utils.imp import load_module

logger = get_logger()

__all__ = ["route", "make_app", "get_app", "JamApplication", "JamRequestHandler"]

__handlers__ = []
__application__ = None
__default_frontend_salt__ = (
    "|v<zj!#PGnxgc])Sd_=i|8,?,7><s5+Ck_IWcbC D:@4a~(1bEA=M+MacyI2udaz"
)

# pylint: disable=global-statement
def route(regex):
    global __handlers__

    def wrap_class(cls):
        __handlers__.append((regex, cls))
        return cls

    return wrap_class


def make_app(modules, settings):
    global __application__, __handlers__
    assert __application__ is None

    for m in modules:
        load_module(m)
    __application__ = JamApplication(__handlers__, **settings)
    return __application__


def get_app():
    global __application__
    return __application__


try:
    _ARG_DEFAULT = RequestHandler._ARG_DEFAULT
except:  # pylint: disable=bare-except
    from tornado.web import _ARG_DEFAULT


class JamRequestHandler(RequestHandler):
    def __init__(self, *args, **kwargs):
        self.session = None
        self.__start_time = None
        self.__end_time = None

        super().__init__(*args, **kwargs)

    def initialize(self):
        if self.application.session_enabled:
            self.session = self.application.get_session(self)
        self.__start_time = time.time()

    def save_session(self):
        if self.application.session_enabled:
            self.session.save()

    def get_template_namespace(self):
        dct = super().get_template_namespace()
        dct["url"] = self.request.uri
        dct["url_root"] = self.application.settings.get("url_root", "/")
        dct["is_localhost"] = self.application.settings.get("is_localhost", False)
        dct["frontend_secret"] = self.application.settings.get(
            "frontend_secret", __default_frontend_salt__
        )
        return dct

    def finish(self, *args, **kwargs):
        self.on_body_finish()
        super().finish(*args, **kwargs)

    def on_body_finish(self):
        self.save_session()

    def on_finish(self):
        self.__end_time = time.time()
        logger.info(
            "{} query {} executed for {:.3f} ms".format(
                self.request.remote_ip,
                self.request.uri,
                (self.__end_time - self.__start_time) * 1000,
            )
        )
        super().on_finish()

    def get_query_argument(
        self, name, default=_ARG_DEFAULT, strip=True, type=None, danger_set=None
    ):
        return self._get_argument(
            name, default, self.request.arguments, strip, type, danger_set
        )

    def get_body_argument(
        self, name, default=_ARG_DEFAULT, strip=True, type=None, danger_set=None
    ):
        return self._get_argument(
            name, default, self.request.body_arguments, strip, type, danger_set
        )

    def get_argument(
        self, name, default=_ARG_DEFAULT, strip=True, type=None, danger_set=None
    ):
        return self._get_argument(
            name, default, self.request.arguments, strip, type, danger_set
        )

    def _get_argument(
        self, name, default, source, strip=True, type=None, danger_set=None
    ):
        raw = super()._get_argument(name, default, source, strip)
        if type is int:
            return self._assert_int(raw)
        elif type is str:
            return self._assert_safe_string(raw, danger_set)
        elif type is None:
            return raw
        else:
            raise NotImplementedError("Unknown argument type: {}.".format(type))

    def _assert_int(self, v):
        try:
            v = int(v)
        except ValueError:
            raise JamRequestException("Invalid data submitted.")
        return v

    def _assert_safe_string(self, s, danger_set):
        if danger_set is None:
            danger_set = "`#'\"<>"
        try:
            s = str(s)
            for i in s:
                if i in danger_set:
                    raise ValueError
        except ValueError:
            raise JamRequestException("Invalid data submitted.")
        return s


class JamApplication(Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        if self.settings["session_engine"] == "off":
            self.session_enabled = False
            self.session_manager = None
        elif self.settings["session_engine"] == "memcached":
            from jamweb.session.memcached import MemcachedSessionManager

            self.session_enabled = True
            self.session_manager = MemcachedSessionManager(
                secret=self.settings["session_secret"],
                memcache_host=self.settings["memcached_host"],
                memcache_port=self.settings["memcached_port"],
                timeout=self.settings["session_timeout"],
                cookie_prefix=self.settings["session_cookie_prefix"],
                memcached_prefix=self.settings["session_memcached_prefix"],
            )
            logger.critical(
                "Initializing the session manager using memcached: {}.".format(
                    self.session_manager.memcache.full_addr
                )
            )
        else:
            raise ValueError(
                "Unknown session engine: {}.".format(self.settings["session_engine"])
            )

    def get_session(self, request_handler):
        assert self.session_manager is not None, "Session manager not initialized."
        return self.session_manager.new(request_handler)


class JamRequestException(Exception):
    pass
