# coding: utf-8

import json
import re
import socket

import hashlib
import time

from pyDes import des, CBC, PAD_PKCS5
import binascii
import requests
from concurrent.futures import ThreadPoolExecutor
from tornado.escape import json_decode, json_encode
from tornado.concurrent import run_on_executor
import tornado.web
from app.base.controller import TPBaseHandler
from app.base.logger import *
from app.const import TP_PRIVILEGE_ASSET_CREATE, TPE_PARAM, TPE_OK, TPE_JSON_FORMAT, TP_AUTH_TYPE_PASSWORD, \
    TP_AUTH_TYPE_PRIVATE_KEY, TPE_EXISTS, TPE_FAILED, TPE_NOT_EXISTS
from app.model import plugin
from app.model.plugin import free_host


# todo 间歇性 ERROR:tornado.application:Uncaught exception

def current_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    ip = s.getsockname()[0]
    s.close()
    return ip


def network_ip():
    req = requests.get("http://txt.go.sohu.com/ip/soip")
    ip = re.findall(r'\d+.\d+.\d+.\d+', req.text)[0]
    return ip


# 秘钥
KEY = 'mHAxsLYz'


def des_encrypt(s, key=None):
    """
    DES 加密
    :param s: 原始字符串
    :return: 加密后字符串，16进制
    """
    secret_key = key or KEY
    iv = secret_key
    k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    en = k.encrypt(s, padmode=PAD_PKCS5)
    return binascii.b2a_hex(en)


def des_descrypt(s, key=None):
    """
    DES 解密
    :param s: 加密后的字符串，16进制
    :return:  解密后的字符串
    """
    secret_key = key or KEY
    iv = secret_key
    k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
    return de


def md5(str):
    m = hashlib.md5()
    b = str.encode(encoding='utf-8')
    m.update(b)
    return m.hexdigest()


class TPBasePluginHandler(TPBaseHandler):
    """
    所有返回JSON数据的控制器均从本类继承，返回的数据格式一律包含三个字段：code/msg/data
    code: TPE_OK=成功，其他=失败
    msg: 字符串，一般用于code为非零时，指出错误原因
    data: 一般用于成功操作的返回的业务数据
    """
    executor = ThreadPoolExecutor(100)

    def __init__(self, application, request, **kwargs):
        super().__init__(application, request, **kwargs)
        self._mode = self.MODE_JSON
        self.local_ip = current_ip()
        self.outer_ip = network_ip()

    async def prepare(self):
        # req = requests.get("http://txt.go.sohu.com/ip/soip")
        # ip = re.findall(r'\d+.\d+.\d+.\d+', req.text)[0]
        # self.request.remote_ip
        _user = self.get_session('user')
        if _user is None:
            _user = {
                'id': 0,
                'username': 'guest',
                'surname': '访客',
                'role_id': 0,
                'role': '访客',
                'privilege': TP_PRIVILEGE_ASSET_CREATE,
                '_is_login': False
            }
        self._user = _user

        if not self.control_ip(self.request.remote_ip):
            raise tornado.web.HTTPError(404)

    def get_payload(self):
        log.i(self.request.body)
        log.i("\n")
        return json_decode(self.request.body)

    def check_ip(self, props):
        ip = props.get("ip", "")
        if ip and not re.findall(r'\d+\.\d+\.\d+\.\d+', ip):
            return False
        else:
            return True

    def control_ip(self, ip):
        items = ip.split(".")[:2]
        if ip == '127.0.0.1' or items == self.outer_ip.split(".")[:2] or items == self.local_ip.split(".")[:2]:
            return True
        else:
            return False

    def finish_json(self, code, msg="成功", data: list = None):
        _ret = {"res": 1 if not code else 0, "ec": code, "msg": msg}
        if data:
            _ret["dt"] = {"lst": data}
        self.set_header("Content-Type", "application/json")
        log.i(_ret)
        log.i('\n')
        self.write(json_encode(_ret))
        self.finish()

    def param_get(self, props: dict, fields: list, default):
        return [props.get(i) or default for i in fields]

    @run_on_executor
    def request_api(self, url, data=None, json=None):
        _user = {'id': 1, 'type': 1, 'auth_type': 2, 'username': 'admin', 'surname': 'admin', 'ldap_dn': '',
                 'role_id': 1,
                 'state': 1, 'fail_count': 0, 'lock_time': 0, 'email': '929749555@qq.com', 'create_time': 1583104533,
                 'last_login': 1583788567, 'last_ip': '172.30.10.106', 'last_chpass': 1583104533, 'mobile': '',
                 'qq': '',
                 'wechat': '', 'desc': '', 'role': '系统管理员', 'privilege': 4294967295, '_is_login': True}
        self._s_id = "tp_1583808860_5c0a3c718114f429"
        self.set_session('user', _user, 12 * 60 * 60)
        cookies = {"_sid": self._s_id, "username": "admin"}
        resp = requests.post(url, data=data, json=json, cookies=cookies)
        return resp.json()

    def query(self, table, field, filter):
        return plugin.query_one(table, [field], filter).get(field, 0)


class UpdateHostHandler(TPBasePluginHandler):
    def generate_assets_num(self, ip, os_type):
        data = ip.split('.')
        data = ['%03d' % int(i) for i in data]
        assets_num = ''.join(data)
        if os_type == 1:
            assets_num = 'W' + assets_num
        elif os_type == 2:
            assets_num = 'L' + assets_num
        return assets_num

    @tornado.gen.coroutine
    def post(self):
        props = self.get_payload()

        if not self.check_ip(props):
            self.finish_json(1001, "IP不符合规范")
            return

        os_type, host_id, app_id, status = self.param_get(props, ['os_type', 'host_id', 'app_id', 'status'], 0)
        ip, username, password, name, desc = self.param_get(props, ['ip', 'username', 'password', 'name', 'desc', ], '')

        # “res”:1,“ec”:”0”,“msg”:”成功”
        if not os_type or not ip or not username or not password:
            self.finish_json(1001, "缺少必要参数异常")
            return

        assets_num = self.generate_assets_num(ip, os_type)

        # 添加主机
        url = "http://127.0.0.1:7190/asset/update-host"

        args = {"args": json.dumps(
            {"id": -1, "os_type": os_type, "ip": ip, "router_ip": "", "router_port": 0, "name": name, "cid": assets_num,
             "desc": desc}).encode()}

        # 带信息带先插入
        resp = yield self.request_api(url, args)
        # {"code": 0, "message": "", "data": 7}
        if resp.get("code") == 8:
            self.finish_json(1003, "已存在主机，不可重复添加")
            return
        elif resp.get("code") != 0:
            self.finish_json(1003, "添加主机异常")
            return

        host_id = resp.get("data")

        url = "http://127.0.0.1:7190/asset/update-account"

        args = {"args": json.dumps({"host_id": host_id, "acc_id": -1,
                                    "param": {"host_ip": ip, "router_ip": "", "router_port": 0,
                                              "protocol": 1, "port": 3389, "auth_type": 1, "username": username,
                                              "password": password, "pri_key": "", "username_prompt": "",
                                              "password_prompt": ""}}).encode()}

        # {"code": 0, "message": "", "data": 5}
        # 带信息带先插入
        resp = yield self.request_api(url, args)

        if resp.get("code") != 0:
            self.finish_json(1004, "添加服务器账户异常")
            return

        # acc_id = resp.get("data")

        args = {"host_id": host_id, "name": name, "ip": ip, "remark": desc, "username": username,
                "password": "", "assets_num": assets_num, "os_type": os_type, "status": status}
        err, info = plugin.add_remote_host(self, args)

        if err == TPE_OK:
            self.finish_json(0, "成功")
        else:
            self.finish_json(1002, "记录主机信息失败")

    @tornado.gen.coroutine
    def put(self):
        props = self.get_payload()

        if not self.check_ip(props):
            self.finish_json(1001, "IP不符合规范")
            return

        os_type, host_id, status = self.param_get(props, ['os_type', 'host_id', 'status'], 0)
        ip, username, password, name, desc = self.param_get(props, ['ip', 'username', 'password', 'name', 'desc', ], '')

        # “res”:1,“ec”:”0”,“msg”:”成功”
        if not os_type or not ip or not username:
            self.finish_json(1001, "缺少必要参数异常")
            return

        assets_num = self.generate_assets_num(ip, os_type)

        url = "http://127.0.0.1:7190/asset/update-host"

        args = {"args": json.dumps(
            {"id": host_id, "os_type": os_type, "ip": ip, "router_ip": "", "router_port": 0, "name": name,
             "cid": assets_num, "desc": desc}).encode()}

        # 带信息带先插入
        resp = yield self.request_api(url, args)
        # {"code": 0, "message": "", "data": 7}
        if resp.get("code") == 8:
            self.finish_json(1003, "已存在主机，不可重复添加")
            return
        elif resp.get("code") != 0:
            self.finish_json(1003, "修改主机异常")
            return

        acc_id = plugin.query("acc", ["id"], {"host_id": host_id, "username": username})

        if not acc_id:
            self.finish_json(1011, "未发现该服务器信息")
            return

        acc_id = acc_id[0].get("id", 0)

        url = "http://127.0.0.1:7190/asset/update-account"

        args = {"args": json.dumps({"host_id": host_id, "acc_id": acc_id,
                                    "param": {"host_ip": ip, "router_ip": "", "router_port": 0,
                                              "protocol": 1, "port": 3389, "auth_type": 1, "username": username,
                                              "password": password, "pri_key": "", "username_prompt": "",
                                              "password_prompt": ""}}).encode()}

        # {"code": 0, "message": "", "data": 5}
        # 带信息带先插入
        resp = yield self.request_api(url, args)

        if resp.get("code") != 0:
            self.finish_json(1004, "添加服务器账户异常")
            return

        acc_id = resp.get("data")

        args = {"os_type": os_type, "ip": ip, "username": username, "password": "", "name": name, "remark": desc,
                "id": host_id, "assets_num": "", "status": status}

        # 调用更新接口
        err = plugin.update_host(self, args)
        # 已经存在数据库
        self.finish_json(0, "成功")


class GetHostListHandler(TPBasePluginHandler):
    async def post(self):
        props = self.get_payload()

        os_type, status, page_index, page_size = self.param_get(props, ['os_type', 'status', 'pageIndex', 'pageSize'],
                                                                0)
        ip, search = self.param_get(props, ['ip', 'search', ], '')

        sql_limit = dict()
        sql_limit['page_index'] = page_index - 1 if page_index - 1 > 0 else 0
        sql_limit['per_page'] = page_size

        err, total_count, page_index, row_data = \
            plugin.get_host_list(sql_limit, os_type, ip, search, status)
        ret = dict()
        ret['page_index'] = page_index
        ret['total'] = total_count
        ret['data'] = row_data

        for item in row_data:
            item['desc'] = item.pop("remark", "")
            item['host_id'] = item.pop("id", 0)
            # 绑定详情
            item['bind'] = plugin.get_bind_info(item['host_id'])

        self.finish_json(0, data=row_data)


class GetHostInfoHandler(TPBasePluginHandler):
    async def post(self):
        props = self.get_payload()
        host_id = props.get("host_id") or 0
        mch_no = props.get("mch_no") or ""

        err, host_info = plugin.get_account_info(host_id=host_id, mch_no=mch_no)
        if err != TPE_OK:
            if mch_no and not host_id:
                # 商家平台 通过商户请求
                self.finish_json(0, data=[])
            else:
                self.finish_json("1004", msg="设备信息获取异常")
            return

        host_info['desc'] = host_info.pop("remark", "")
        host_info['host_id'] = host_info.pop("id", 0)

        self.finish_json(0, data=[host_info])


class GetSessionInfoHandler(TPBasePluginHandler):
    async def post(self):
        props = self.get_payload()

        if not self.check_ip(props):
            self.finish_json(1001, "IP不符合规范")
            return

        host_id = props.get("host_id") or 0
        mch_no = props.get("mch_no") or ""

        ip = props.get("ip") or ""
        username = props.get("username") or ""
        password = props.get("password") or ""

        if not host_id and not mch_no and not (ip and username and password):
            self.finish_json(1001, "缺少必要参数异常")
            return

        if ip and username and password:
            data = {"ip": ip, "os_type": 1, "username": username, "password": password}
            url = "http://127.0.0.1:7190/plugin/update_host"
            resp = await self.request_api(url, json=data)

        err, info, host_id = plugin.get_session_info(host_id, mch_no, ip)
        url = "http://127.0.0.1:7190/ops/get-session-id"

        args = {"args": json.dumps(
            {"mode": 2, "auth_id": "none", "acc_id": info, "host_id": host_id, "protocol_type": 1,
             "protocol_sub_type": 100, "rdp_width": 0, "rdp_height": 0, "rdp_console": False}).encode()}

        # 带信息带先插入
        resp = await self.request_api(url, args)

        if isinstance(resp, dict):
            ip = ip or plugin.get_host_ip(host_id)
            resp = {"teleport_ip": "172.30.10.104", "teleport_port": 52089, "remote_host_ip": ip,
                    "session_id": resp.get("data", {}).get("session_id"), "protocol_type": 1, "protocol_sub_type": 100,
                    "protocol_flag": resp.get("data", {}).get("protocol_flag"), "rdp_width": 0, "rdp_height": 0,
                    "rdp_console": False}
            resp = """http://localhost:50022/api/run/{}""".format(json.dumps(resp))

        self.finish_json(0, data=[resp])
        return


class BindPayAccountHandler(TPBasePluginHandler):

    async def add_members(self, biz_id, host_id):
        args = {"args": json.dumps(
            {"id": -1, "role": 2, "auth_type": 0, "username": str(biz_id), "surname": "", "email": "", "mobile": "",
             "qq": "", "wechat": "", "desc": ""}).encode()}

        url = 'http://127.0.0.1:7190/user/update-user'

        # biz 创建对象
        resp = await self.request_api(url, args)

        user_id = resp.get("data", 0) or self.query("user", "id", {"username": str(biz_id)})

        url = "http://172.30.10.104:7190/ops/policy/update"
        args = {"args": json.dumps({"id": -1, "name": str(biz_id), "desc": ""})}
        # 创建权限组
        resp = await self.request_api(url, args)

        policy_id = resp.get("data", 0) or self.query("ops_policy", "id", {"name": str(biz_id)})

        # 运维权限
        url = "http://172.30.10.104:7190/ops/policy/add-members"
        args = {"args": json.dumps(
            {"policy_id": policy_id, "type": 0, "rtype": 1, "members": [{"id": user_id, "name": str(biz_id)}]})}
        resp = await self.request_api(url, args)

        ip = self.query("host", 'ip', {"id": host_id})

        url = "http://172.30.10.104:7190/ops/policy/add-members"
        args = {"args": json.dumps(
            {"policy_id": policy_id, "type": 1, "rtype": 5, "members": [{"id": host_id, "name": ip}]})}
        resp = await self.request_api(url, args)

    def allot_ip(self, host_assigned, ip, username, password):
        return

    def update(self, id):
        pass

    async def post(self):
        props = self.get_payload()

        if not self.check_ip(props):
            self.finish_json(1001, "IP不符合规范")
            return

        comp_id, biz_id, host_assigned, host_id = \
            self.param_get(props, ['comp_id', 'biz_id', 'host_assigned', 'host_id'], 0)
        host_assigned = int(host_assigned)
        ip, username, host_password, account, password, mch_no \
            = self.param_get(props, ['ip', 'username', 'host_password', 'account', 'password', 'mch_no'], "")

        if not biz_id or not mch_no or not comp_id or not account or not password:
            self.finish_json(1001, "缺少必要参数异常：biz_id,mch_no")
            return

        # not host_id and not host_assigned and (not ip or not username or not host_password)
        if not host_id and not host_assigned and (not ip or not username or not host_password):
            self.finish_json(1001, "手动分配，缺少必要参数异常：ip,username,password")
            return

        if not host_id:
            if int(host_assigned) == 1:
                # 自动分配功能
                err, info = free_host()
                if err == TPE_OK:
                    host_id = info
                else:
                    self.finish_json(1010, "未发现空闲主机")
                    return
            else:
                data = {"ip": ip, "os_type": 1, "username": username, "password": host_password}
                url = "http://127.0.0.1:7190/plugin/update_host"
                resp = await self.request_api(url, json=data)
                host_id = resp.get("data", 0) or self.query("host", 'id', {"ip": ip})

        if not host_id:
            self.finish_json(1010, "无法找到对应主机信息")
            return

        args = props
        args['host_id'] = host_id

        id = self.query("remote_account_host_bind", "id", {"host_id": host_id, "mch_no": mch_no})

        password = des_encrypt(password)
        # xiugai
        if id:
            args = {"mch_no": mch_no, "comp_id": comp_id, "host_id": host_id, "host_assigned": host_assigned,
                    "account_source": 1, "account": account, "password": password, "login_status": 0, "mch_name": ""}
            err = plugin.update_account_host_bind(self, args)

            if err == TPE_FAILED:
                self.finish_json(1011, "不允许修改公司资质")
            elif err == TPE_NOT_EXISTS:
                self.finish_json(1012, "设备不存在")
            elif err == TPE_OK:
                self.finish_json(0)

            return

        args['password'] = password
        err, info = plugin.add_account_host_bind(self, args)

        if err == TPE_EXISTS:
            self.finish_json(1011, "绑定的支付宝账户已存在")
        elif err == TPE_FAILED:
            self.finish_json(1011, "不可跨资质绑定")
        elif err == TPE_NOT_EXISTS:
            self.finish_json(1011, "绑定设备不存在")
        else:
            self.finish_json(0)

        if err != TPE_OK:
            return

        await self.add_members(biz_id, host_id)

    async def put(self):
        props = self.get_payload()
        args = props

        if not self.check_ip(props):
            self.finish_json(1001, "IP不符合规范")
            return

        comp_id, biz_id, host_assigned, host_id = \
            self.param_get(props, ['comp_id', 'biz_id', 'host_assigned', 'host_id'], 0)

        ip, username, host_password, account, password, mch_no \
            = self.param_get(props, ['ip', 'username', 'host_password', 'account', 'password', 'mch_no'], "")

        host_id = host_id or self.query('remote_account_host_bind', 'host_id', {"mch_no": mch_no})
        if not host_id:
            self.finish_json(1010, "未发现对应主机信息")
            return

        args = {"mch_no": mch_no, "comp_id": comp_id, "host_id": host_id, "host_assigned": host_assigned,
                "account_source": 1, "account": account, "password": password, "login_status": 0, "mch_name": ""}
        err = plugin.update_account_host_bind(self, args)
        if err == TPE_FAILED:
            self.finish_json(1011, "不允许修改公司资质")
        elif err == TPE_NOT_EXISTS:
            self.finish_json(1012, "设备不存在")
        elif err == TPE_OK:
            self.finish_json(0)

        if err != TPE_OK:
            return


class AccountStatusHandler(TPBasePluginHandler):
    async def post(self):
        prop = self.get_payload()
        ip = self.request.remote_ip
        mch_no = prop.get("mch_no") or ""
        mch_name = prop.get("mch_name") or ""
        login_status = prop.get("login_status") or 0

        host_id = self.query("host", ["id"], {"ip": ip})

        if not host_id:
            self.finish_json(1011, "未发现该服务器信息")
            return

        plugin.update("tp_remote_account_host_bind", {"mch_name": mch_name, "login_status": login_status},
                      {"host_id": host_id, "mch_no": mch_no})

        self.finish_json(0)


class AccountInfoHandler(TPBasePluginHandler):
    def key(self, timestamp):
        key = '{}{}'.format(KEY, timestamp)
        return

    async def post(self):
        prop = self.get_payload()
        info = prop.get("info") or ""
        timestamp = prop.get("timestamp")

        if not timestamp:
            self.finish_json(1001, "传递参数错误")
            return

        # md5 (key + timestamp)[:8]
        key = md5('{}{}'.format(KEY, timestamp))[:8]
        info = des_descrypt(info, key)
        info = info.decode()
        # ip = en
        if info:
            host_id = self.query('host', 'id', {"ip": info})
            accounts = plugin.query('remote_account_host_bind', ['account', 'password'],
                                    {"account_source": 1, "host_id": host_id})
            info = {k: v for account in accounts for k, v in account.items()}
            timestamp = int(time.time())
            key = md5('{}{}'.format(KEY, timestamp))[:8]
            info = json.dumps(info)
            data = des_encrypt(info, key).decode()
            self.finish_json(0, data=[{"info": data, "timestamp": timestamp}])
        else:
            self.finish_json(1001, "传递参数错误")
