# -*- coding: utf-8 -*-
"""
@Author: ChenXiaolei
@Date: 2020-03-06 23:17:54
@LastEditTime: 2020-04-25 23:55:32
@LastEditors: ChenXiaolei
@Description: Handler基础类
"""

# base import
import tornado.web
import time
import datetime
import base64
import json
import io
import bleach
import asyncio

# tornado import
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor

# seven_framework import
from seven_framework import *


class BaseHandler(tornado.web.RequestHandler):
    THREAD_POOL_COUNT = 500
    """
    @description: 基础handler
    @last_editors: ChenXiaolei
    """
    executor = ThreadPoolExecutor(THREAD_POOL_COUNT)

    def __init__(self, *argc, **argkw):
        """
        @description: 初始化
        @last_editors: ChenXiaolei
        """
        super(BaseHandler, self).__init__(*argc, **argkw)
        self.logger_error = Logger.get_logger_by_name("log_error")
        self.logger_info = Logger.get_logger_by_name("log_info")

    # 异步重写
    async def get(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.get_async)

    async def post(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.post_async)

    async def delete(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.delete_async)

    async def put(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.put_async)

    async def head(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.head_async)

    async def options(self, *args, **kwargs):
        await asyncio.get_event_loop().run_in_executor(self.executor,
                                                       self.options_async)

    # 通用handle页面数据返回
    def render(self, template_name, **template_vars):
        """
        @description: 渲染html源码
        @param template_name: 前端模板路径
        @param **template_vars: 传递给模板的参数
        @return: 返回客户端渲染页面
        @last_editors: ChenXiaolei
        """
        html = self.render_string(template_name, **template_vars)
        self.write(html)

    def check_xsrf_cookie(self):
        """
        @description: 过滤受_xsrf影响的post请求 通过获取_xsrf|X-Xsrftoken|X-Csrftoken判断
        @last_editors: ChenXiaolei
        """
        def _time_independent_equals(a, b):
            if len(a) != len(b):
                return False
            result = 0
            if isinstance(a[0], int):  # python3 byte strings
                for x, y in zip(a, b):
                    result |= x ^ y
            else:  # python2
                for x, y in zip(a, b):
                    result |= ord(x) ^ ord(y)
            return result == 0

        if '/api/' in self.request.uri:
            return
        else:
            token = (self.get_argument("_xsrf", None)
                     or self.request.headers.get("X-Xsrftoken")
                     or self.request.headers.get("X-Csrftoken"))
            if not token:
                raise tornado.web.HTTPError(
                    403, "'_xsrf' argument missing from POST")
            _, token, _ = self._decode_xsrf_token(token)
            _, expected_token, _ = self._get_raw_xsrf_token()
            if not _time_independent_equals(
                    tornado.web.escape.utf8(token),
                    tornado.web.escape.utf8(expected_token)):
                raise tornado.web.HTTPError(
                    403, "XSRF cookie does not match POST argument")

    # 页面过滤方法，防止注入
    def html_clean(self, htmlstr):
        """
           采用bleach来清除不必要的标签，并linkify text
        """
        tags = [
            'a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li',
            'ol', 'strong', 'ul'
        ]
        tags.extend([
            'div', 'p', 'hr', 'br', 'pre', 'code', 'span', 'h1', 'h2', 'h3',
            'h4', 'h5', 'del', 'dl', 'img', 'sub', 'sup', 'u'
            'table', 'thead', 'tr', 'th', 'td', 'tbody', 'dd', 'caption',
            'blockquote', 'section'
        ])
        attributes = {
            '*': ['class', 'id'],
            'a': ['href', 'title', 'target'],
            'img': ['src', 'style', 'width', 'height']
        }
        return bleach.linkify(
            bleach.clean(htmlstr, tags=tags, attributes=attributes))

    def write_error(self, status_code, **kwargs):
        """
        @description: 重写全局异常事件捕捉
        @last_editors: ChenXiaolei
        """
        self.logger_error.exception('error_info:')

    def get_param(self, param_name, default=""):
        """
        @description: 二次封装获取参数，经过html注入过滤
        @param param_name: 参数名
        @param default: 如果无此参数，则返回默认值
        @return: 参数值
        @last_editors: ChenXiaolei
        """
        param_ret = self.get_argument(param_name, default)
        return self.html_clean(param_ret)

    def get_remote_ip(self):
        """
        @description: 获取客户端真实IP
        @return: 客户端真实IP字符串
        @last_editors: ChenXiaolei
        """
        ip_address = ""
        if "X-Forwarded-For" in self.request.headers:
            ip_address = self.request.headers['X-Forwarded-For']
        elif "X-Real-Ip" in self.request.headers:
            ip_address = self.request.headers['X-Real-Ip']
        else:
            ip_address = self.request.remote_ip
        return ip_address

    def reponse_common(self, result, desc, data=None):
        """
        @description: 输出公共json模型
        @param result: 返回结果标识
        @param desc: 返回结果描述
        @param data: 返回结果对象，即为数组，字典
        @return: 将dumps后的数据字符串返回给客户端
        @last_editors: ChenXiaolei
        """
        template_value = {}
        template_value['result'] = result
        template_value['desc'] = desc
        template_value['data'] = data

        self.http_reponse(
            json.dumps(template_value, ensure_ascii=False, cls=JsonEncoder))

    def http_reponse(self, content):
        """
        @description: 将字符串返回给客户端
        @param content: 内容字符串
        @return: 将字符串返回给客户端
        @last_editors: ChenXiaolei
        """
        self.write(content)

    def reponse_json_success(self, data=None, desc='success'):
        """
        @description: 通用成功返回json结构
        @param data: 返回结果对象，即为数组，字典
        @param desc: 返回结果描述
        @return: 将dumps后的数据字符串返回给客户端
        @last_editors: ChenXiaolei
        """
        self.reponse_common(1, desc, data)

    def reponse_json_error(self, desc='error'):
        """
        @description: 通用错误返回json结构
        @param desc: 返错误描述
        @return: 将dumps后的数据字符串返回给客户端
        @last_editors: ChenXiaolei
        """
        self.reponse_common(0, desc)

    def reponse_json_error_params(self, desc='params error'):
        """
        @description: 通用参数错误返回json结构
        @param desc: 返错误描述
        @return: 将dumps后的数据字符串返回给客户端
        @last_editors: ChenXiaolei
        """
        self.reponse_common(0, desc)

    def redirect_url(self,
                     url: str,
                     permanent: bool = False,
                     status: int = None):
        """
        @description: 用于异步handler直接进行页面重定向
        @param url: 需跳转到的url
        @param permanent: 表示该重定向为临时性的；如果为True，则该重定向为永久性。
        @param status: 默认302,当status被指定了值的话，那个该值将会作为HTTP返回给客户端的状态码；如果没有指定特定的值，那么根据上方的permanent状态，如果permanent为True，则该status返回301；如果permanent为False，则该status返回302。
        @return: 重定向
        @last_editors: ChenXiaolei
        """
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        self.redirect(url, permanent, status)


def filter_check_params(must_params, optional_params=None):
    """
    @description: 参数过滤装饰器 仅限handler使用,
                  提供参数的检查及获取参数功能
                  装饰器使用方法:
                  @param_filter("param_a","param_b","param_c","param_d")  或
                  @param_filter(["param_a","param_b"],["param_c","param_d"])
                  参数获取方法:
                  self.request_parms[param_key]
    @param must_params: 必须传递的参数集合
    @param optional_params: 可选传递的参数集合
    @last_editors: ChenXiaolei
    """
    def check_params(handler):
        def wrapper(self, **args):
            if not must_params and not optional_params:
                self.reponse_json_error_params()
                return
            self.request_parms = {}
            if type(must_params) == str:
                must_array = must_params.split(",")
            if optional_params and type(optional_params) == str:
                optional_array = optional_params.split(",")

            for param in must_array:
                value = self.get_param(param)
                if not value or value == "":
                    self.reponse_json_error_params()
                    return
                self.request_parms[param] = value

            if not optional_array and len(optional_array) > 0:
                for param in optional_array:
                    value = self.get_param(param)
                    if value and value != "":
                        self.request_parms[param] = value
            return handler(self, **args)

        return wrapper

    return check_params