# coding=utf-8
from binascii import b2a_hex, a2b_hex
from datetime import datetime

from utils import crc_modbus, signed_int_value, getLogger, signed_int_hex

logger = getLogger(__name__, filename="dtsy1352_4g.log")


class AcrelDTSY13524G:
    PAYMENT_COMMAND_TABLE = "ld_billing_pay_command"
    PAYMENT_ORDER_TABLE = "ld_billing_pay"
    CONTROL_COMMAND_TABLE = "ld_billing_device_command"
    CONTROL_RECORD_TABLE = "ld_billing_device_command_record"
    BALANCE_TABLE = "ld_device_prepaid_month"

    def initial(self, data, protocol, reactor):
        self.protocol = protocol
        self.factory = protocol.factory
        self.reactor = reactor
        self.parse(data)

    def parse(self, data):
        self.data = b2a_hex(data).decode()
        crc_received = self.data[-8:-4].lower()
        crc = crc_modbus(self.data[4:-8], left_high=False).lower()
        assert crc == crc_received, "DTSY1352-4G raw data crc verify failed"
        self.operation = self.data[4:6]

    def run_operations(self):
        eval('self.event_{}'.format(self.operation))()

    def event_84(self):
        """解析设备注册包，并回复仪表"""
        meter_id = a2b_hex(self.data[6:46]).decode().replace("\x00", "")
        self.factory.clients_content[self.protocol.clientID]["building_id"] = "DTSY1352"
        self.factory.clients_content[self.protocol.clientID]["gateway_id"] = "DTSY1352"
        self.factory.clients_content[self.protocol.clientID]["meter_id"] = meter_id

        response = "7b7b84bf237d7d"
        self.protocol.write(a2b_hex(response))

        # 充值轮巡
        self.reactor.callLater(5,
                               self.protocol.loop_task,
                               func=self.check_and_recharge,
                               tm=60)
        # 强控轮巡
        self.reactor.callLater(35,
                               self.protocol.loop_task,
                               func=self.check_control_command,
                               tm=60)

    def event_93(self):
        """仪表校时包"""
        now = datetime.now()
        now_str = now.strftime("%Y%m%d%H%M%S")
        year = hex(int(now_str[2:4]))[2:].zfill(2)
        month = hex(int(now_str[4:6]))[2:].zfill(2)
        day = hex(int(now_str[6:8]))[2:].zfill(2)
        weekday = hex(now.weekday()+1)[2:].zfill(2)
        hour = hex(int(now_str[8:10]))[2:].zfill(2)
        minute = hex(int(now_str[10:12]))[2:].zfill(2)
        second = hex(int(now_str[12:14]))[2:].zfill(2)

        response = "93" + year + month + day + weekday + hour + minute + second
        crc = crc_modbus(response, left_high=False)
        response = "7b7b" + response + crc + "7d7d"
        self.protocol.write(a2b_hex(response))

    def event_94(self):
        """心跳包"""
        pass

    def event_91(self):
        """解析电能包，保存至数据库，并回复仪表"""
        response = "7b7b917eec7d7d"
        self.protocol.write(a2b_hex(response))
        self.parse_and_save_power()

    def parse_and_save_power(self):
        body = self.data[60:262]
        crc_received = body[-4:].upper()
        crc = crc_modbus(body[:-4], left_high=False)
        assert crc == crc_received, "DTSY1352-4G energy data crc verify failed"

        id_ = body[6:10]
        if int(id_, base=16) in [5, 7]:  # DTSY1352-4G/DDSY1352-4G
            pt = int(body[82:86], base=16)  # 电压变比
            ct = int(body[86:90], base=16)  # 电流变比
            ua = int(body[10:14], base=16) * 0.1 * pt  # A相电压
            ub = int(body[14:18], base=16) * 0.1 * pt  # B相电压
            uc = int(body[18:22], base=16) * 0.1 * pt  # C相电压
            ia = int(body[22:26], base=16) * 0.01 * ct  # A相电流
            ib = int(body[26:30], base=16) * 0.01 * ct  # B相电流
            ic = int(body[30:34], base=16) * 0.01 * ct  # C相电流
            pa = signed_int_value(body[34:38], bit=16) * 0.001 * pt * ct  # A相有功功率
            pb = signed_int_value(body[38:42], bit=16) * 0.001 * pt * ct  # B相有功功率
            pc = signed_int_value(body[42:46], bit=16) * 0.001 * pt * ct  # C相有功功率
            p = signed_int_value(body[46:50], bit=16) * 0.001 * pt * ct  # 总有功功率
            qa = signed_int_value(body[50:54], bit=16) * 0.001 * pt * ct  # A相无功功率
            qb = signed_int_value(body[54:58], bit=16) * 0.001 * pt * ct  # B相无功功率
            qc = signed_int_value(body[58:62], bit=16) * 0.001 * pt * ct  # C相无功功率
            q = signed_int_value(body[62:66], bit=16) * 0.001 * pt * ct  # 总无功功率
            pfa = signed_int_value(body[66:70], bit=16) * 0.001  # A相功率因数
            pfb = signed_int_value(body[70:74], bit=16) * 0.001  # B相功率因数
            pfc = signed_int_value(body[74:78], bit=16) * 0.001  # C相功率因数
            pf = signed_int_value(body[78:82], bit=16) * 0.001  # 总功率因数
            epi = int(body[90:98], base=16) * 0.01 * pt * ct  # 正向有功电能
            epi_j = int(body[98:106], base=16) * 0.01 * pt * ct  # 正向有功电能 尖
            epi_f = int(body[106:114], base=16) * 0.01 * pt * ct  # 正向有功电能 峰
            epi_p = int(body[114:122], base=16) * 0.01 * pt * ct  # 正向有功电能 平
            epi_g = int(body[122:130], base=16) * 0.01 * pt * ct  # 正向有功电能 谷
            balance = signed_int_value(body[130:138], bit=32) * 0.01  # 余额
            buy_time = int(body[138:142], base=16)
            control_time = int(body[142:146], base=16)
            allow_time = int(body[146:150], base=16)
            status1 = bin(int(body[150:154], base=16))[2:].zfill(16)
            status2 = bin(int(body[154:158], base=16))[2:].zfill(16)
            control = status1[-4]
            if control == "1":
                switch = "1" if int(status1[-3:]) == 0 else "0"
            else:
                switch = "1" if float(balance) <= 0 else "0"

            year = 2000 + int(body[166:168], base=16)  # 年
            month = int(body[168:170], base=16)  # 月
            day = int(body[170:172], base=16)  # 日
            week = int(body[172:174], base=16)  # 星期
            hour = int(body[174:176], base=16)  # 时
            minute = int(body[176:178], base=16)  # 分
            second = int(body[178:180], base=16)  # 秒

            # 数据库存储
            time = datetime(year=year,
                            month=month,
                            day=day,
                            hour=hour,
                            minute=minute,
                            second=second)
            building_code = self.factory.clients_content[self.protocol.clientID]["building_id"]
            gateway_code = self.factory.clients_content[self.protocol.clientID]["gateway_id"]
            meter_code = self.factory.clients_content[self.protocol.clientID]["meter_id"]

            if int(id_, base=16) == 5:  # DTSY1352-4G
                db_data = {'device_imei': '--'.join([building_code, gateway_code, meter_code]),
                           'building_code': building_code,
                           'gateway_code': gateway_code,
                           'meter_code': meter_code,
                           'day': time.strftime("%Y-%m-%d"),
                           'hour': time.strftime("%H"),
                           'device_content': '',
                           'a_dy': ua,
                           'b_dy': ub,
                           'c_dy': uc,
                           'a_dl': ia,
                           'b_dl': ib,
                           'c_dl': ic,
                           'a_yggl': pa,
                           'b_yggl': pb,
                           'c_yggl': pc,
                           'total_yggl': p,
                           'a_wggl': qa,
                           'b_wggl': qb,
                           'c_wggl': qc,
                           'total_wggl': q,
                           'a_glys': pfa,
                           'b_glys': pfb,
                           'c_glys': pfc,
                           'total_glys': pf,
                           'epi': epi,
                           'epi_j': epi_j,
                           'epi_f': epi_f,
                           'epi_p': epi_p,
                           'epi_g': epi_g,
                           'create_date': time.strftime("%Y-%m-%d %H:%M:%S"),
                           'create_by': 'cc',
                           'update_date': time.strftime("%Y-%m-%d %H:%M:%S"),
                           'update_by': 'cc',
                           'extend_s1': ",".join([control, switch, "1", str(round(balance, 2))]),
                           }
            elif int(id_, base=16) == 7:  # DDSY1352-4G
                db_data = {'device_imei': '--'.join([building_code, gateway_code, meter_code]),
                           'building_code': building_code,
                           'gateway_code': gateway_code,
                           'meter_code': meter_code,
                           'day': time.strftime("%Y-%m-%d"),
                           'hour': time.strftime("%H"),
                           'device_content': '',
                           'a_dy': ua,
                           'a_dl': ia,
                           'total_yggl': p,
                           'total_wggl': q,
                           'total_glys': pf,
                           'epi': epi,
                           'epi_j': epi_j,
                           'epi_f': epi_f,
                           'epi_p': epi_p,
                           'epi_g': epi_g,
                           'create_date': time.strftime("%Y-%m-%d %H:%M:%S"),
                           'create_by': 'cc',
                           'update_date': time.strftime("%Y-%m-%d %H:%M:%S"),
                           'update_by': 'cc',
                           'extend_s1': ",".join([control, switch, "1", str(round(balance, 2))]),
                           }
            else:
                db_data = {}

            # 生成sql语句并插入数据库
            column = []
            content = []
            for k, v in db_data.items():
                column.append('`%s`' % k)
                if isinstance(v, float):
                    content.append("'%s'" % str(round(v, 2)))
                else:
                    content.append("'%s'" % str(v))
            table = 'ld_device_data_%s' % time.strftime("%Y%m")
            col = "(" + ','.join(column) + ")"
            val = "(" + ','.join(content) + ")"
            sql = "INSERT INTO {} {} VALUES {} ".format(table, col, val)
            self.protocol.db.sql(sql)

    def responses_ack(self, status=False, add_header=False):
        pass

    def check_and_recharge(self):
        building_id = self.factory.clients_content[self.protocol.clientID]["building_id"]
        gateway_id = self.factory.clients_content[self.protocol.clientID]["gateway_id"]
        meter_id = self.factory.clients_content[self.protocol.clientID]["meter_id"]
        command_sql = """
        SELECT serial_number AS order_id, meter_id, recharge_amount AS payment 
        FROM {table} 
        WHERE building_id = '{building_id}' AND gateway_id = '{gateway_id}' AND meter_id = '{meter_id}'
        ORDER BY create_date
        LIMIT 1
        """.format(table=self.PAYMENT_COMMAND_TABLE,
                   building_id=building_id,
                   gateway_id=gateway_id,
                   meter_id=meter_id)
        self.protocol.payment_db.fetch(command_sql).addCallback(self.do_recharge)

    def do_recharge(self, data):
        if len(data) == 0:
            logger.debug("{} {} NO RECHARGE COMMAND...".format(self.factory.clients_content[self.protocol.clientID]["building_id"],
                                                               self.factory.clients_content[self.protocol.clientID]["gateway_id"],
                                                               self.factory.clients_content[self.protocol.clientID]["meter_id"]))
        else:
            kw = data[0]
            self.factory.clients_content[self.protocol.clientID]["order_id"] = kw["order_id"]
            self.factory.clients_content[self.protocol.clientID]["meter_id"] = kw["meter_id"]
            self.factory.clients_content[self.protocol.clientID]["payment"] = float(kw["payment"])
            self.factory.clients_content[self.protocol.clientID]["recharge_msg"] = self.recharge_message(**kw)
            self.factory.clients_content[self.protocol.clientID]["recharge_status"] = 0
            self.factory.clients_content[self.protocol.clientID]["before_collect"] = 1
            self.factory.clients_content[self.protocol.clientID]["after_collect"] = 1
            self.send_data_collect_msg()

    # 集抄指令
    def send_data_collect_msg(self):
        msg = self.data_collect_message()
        self.protocol.write(msg)
        self.reactor.callLater(5, self.check_recharge_status)
        if self.factory.clients_content[self.protocol.clientID]["recharge_status"] == 0:
            self.factory.clients_content[self.protocol.clientID]["recharge_status"] = 1
        elif self.factory.clients_content[self.protocol.clientID]["recharge_status"] == 2:
            self.factory.clients_content[self.protocol.clientID]["recharge_status"] = 3

    def data_collect_message(self):
        """集抄指令"""
        meter_id = self.factory.clients_content[self.protocol.clientID]["meter_id"]
        addr = "00"  # 仪表地址
        command = "%s0301000007" % addr
        msg = self.create_pass_through_msg(meter_id, command)
        return msg

    def check_recharge_status(self):
        recharge_status = self.factory.clients_content[self.protocol.clientID]["recharge_status"]
        before = self.factory.clients_content[self.protocol.clientID]["before_collect"]
        after = self.factory.clients_content[self.protocol.clientID]["after_collect"]
        if recharge_status == 1:
            if before < 3:
                self.send_data_collect_msg()
                self.factory.clients_content[self.protocol.clientID]["before_collect"] += 1
            else:
                order_id = self.factory.clients_content[self.protocol.clientID]["order_id"]  # 充值订单号
                # 下发失败，订单状态改为6下发失败
                update_sql = """
                UPDATE {table} SET status='6', update_date='{update_time}', update_by='cc' WHERE serial_number='{order_id}'
                """.format(table=self.PAYMENT_ORDER_TABLE, order_id=order_id,
                           update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                self.protocol.payment_db.sql(update_sql)
                logger.debug(order_id + " COMMAND SENT FAILED!")
                # 删除command表对应的订单号
                del_sql = "DELETE FROM {table} WHERE serial_number='{order_id}'".format(table=self.PAYMENT_COMMAND_TABLE, order_id=order_id)
                self.protocol.payment_db.sql(del_sql)
                logger.debug(order_id + " COMMAND CLEANED!")

        elif recharge_status == 3:
            if after < 3:
                self.send_data_collect_msg()
                self.factory.clients_content[self.protocol.clientID]["after_collect"] += 1
            else:
                order_id = self.factory.clients_content[self.protocol.clientID]["order_id"]  # 充值订单号
                # 未收到充值后集抄回复，订单状态改为8充值异常
                update_sql = """
                UPDATE {table} SET status='8', update_date='{update_time}', update_by='cc' WHERE serial_number='{order_id}'
                """.format(table=self.PAYMENT_ORDER_TABLE, order_id=order_id,
                           update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                self.protocol.payment_db.sql(update_sql)
                logger.debug(order_id + " BALANCE CHECK FAILED!")

    # 充值指令
    def send_recharge_msg(self):
        order_id = self.factory.clients_content[self.protocol.clientID]["order_id"]  # 充值订单号
        msg = self.factory.clients_content[self.protocol.clientID]["recharge_msg"]  # 充值透传内容
        # 下发透传指令
        self.protocol.write(msg)
        # 下发完成，订单状态改为5下发成功
        update_sql = """
        UPDATE {table} SET status='5', update_date='{update_time}', update_by='cc' WHERE serial_number='{order_id}'
        """.format(table=self.PAYMENT_ORDER_TABLE, order_id=order_id, update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
        self.protocol.payment_db.sql(update_sql)
        logger.debug(order_id + " RECHARGE COMMAND ASSIGNED!")
        # 删除command表对应的订单号
        del_sql = "DELETE FROM {table} WHERE serial_number='{order_id}'".format(table=self.PAYMENT_COMMAND_TABLE, order_id=order_id)
        self.protocol.payment_db.sql(del_sql)
        logger.debug(order_id + " RECHARGE COMMAND CLEANED!")

    def recharge_message(self, **kwarg):
        """充值透传指令"""
        meter_id = kwarg["meter_id"]  # 电表id
        payment = float(kwarg["payment"])  # 充值金额
        addr = "00"  # 仪表地址
        command = "{addr}10004C000204{payment}".format(addr=addr,
                                                       payment=signed_int_hex(int(payment * 100)))  # 充值指令
        msg = self.create_pass_through_msg(meter_id, command)
        return msg

    # 强控指令
    def check_control_command(self):
        building_id = self.factory.clients_content[self.protocol.clientID]["building_id"]
        gateway_id = self.factory.clients_content[self.protocol.clientID]["gateway_id"]

        command_sql = """
        SELECT id AS command_id, meter_id, type, value
        FROM {table} 
        WHERE building_id = '{building_id}' AND gateway_id = '{gateway_id}' and type = 'control'
        ORDER BY create_date
        LIMIT 5
        """.format(table=self.CONTROL_COMMAND_TABLE,
                   building_id=building_id,
                   gateway_id=gateway_id)
        self.protocol.payment_db.fetch(command_sql).addCallback(self.do_control_command)

    def do_control_command(self, data):
        if len(data) == 0:
            logger.debug("{} {} {} NO CONTROL COMMAND...".format(
                self.factory.clients_content[self.protocol.clientID]["building_id"],
                self.factory.clients_content[self.protocol.clientID]["gateway_id"],
                self.factory.clients_content[self.protocol.clientID]["meter_id"]))
        else:
            sec = 0
            self.factory.clients_content[self.protocol.clientID]["command_status"] = {}
            for kw in data:
                self.reactor.callLater(sec, self.send_control_command_msg, kw=kw)
                sec += 2

    def send_control_command_msg(self, kw):
        self.factory.clients_content[self.protocol.clientID]["command_id"] = kw["command_id"]
        self.factory.clients_content[self.protocol.clientID]["command_status"].update({kw["command_id"]: 0})
        meter_id = kw["meter_id"]
        command_id = kw["command_id"]
        addr = "00"  # 仪表地址
        if kw["value"] == "0":  # 强制闭合
            command = "{addr}10005700020400010000".format(addr=addr)
        elif kw["value"] == "1":  # 强制断开
            command = "{addr}10005700020400010001".format(addr=addr)
        elif kw["value"] == "2":  # 恢复预付费
            command = "{addr}10005700020400000000".format(addr=addr)
        else:
            return
        msg = self.create_pass_through_msg(meter_id, command)
        self.protocol.write(msg)
        del_sql = "DELETE FROM {table} WHERE id='{command_id}'".format(table=self.CONTROL_COMMAND_TABLE, command_id=command_id)
        self.protocol.payment_db.sql(del_sql)
        logger.debug(str(command_id) + " CONTROL COMMAND CLEANED!")
        self.reactor.callLater(5, self.check_command_status, command_id=command_id)

    def check_command_status(self, command_id):
        command_status = self.factory.clients_content[self.protocol.clientID]["command_status"][command_id]
        if command_status == 0:  # 未收到回复
            result = '{"message":"failed","code":"1", "data":"1"}'
            update_sql = """
            UPDATE %s SET result='%s', update_date='%s', update_by='cc' 
            WHERE id='%s'
            """ % (self.CONTROL_RECORD_TABLE,
                   result,
                   datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                   command_id)
            self.protocol.payment_db.sql(update_sql)
        elif command_status == 1:  # 已收到回复
            pass
        else:  # 其他情况
            pass

    # 透传回复（集抄回复，充值回复，强控回复）
    def event_97(self):
        """透传指令回复"""
        order_id = self.factory.clients_content[self.protocol.clientID].get("order_id")
        recharge_status = self.factory.clients_content[self.protocol.clientID].get("recharge_status")
        command_id = self.factory.clients_content[self.protocol.clientID].get("command_id")

        modbus_command = self.data[86:-8].upper()
        return_msg = modbus_command[:-4]
        return_crc = modbus_command[-4:]
        crc = crc_modbus(return_msg, left_high=False)
        logger.debug("透传指令回复：" + modbus_command)
        assert crc == return_crc, "DTSY1352-4G pass through data crc verify failed"
        if return_msg[2:6] == "030E":  # 集抄回复
            p = int(return_msg[6:10], base=16) * 0.001
            ept = int(return_msg[10:18], base=16) * 0.01
            balance = signed_int_value(return_msg[18:26]) * 0.01
            buy_time = int(return_msg[26:30], base=16)
            meter_status = int(return_msg[30:34], base=16)
            if recharge_status == 1:
                self.factory.clients_content[self.protocol.clientID]["buy_time"] = buy_time
                update_sql = """
                UPDATE {table} 
                SET before_balance={before_balance}, update_date='{update_time}', update_by='cc' 
                WHERE serial_number='{order_id}'
                """.format(table=self.PAYMENT_ORDER_TABLE,
                           before_balance=balance,
                           order_id=order_id,
                           update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                self.protocol.payment_db.sql(update_sql)
                self.send_recharge_msg()
                self.reactor.callLater(5, self.send_data_collect_msg)
                self.factory.clients_content[self.protocol.clientID]["recharge_status"] = 2
            elif recharge_status == 3:
                if buy_time - self.factory.clients_content[self.protocol.clientID]["buy_time"] == 1:
                    self.factory.clients_content[self.protocol.clientID]["buy_time"] = buy_time
                    update_sql = """
                    UPDATE {table} 
                    SET status='7', after_balance={after_balance}, update_date='{update_time}', update_by='cc' 
                    WHERE serial_number='{order_id}'
                    """.format(table=self.PAYMENT_ORDER_TABLE,
                               after_balance=balance,
                               order_id=order_id,
                               update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                    self.protocol.payment_db.sql(update_sql)
                else:
                    update_sql = """
                    UPDATE {table} SET status='8', update_date='{update_time}', update_by='cc' WHERE serial_number='{order_id}'
                    """.format(table=self.PAYMENT_ORDER_TABLE,
                               order_id=order_id,
                               update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                    self.protocol.payment_db.sql(update_sql)
                self.factory.clients_content[self.protocol.clientID]["recharge_status"] = 4
        elif return_msg[2:8] == '10004C':  # 充值回复
            update_sql = """
            UPDATE {table} SET status='7', update_date='{update_time}', update_by='cc' WHERE serial_number='{order_id}'
            """.format(table=self.PAYMENT_ORDER_TABLE, order_id=order_id, update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            self.protocol.payment_db.sql(update_sql)
            logger.debug(order_id + " RECHARGE COMPLETE!")
        elif return_msg[2:8] == '100057':  # 强控回复
            result = '{"message":"success","code":"0", "data":"0"}'
            update_sql = """
            UPDATE %s SET result='%s', update_date='%s', update_by='cc' 
            WHERE id='%s'
            """ % (self.CONTROL_RECORD_TABLE,
                   result,
                   datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                   command_id)
            self.protocol.payment_db.sql(update_sql)
            self.factory.clients_content[self.protocol.clientID]["command_status"].update({command_id: 1})

    @staticmethod
    def create_pass_through_msg(meter_id, modbus_command):
        """创建透传命令报文"""
        sn = b2a_hex(meter_id.encode()).decode().ljust(28, "0")
        command_crc = crc_modbus(modbus_command, left_high=False)
        command = modbus_command + command_crc
        body = "97f416dd4f68{}68{}".format(sn, command)
        crc = crc_modbus(body, left_high=False)
        msg = "7b7b" + body + crc + "7d7d"
        logger.debug("透传指令下发：" + msg)
        return a2b_hex(msg)

