# -*- coding:utf-8 -*-
"""
@File    :   common_service
@Author  :   xuxingchen
@Version :   2.0
@Contact :   xuxingchen@sinochem.com
@Desc    :   业务逻辑服务层
"""
import json
import time
import traceback
import requests

import devices.meian_model as meian_data_entity
import devices.yunfu_model as yunfu_data_entity

import logger
from logger import Logger, speed_ms
from devices.meian_db import DeviceTable, RegisterTable, GatewayTable, HeartBeatTable, UserInfoTable, RecordTable
from devices.common_model import ErrorCode, UserData, UserInfo
from utils import generate_token, to_obj, datetime_to_timestamp, generate_time_token, encode_to_base64, \
    extract_building_unit, extract_number
from config import TIMEOUT_SECOND, SLEEP_TIME, BACKEND_URL, BACKEND_ID, BACKEND_SECRET, GATEWAY_CONFIG


class BaseService:
    def __init__(self):
        self.table_handler = None
        self.topic = None
        self.clients = None

    def set_meta(self, userdata: UserData):
        self.table_handler = userdata.table_handler
        self.topic = userdata.topic
        self.clients = userdata.clients

    def handle(self, **kwargs):
        """must be `override`"""
        pass


def get_owner_house_floor(user_id, project_code: str, device_position_desc: str) -> int:
    """根据用户ID获取用户房产信息，提取楼层信息并返回

    1. 该功能只支持业主使用，访客查询不到，默认返回
    """
    token_url = f"{BACKEND_URL}/v2/accesskey_auth"
    owner_info_url = f"{BACKEND_URL}/v3/realty-master-data/owners/{user_id}"
    house_info_url = f"{BACKEND_URL}/v3/realty-master-data/houses"

    # get token
    data = {'id': BACKEND_ID, 'secret': BACKEND_SECRET}
    token_response = requests.post(token_url, json=data)
    if token_response.status_code == 200:
        logger.Logger.debug(f"token 请求成功，响应内容：{token_response.json()}")
        token = token_response.json()["access_token"]
    else:
        logger.Logger.error(f"token 请求失败，状态码: {token_response.status_code}")
        raise Exception(f"token 请求失败，状态码: {token_response.status_code}")
    # get house ids
    headers = {
        "Accept": "application/json",
        "Access-Token": token,
        "Content-Type": "application/json;charset=UTF-8"
    }
    owner_info_response = requests.get(owner_info_url, headers=headers)
    if owner_info_response.status_code == 200:
        owner_info = owner_info_response.json()
        logger.Logger.debug(f"owner_info 请求成功，响应内容：{owner_info}")
        # 根据 project_code 获取 project_id
        if GATEWAY_CONFIG[project_code][2] == "" or owner_info["data"]["project_id"] == GATEWAY_CONFIG[project_code][2]:
            house_ids = owner_info["data"]["house_ids"]
        else:
            logger.Logger.error(f"project id 不匹配，"
                                f"预置：{GATEWAY_CONFIG[project_code][2]}, 用户信息所属：{owner_info['data']['project_id']}")
            raise Exception(f"project id 不匹配")
    elif owner_info_response.status_code == 404 and owner_info_response.json()["msg"] == "OWNER_NOT_EXISTS":
        logger.Logger.warn(f"owner_info 请求成功，但用户ID不存在，非住户")
        return 1
    else:
        logger.Logger.error(f"token 请求失败，状态码: {token_response.status_code}")
        raise Exception(f"token 请求失败，状态码: {token_response.status_code}")

    # 结合设备注册备注 获取楼层信息
    data = {
        "query": {
            "id": {"$in": house_ids}
        }
    }
    house_info_response = requests.post(house_info_url, json=data, headers=headers)
    if house_info_response.status_code == 200:
        logger.Logger.debug(f"house_info 请求成功，响应内容：{house_info_response.json()}")
        building_number, unit_number = extract_building_unit(device_position_desc)
        house_info = house_info_response.json()["data"]["list"]
        floor = 1
        for house_item in house_info:
            building_name = extract_number(house_item["building_name"]).zfill(2)
            unit_name = extract_number(house_item["unit_name"]).zfill(2)
            if building_name == building_number and unit_name == unit_number:
                floor = int(house_item["floor_name"])
                break
    else:
        logger.Logger.error(f"house_info 请求失败，状态码: {token_response.status_code}")
        raise Exception(f"house_info 请求失败，状态码: {token_response.status_code}")

    return floor


class Services:
    """业务逻辑入口"""

    class HeartBeatService(BaseService):
        def handle(self, msg_obj: meian_data_entity.HeartBeat):
            # Logger.debug("设备心跳已接收，正在执行上线 ...", log_path=None)
            last_time = HeartBeatTable.get_last_time(self.table_handler, msg_obj.device_id)
            # 若无心跳记录或者距离上次心跳记录时间达到5分钟
            if (last_time is None or
                    (time.time() - float(
                        HeartBeatTable.get_last_time(self.table_handler, msg_obj.device_id))) > (60. * 5.)):
                status = HeartBeatTable.update(self.table_handler, msg_obj, self.topic)
                if status:
                    # 执行上线操作
                    project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
                    if project_code:
                        gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
                        if gateway_id is None:
                            Logger.error("网关不存在")
                            return -1
                        aiot_id = DeviceTable.get_aiot_id(self.table_handler, msg_obj.device_id)
                        if aiot_id is None:
                            Logger.warn("设备未注册，不执行上线操作")
                            return -1
                        topic = f"/jmlink/{aiot_id}/comm/online"
                        topic_resp = f"/jmlink/{aiot_id}/comm/online_resp"
                        s = time.time()
                        # 获取客户端
                        client_1 = self.clients[gateway_id][0]
                        userdata = self.clients[gateway_id][1]
                        userdata.set_status_add("status", False)
                        userdata.set_status_add("start_timestamp", time.time())
                        try:
                            # 订阅回传
                            client_1.subscribe(topic_resp)
                            Logger.debug(f"subscribe topics: {topic_resp}")
                            # 发布事件
                            token = generate_token()
                            userdata.set_token(token)
                            online_json = json.dumps(yunfu_data_entity.Online(messageId=token).__dict__)
                            client_1.publish(topic, online_json)
                            while True:
                                if userdata.status["status"]:
                                    # 拿到结果后写入到数据库中更新数据
                                    if "code" in userdata.status["response"].keys():
                                        error_code = userdata.status["response"]["code"]
                                        if error_code == 0:
                                            Logger.info(f"设备 - {aiot_id} 完成上线")
                                        else:
                                            Logger.error(
                                                f"{topic_resp} 返回错误码: {error_code}, "
                                                f"{yunfu_data_entity.error_code[error_code]}")
                                    break
                                if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                                    Logger.debug(
                                        f"等待回复超时: {speed_ms(userdata.status['start_timestamp'])}ms")
                                    break
                                # Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
                                time.sleep(SLEEP_TIME)
                        finally:
                            # 属性复位
                            client_1.unsubscribe(topic_resp)
                            # Logger.debug(f"移除订阅: {topic_resp}")
                            userdata.set_token(None)
                            userdata.set_status_remove("response")
                            # Logger.debug(f"{speed_ms(s)}ms")
                else:
                    Logger.warn("上线请求非法，默认无操作")
            else:
                Logger.debug("未达到重新上线阈值，默认无操作")
                pass

    class RegisterService(BaseService):
        def handle(self, msg_obj: meian_data_entity.Register):
            Logger.debug("RegisterService Handle ...")
            status = RegisterTable.update(self.table_handler, msg_obj, self.topic)
            if status:
                if not DeviceTable.get_device_register_type(self.table_handler, msg_obj.device_id):
                    Logger.debug("Execute register ...")
                    project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
                    if project_code:
                        gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
                        if gateway_id is None:
                            Logger.error("网关不存在")
                            return -1
                        topic = f"/jmlink/{gateway_id}/comm/sub/register"
                        topic_resp = f"/jmlink/{gateway_id}/comm/sub/register_resp"
                        # 将连接信息(网关&网关secret)和回传信息发送给回传监听进程
                        s = time.time()
                        # 获取客户端
                        client_1 = self.clients[gateway_id][0]
                        userdata = self.clients[gateway_id][1]
                        userdata.set_status_add("status", False)
                        userdata.set_status_add("start_timestamp", time.time())
                        try:
                            # 订阅回传
                            client_1.subscribe(topic_resp)
                            Logger.debug(f"subscribe topics: {topic_resp}")
                            # 发布事件
                            token = generate_token()
                            userdata.set_token(token)
                            register_json = json.dumps(yunfu_data_entity.CommRegister(
                                messageId=token,
                                params=yunfu_data_entity.RegisterParam(
                                    deviceName=msg_obj.device_id,
                                    displayName=f"美安-{msg_obj.device_id}-{msg_obj.device_position_desc}"
                                ).__dict__
                            ).__dict__)
                            Logger.debug(register_json)
                            client_1.publish(topic, register_json)
                            while True:
                                if userdata.status["status"]:
                                    # 拿到结果后写入到数据库中更新数据
                                    if "data" in userdata.status["response"].keys():
                                        resp = userdata.status["response"]["data"][0]
                                        if resp["code"] == 0:
                                            DeviceTable.update_aiot_id(self.table_handler, resp["deviceName"],
                                                                       resp["deviceId"])
                                            Logger.info(f"设备 - {msg_obj.device_id} 完成注册")

                                            # 每次完成注册后刷新订阅信息
                                            Logger.debug(f"刷新订阅列表中 ...")
                                            sub_aiot_id_list = json.loads(
                                                GatewayTable.get_registered_sub_aiot_id(self.table_handler, gateway_id)[
                                                    0])
                                            current_topics = set(
                                                [f"/jmlink/{aiot_id}/tml/service/call" for aiot_id in sub_aiot_id_list])
                                            last_topics = set(userdata.topics)
                                            topics_to_subscribe = current_topics - last_topics
                                            topics_to_unsubscribe = last_topics - current_topics
                                            if topics_to_subscribe or topics_to_unsubscribe:
                                                # 订阅缺少的主题
                                                for topic in list(topics_to_subscribe):
                                                    client_1.subscribe(topic)
                                                    Logger.debug(f"新增订阅: {topic}")
                                                # 取消订阅多余的主题
                                                for topic in list(topics_to_unsubscribe):
                                                    client_1.unsubscribe(topic)
                                                    Logger.debug(f"移除订阅: {topic}")
                                            else:
                                                Logger.debug(f"订阅列表无变化")
                                            Logger.debug(f"订阅列表刷新完成 ✅")
                                        else:
                                            Logger.error(
                                                f"{topic_resp} 返回错误码: {resp['code']}, "
                                                f"{yunfu_data_entity.error_code[resp['code']]}")
                                    break
                                if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                                    Logger.debug(
                                        f"等待回复超时: {speed_ms(userdata.status['start_timestamp'])}ms")
                                    break
                                # Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
                                time.sleep(SLEEP_TIME)  # 实际运行中设为0.1，100ms检测一次
                        finally:
                            client_1.unsubscribe(topic_resp)
                            Logger.debug(f"移除订阅: {topic_resp}")
                            userdata.set_token(None)
                            userdata.set_status_remove("response")
                            Logger.debug(f"{speed_ms(s)}ms")
                    else:
                        Logger.error("网关配置表中不存在设备关联的项目信息")
                else:
                    Logger.debug("设备已注册过，默认无操作")

    class PushRtAccessRecordService(BaseService):
        def handle(self, msg_obj: meian_data_entity.PushRtAccessRecord):
            Logger.debug("Execute open_event ...")
            # 若存在通行记录，但人员信息表中不存在人员信息，则将通行记录本地保存留用
            if msg_obj.user_id.startswith("old-"):  # 历史遗留在设备中的人员id应当以old-开头进行插入
                try:
                    RecordTable.add(self.table_handler, msg_obj)
                    Logger.info("通行事件本地记录成功")
                    return 1
                except Exception as e:
                    Logger.error(f"{type(e).__name__}, {e}")
                    if logger.DEBUG:
                        traceback.print_exc()
            # 查询子设备ID
            aiot_id = DeviceTable.get_aiot_id(self.table_handler, msg_obj.device_id)
            if aiot_id is None:
                Logger.warn(f"Device - {aiot_id} is not registered")
                return -1
            topic = f"/jmlink/{aiot_id}/tml/event/post"
            topic_resp = f"/jmlink/{aiot_id}/tml/event/post_resp"
            project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
            if project_code:
                gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
                # 将连接信息(网关&网关secret)和回传信息发送给回传监听进程
                s = time.time()
                # 获取客户端
                client_1 = self.clients[gateway_id][0]
                userdata = self.clients[gateway_id][1]
                userdata.set_status_add("status", False)
                userdata.set_status_add("start_timestamp", time.time())
                # 订阅回传
                client_1.subscribe(topic_resp)
                Logger.debug(f"subscribe topics: {topic_resp}")
                try:
                    # 数据转换
                    open_event = yunfu_data_entity.OpenEvent()
                    open_event.datetime = str(datetime_to_timestamp(msg_obj.time))
                    open_event.type = yunfu_data_entity.OpenEventType.__dict__[msg_obj.access_mode.upper()].value
                    open_event.certificate_type = yunfu_data_entity.OpenEventCertificateType.__dict__[
                        msg_obj.access_mode.upper()].value
                    # 若是二维码通信，根据二维码获取用户信息，若是人脸通信，根据userid获取用户信息
                    if msg_obj.access_mode == "qrCode":
                        user = UserInfoTable.get_user_by_qrcode(self.table_handler, msg_obj.user_id, msg_obj.device_id)
                        open_event.code = user[0]
                        open_event.certificate = user[0]
                        name = user[1]
                    else:
                        name = UserInfoTable.get_name(self.table_handler, msg_obj.user_id, msg_obj.device_id)
                        open_event.code = msg_obj.user_id
                        open_event.certificate = msg_obj.user_id
                    open_event.name = name
                    open_event.check()
                    token = generate_token()
                    userdata.set_token(token)
                    event_json = json.dumps(yunfu_data_entity.EventPost(
                        messageId=token, eventCode="open_event",
                        params=open_event.__dict__
                    ).__dict__)
                    # 发布事件
                    client_1.publish(topic, event_json)
                    # 等待回传
                    while True:
                        if userdata.status["status"]:
                            if "code" in userdata.status["response"].keys():
                                if userdata.status["response"]["code"] != 0:
                                    error_code = userdata.status['response']['errorCode']
                                    Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
                                                 f"{meian_data_entity.error_code[error_code]}")
                                else:
                                    Logger.debug("通行事件推送成功")
                            break
                        if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                            Logger.error("等待回复超时")
                            break
                        # Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
                        time.sleep(SLEEP_TIME)
                finally:
                    # 属性复位
                    client_1.unsubscribe(topic_resp)
                    Logger.debug(f"移除订阅: {topic_resp}")
                    userdata.set_token(None)
                    userdata.set_status_remove("response")
                    Logger.debug(f"{speed_ms(s)}ms")

    class GetQrCodeService(BaseService):
        """获取访客二维码

        1. 将接受的用户信息记录追加或更新到本地DB
        2. 根据时间戳和访客id生成一个token字符串，作为唯一识别id
        3. 将生成的二维码下置给指定设备
        4. 将唯一识别id作为二维码字符串返回给平台
        """

        def handle(self, msg: dict):
            Logger.debug("Execute Get QrCode ...")
            code = ErrorCode.SUCCESS
            device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
            project_code = DeviceTable.get_project_code(self.table_handler, device_id)
            gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
            success_aiot_list = []
            aiot_ids = []
            qrcode = None
            try:
                # 根据时间戳和访客id生成一个token字符串，作为唯一识别id
                qrcode = generate_time_token(msg["params"]["user_id"])
                # 将生成的二维码下置给指定设备
                aiot_ids = json.loads(msg["params"]["device_ids"])
                s = time.time()
                for aiot_id in aiot_ids:  # 一般情况 aiot_ids 里应该只有一个值
                    # 查询对应aiot_id实际的美安设备id和订阅主题
                    device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
                    if device_id is None:
                        continue

                    # 将接受的用户信息记录追加或更新到本地DB
                    user_type = 0 if msg["serviceCode"] != "get_visitor_qrcode" else 1  # 业主0 访客1，身份类型在插入后就不会再被更新
                    device_position_desc = RegisterTable.get_device_position_desc(self.table_handler, device_id)
                    if user_type == 1 or device_position_desc is None:
                        floor = 1
                    else:
                        floor = get_owner_house_floor(msg["params"]["user_id"], project_code, device_position_desc)
                    user_info = UserInfo(
                        user_id=msg["params"]["user_id"],
                        device_id=device_id,
                        name=msg["params"]["name"],
                        user_type=user_type
                    )
                    UserInfoTable.update(self.table_handler, user_info)

                    topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
                    # 获取客户端
                    client_0 = self.clients["center"][0]
                    userdata_0 = self.clients["center"][1]
                    userdata_0.set_status_add("status", False)
                    userdata_0.set_status_add("start_timestamp", time.time())
                    try:
                        Logger.debug(f"subscribe topics: {topic_resp}")
                        # 构建消息体
                        token = generate_token()  # 正式运行应使用 generate_token()
                        userdata_0.set_token(token)
                        userdata_0.set_code("qrCodeDownState")
                        qrcode_json = json.dumps(meian_data_entity.QrCodeInfo(
                            dataType="qrCodeInfo",
                            deviceId=device_id,
                            token=token,
                            userId=qrcode,
                            qrCode=encode_to_base64(qrcode),
                            floor=floor
                        ).__dict__)
                        # 发布事件
                        client_0.publish(topic, qrcode_json)
                        # 等待回传
                        while True:
                            if userdata_0.status["status"]:
                                if "errorCode" in userdata_0.status["response"].keys():
                                    if userdata_0.status["response"]["errorCode"] != 0:
                                        error_code = userdata_0.status['response']['errorCode']
                                        Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
                                                     f"{meian_data_entity.error_code[error_code]}")
                                    else:
                                        Logger.info(f"设备 - {device_id} 二维码 - {qrcode} 完成下置")
                                        UserInfoTable.update_qrcode(self.table_handler,
                                                                    user_info.user_id, device_id, qrcode)
                                        success_aiot_list.append(aiot_id)
                                break
                            if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                                Logger.error("等待回复超时")
                                break
                            # Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
                            time.sleep(SLEEP_TIME)
                    finally:
                        userdata_0.set_token(None)
                        userdata_0.set_code(None)
                        userdata_0.set_status_remove("response")
                Logger.debug(f"{speed_ms(s)}ms")
            except Exception as e:
                Logger.error(f"{type(e).__name__}, {e}")
                if type(e).__name__ == "TypeError":
                    code = ErrorCode.INPUT_TYPE_ERROR
                else:
                    code = ErrorCode.UNKNOWN

            # 将唯一识别id作为二维码字符串返回给平台
            # 获取客户端
            client_1 = self.clients[gateway_id][0]
            userdata = self.clients[gateway_id][1]
            userdata.set_status_add("status", False)
            userdata.set_status_add("start_timestamp", time.time())
            try:
                if code == ErrorCode.SUCCESS:
                    if set(success_aiot_list) == set(aiot_ids):  # 全部完成下发
                        Logger.debug("指令完成下发")
                    elif success_aiot_list:  # 完成一部分下发
                        code = ErrorCode.PART_SUCCESS
                        Logger.warn(f"部分成功下发: {success_aiot_list}")
                        UserInfoTable.update_qrcode(self.table_handler, msg["params"]["user_id"], project_code, qrcode)
                    elif len(success_aiot_list) == 0:  # 一个都没下发
                        code = ErrorCode.FAILURE
                        Logger.error("无指令完成下发")
                aiot_topic = self.topic + "_resp"
                return_json = json.dumps(yunfu_data_entity.EventPostResp(
                    messageId=msg["messageId"],
                    requestTime=msg["time"],
                    serviceCode=msg["serviceCode"],
                    data=yunfu_data_entity.QrCodeResp(code=code.value, message=code.message, qrcode=qrcode).__dict__
                ).__dict__)
                client_1.publish(aiot_topic, return_json)
            finally:
                userdata.set_token(None)
                userdata.set_status_remove("response")

    class AddFaceService(BaseService):
        def handle(self, msg: dict):
            Logger.debug("Execute Add Face ...")
            code = ErrorCode.SUCCESS
            device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
            project_code = DeviceTable.get_project_code(self.table_handler, device_id)
            gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
            success_aiot_list = []
            aiot_ids = json.loads(msg["params"]["device_ids"])
            user_id = msg["params"]["user_id"]
            face_url = f"{msg['params']['face_url']}?x-oss-process=image/resize,m_pad,h_960,w_540,color_FFFFFF"
            try:
                # 将人脸下置给指定设备
                s = time.time()
                for aiot_id in aiot_ids:
                    # 查询对应aiot_id实际的美安设备id和订阅主题
                    device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
                    if device_id is None:
                        continue

                    # 将接受的用户信息记录追加或更新到本地DB
                    user_info = UserInfo(
                        user_id=user_id,
                        device_id=device_id,
                        name=msg["params"]["name"],
                        user_type=0
                    )
                    UserInfoTable.update(self.table_handler, user_info)

                    topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
                    # 获取客户端
                    client_0 = self.clients["center"][0]
                    userdata_0 = self.clients["center"][1]
                    userdata_0.set_status_add("status", False)
                    userdata_0.set_status_add("start_timestamp", time.time())
                    try:
                        Logger.debug(f"subscribe topics: {topic_resp}")
                        # 构建消息体
                        token = generate_token()  # 正式运行应使用 generate_token()
                        userdata_0.set_token(token)
                        userdata_0.set_code("faceDownState")
                        device_position_desc = RegisterTable.get_device_position_desc(self.table_handler, device_id)
                        if device_position_desc is None:
                            floor = 1
                        else:
                            floor = get_owner_house_floor(user_id, project_code, device_position_desc)
                        face_json = json.dumps(meian_data_entity.FaceInfo(
                            dataType="faceInfo",
                            deviceId=device_id,
                            token=token,
                            userId=user_id,
                            faceUrl=face_url,
                            floor=floor
                        ).__dict__)
                        # 发布事件
                        client_0.publish(topic, face_json)
                        Logger.debug(f"{face_json} ==> {topic}")
                        # 等待回传
                        while True:
                            if userdata_0.status["status"]:
                                if "errorCode" in userdata_0.status["response"].keys():
                                    if userdata_0.status["response"]["errorCode"] != 0:
                                        error_code = userdata_0.status['response']['errorCode']
                                        Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
                                                     f"{meian_data_entity.error_code[error_code]}")
                                    else:
                                        Logger.info(f"设备 - {device_id} 用户({user_id}) 人脸完成下置")
                                        UserInfoTable.update_face_url(self.table_handler, user_id, device_id, face_url)
                                        success_aiot_list.append(aiot_id)
                                break
                            if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                                Logger.error("等待回复超时")
                                break
                            # Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
                            time.sleep(SLEEP_TIME)
                    finally:
                        userdata_0.set_token(None)
                        userdata_0.set_code(None)
                        userdata_0.set_status_remove("response")
                Logger.debug(f"{speed_ms(s)}ms")
            except Exception as e:
                Logger.error(f"{type(e).__name__}, {e}")
                if type(e).__name__ == "TypeError":
                    code = ErrorCode.INPUT_TYPE_ERROR
                else:
                    code = ErrorCode.UNKNOWN

            # 将下置结果返回给平台
            # 获取客户端
            client_1 = self.clients[gateway_id][0]
            userdata = self.clients[gateway_id][1]
            userdata.set_status_add("status", False)
            userdata.set_status_add("start_timestamp", time.time())
            try:
                if code == ErrorCode.SUCCESS:
                    if set(success_aiot_list) == set(aiot_ids):  # 全部完成下发
                        Logger.info("指令完成下发")
                    elif success_aiot_list:  # 完成一部分下发
                        code = ErrorCode.PART_SUCCESS
                        Logger.warn(f"部分成功下发: {success_aiot_list}")
                    else:  # 一个都没下发
                        code = ErrorCode.FAILURE
                        Logger.error("无指令完成下发")
                aiot_topic = self.topic + "_resp"
                return_json = json.dumps(yunfu_data_entity.EventPostResp(
                    messageId=msg["messageId"],
                    requestTime=msg["time"],
                    serviceCode=msg["serviceCode"],
                    data=yunfu_data_entity.AddFaceResp(code=code.value, message=code.message).__dict__
                ).__dict__)
                client_1.publish(aiot_topic, return_json)
            finally:
                userdata.set_token(None)
                userdata.set_status_remove("response")

    class DeleteFaceService(BaseService):
        def handle(self, msg: dict):
            Logger.debug("Execute Delete Face ...")
            code = ErrorCode.SUCCESS
            device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
            project_code = DeviceTable.get_project_code(self.table_handler, device_id)
            gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
            success_aiot_list = []
            aiot_ids = json.loads(msg["params"]["device_ids"])
            user_id = msg["params"]["user_id"]
            try:
                s = time.time()
                # 将人脸删除指令下发指定设备
                for aiot_id in aiot_ids:
                    # 查询对应aiot_id实际的美安设备id和订阅主题
                    device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
                    if device_id is None:
                        continue
                    # 本地DB判断人脸信息是否存在
                    if UserInfoTable.exists_face_url(self.table_handler, user_id, device_id):
                        topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
                        # 获取客户端
                        client_0 = self.clients["center"][0]
                        userdata_0 = self.clients["center"][1]
                        userdata_0.set_status_add("status", False)
                        userdata_0.set_status_add("start_timestamp", time.time())
                        try:
                            Logger.debug(f"subscribe topics: {topic_resp}")
                            # 构建消息体
                            token = generate_token()  # 正式运行应使用 generate_token()
                            userdata_0.set_token(token)
                            userdata_0.set_code("deleteUserState")
                            face_json = json.dumps(meian_data_entity.DeleteUser(
                                dataType="deleteUser",
                                deviceId=device_id,
                                token=token,
                                userId=user_id
                            ).__dict__)
                            # 发布事件
                            client_0.publish(topic, face_json)
                            # 等待回传
                            while True:
                                if userdata_0.status["status"]:
                                    if "errorCode" in userdata_0.status["response"].keys():
                                        if userdata_0.status["response"]["errorCode"] != 0:
                                            error_code = userdata_0.status['response']['errorCode']
                                            Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
                                                         f"{meian_data_entity.error_code[error_code]}")
                                        else:
                                            Logger.info(f"设备 - {device_id} 移除用户({user_id})人脸")
                                            UserInfoTable.update_face_url(self.table_handler, user_id, device_id, None)
                                            success_aiot_list.append(aiot_id)
                                    break
                                if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND:  # 超时跳出
                                    Logger.error("等待回复超时")
                                    break
                                # Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
                                time.sleep(SLEEP_TIME)
                        finally:
                            userdata_0.set_token(None)
                            userdata_0.set_code(None)
                            userdata_0.set_status_remove("response")
                    else:
                        code = ErrorCode.NEVER_POST_FACE
                        Logger.warn(f"本地数据库中未发现下发过人脸信息，默认无操作")
                Logger.debug(f"{speed_ms(s)}ms")
            except Exception as e:
                Logger.error(f"{type(e).__name__}, {e}")
                if type(e).__name__ == "TypeError":
                    code = ErrorCode.INPUT_TYPE_ERROR
                else:
                    code = ErrorCode.UNKNOWN

            # 将下置结果返回给平台
            # 获取客户端
            client_1 = self.clients[gateway_id][0]
            userdata = self.clients[gateway_id][1]
            userdata.set_status_add("status", False)
            userdata.set_status_add("start_timestamp", time.time())
            try:
                if code == ErrorCode.SUCCESS:
                    if set(success_aiot_list) == set(aiot_ids):  # 全部完成下发
                        Logger.info("指令完成下发")
                    elif success_aiot_list:  # 完成一部分下发
                        code = ErrorCode.PART_SUCCESS
                        Logger.warn(f"部分成功下发: {success_aiot_list}")
                    else:  # 一个都没下发
                        code = ErrorCode.FAILURE
                        Logger.error("无指令完成下发")
                aiot_topic = self.topic + "_resp"
                return_json = json.dumps(yunfu_data_entity.EventPostResp(
                    messageId=msg["messageId"],
                    requestTime=msg["time"],
                    serviceCode=msg["serviceCode"],
                    data=yunfu_data_entity.DelFaceResp(code=code.value, message=code.message).__dict__
                ).__dict__)
                client_1.publish(aiot_topic, return_json)
            finally:
                userdata.set_token(None)
                userdata.set_status_remove("response")


def auto_service(msg: dict, userdata: UserData):
    """跟据dataType自动化加载不同的服务层"""
    if "dataType" in msg.keys():
        entity_name = msg["dataType"][0].upper() + msg["dataType"][1:]
        if entity_name in meian_data_entity.__dict__.keys():
            Logger.debug(f"Target service name: {entity_name}")
            # 转为服务层数据实体
            entity_type = meian_data_entity.__dict__[entity_name]
            msg_obj = to_obj(msg, entity_type)
            # 构建服务层实体并进行调用
            service_name = entity_name + "Service"
            if service_name in Services.__dict__.keys():
                servicer = Services.__dict__[service_name]()
                servicer.set_meta(userdata)
                servicer.handle(msg_obj)
    elif "serviceCode" in msg.keys() and "params" in msg.keys():  # 校验是aiot平台服务调用下发
        service_map = {
            "get_owner_qrcode": "GetQrCodeService",
            "get_visitor_qrcode": "GetQrCodeService",
            "add_face": "AddFaceService",
            "del_face": "DeleteFaceService"
        }
        # 构建服务层实体并进行调用
        if msg["serviceCode"] in service_map.keys():
            service_name = service_map[msg["serviceCode"]]
            servicer = Services.__dict__[service_name]()
            servicer.set_meta(userdata)
            servicer.handle(msg)
    else:
        pass
