# coding=utf-8
import datetime
from decimal import Decimal
import xmltodict
import copy
from binascii import b2a_hex, a2b_hex
from functools import partial
import json
import re

from exceptions import ParseHeaderError
from utils import xml_parse, AESBCBEncrypt, crc_modbus, getLogger, signed_int_hex

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

aes_bcb = AESBCBEncrypt("fromhdsegtoacrel", "fromhdsegtoacrel")
ACREL_COMMON_DATAMETA = {
    "root": {
        "common": {
            "building_id": None,
            "gateway_id": None,
            "type": None,
        },
    }
}


class PZ96LOperations(object):

    def __getattr__(self, item):
        if isinstance(item, str):
            return getattr(self, 'event_' + item)

    def event_request(self):
        validate_data = {"@operation": "sequence", "sequence": "powerKeeper"}
        return validate_data

    def event_md5(self):
        validate_data = {"@operation": "result", "result": "pass"}
        return validate_data

    def __init__(self, adapter):
        self.adapter = adapter

    def operations(self, adapter):
        eval('self.event_{}'.format(adapter.operation))(adapter)

    def heart_beat(self, adapter):
        rst = copy.deepcopy(adapter.response_meta)
        time = ''.join(datetime.datetime.now().strftime('%Y%m%d%H%M%S').split("/"))
        rst["root"]["common"]["type"] = "heart_beat"
        rst["root"]["heart_beat"] = {"@operation": "time", "time": time}
        return rst

    def do_heart_beat(self, adapter):
        heart_beat_body = self.heart_beat(adapter)
        rst_data = heart_beat_body
        rst_action = "02"
        rst_body = adapter.get_response_body(rst_data)
        rst_headers = adapter.get_response_headers(rst_action, rst_body)
        result = a2b_hex(rst_headers) + rst_body
        adapter.protocol.write(result)

    def event_01(self, adapter):
        validate_data = getattr(self, 'event_' + adapter.body["root"]["id_validate"]["@operation"])()
        rst = copy.deepcopy(adapter.response_meta)
        rst["root"]["common"]["type"] = "id_validate"
        rst["root"]["id_validate"] = validate_data
        rsts_with_type = [(rst, "01")]
        if adapter.body["root"]["id_validate"]["@operation"] == "md5":  # 身份验证通过
            response_list = adapter.responses(rsts_with_type)
            adapter.protocol.write(response_list[0])

            # 每1分钟请求一次0x02心跳包
            heart_beat = partial(self.do_heart_beat, adapter)
            adapter.protocol.loop_task(heart_beat)

            # 5秒后，每5分钟请求一次0x03电能数据
            request_data = partial(adapter.protocol.write, b'\x1f\x1f\x03\x00\x00\x00\x00')
            adapter.reactor.callLater(5,
                                      adapter.protocol.loop_task,
                                      func=request_data,
                                      tm=300)
        else:
            response_list = adapter.responses(rsts_with_type)
            adapter.protocol.write(response_list[0])

    def event_02(self, adapter):
        pass

    # 电能数据
    def event_03(self, adapter):
        return_time = None
        if adapter.body is not None:
            if isinstance(
                    json.loads(
                        json.dumps(
                            adapter.body['root']['data']['meters']['meter'])),
                    list):
                for meter_content in adapter.body['root']['data']['meters']['meter']:
                    self.insert_values(meter_content, adapter)
            else:
                meter_content = adapter.body['root']['data']['meters']['meter']
                self.insert_values(meter_content, adapter)

            return_time = adapter.body["root"]["data"]["time"]
        adapter.responses_ack(time=return_time, add_header=False)

    def insert_values(self, meter_content, adapter):
        meter = {}
        meter["building_id"] = adapter.body['root']["common"]["building_id"]
        meter["gateway_id"] = adapter.body['root']["common"]["gateway_id"]
        meter["meter_id"] = meter_content['@id']
        meter["meter_name"] = meter_content['@name']
        up_time = adapter.body['root']["data"]['time']
        tday = '-'.join([up_time[0:4], up_time[4:6], up_time[6:8]])
        meter["day"] = tday
        meter["hour"] = tday + ' %s' % up_time[8:10]
        meter["time"] = meter["hour"] + ':%s:%s' % (up_time[10:12], up_time[12:14])
        for j in meter_content["function"]:
            meter[j["@id"]] = float(j["#text"])
            if j["@error"] == "":
                meter["status"] = "1"
            else:
                meter["status"] = "0"
        sql = create_insert_sql(meter)
        adapter.protocol.db.sql(sql)


class AcrelPZ96L(object):
    operations = ["01", "02", "03", "f2"]
    header_format = '1f1f'
    headers = None
    operation = None
    length = None
    message_length = 0
    message_format = 'xml'
    response_meta = copy.deepcopy(ACREL_COMMON_DATAMETA)

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

    def parse(self, data):
        self.parse_headers(data)
        self.parse_body()

    def parse_headers(self, data):
        headers = b2a_hex(data[0:7]).decode()
        if headers.startswith(self.header_format):
            self.headers = headers
            self.factory.headers[self.protocol.clientID] = self.headers
            self.data = data
        else:
            if self.factory.not_enough[self.protocol.clientID]:
                self.headers = self.factory.headers[self.protocol.clientID]
                self.factory.clients_message[self.protocol.clientID] += data
                self.data = self.factory.clients_message[self.protocol.clientID]
            else:
                raise ParseHeaderError("error message information")

        if len(self.headers) == 0:
            raise ParseHeaderError("no found headers information")
        self.operation = self.headers[4:6]
        if self.operation not in self.operations:
            raise ParseHeaderError("unknown operation: %s" % self.operation)
        self.length = int(self.headers[6:], 16)
        self.message_length = self.length + 7

        recv_message_length = len(self.data)
        if recv_message_length < self.message_length:
            self.factory.clients_message[self.protocol.clientID] = self.data
            self.factory.not_enough[self.protocol.clientID] = True
        elif recv_message_length == self.message_length:
            self.factory.clients_message[self.protocol.clientID] = b''
            self.factory.not_enough[self.protocol.clientID] = False
        elif recv_message_length > self.message_length:
            self.factory.clients_message[self.protocol.clientID] = b''
            self.factory.not_enough[self.protocol.clientID] = False
            raise ParseHeaderError("recv message length too lang, reset cache")

    def parse_body(self):
        self.body = None
        if not self.factory.not_enough[self.protocol.clientID]:
            body = self.data[7:]
            eval("self.body_{0}".format(self.operation))(body)
            if self.original_body is not None:
                if self.operation == "f2":
                    self.body = self.original_body
                else:
                    xml_header, xml_body = xml_parse(self.original_body)
                    if xml_body is not None:
                        self.body = xmltodict.parse(xml_body)
                        self.factory.clients_content[self.protocol.clientID]["building_id"] = self.body["root"]["common"]["building_id"]
                        self.factory.clients_content[self.protocol.clientID]["gateway_id"] = self.body["root"]["common"]["gateway_id"]
        self.response_meta["root"]["common"]["building_id"] = self.factory.clients_content[self.protocol.clientID]["building_id"]
        self.response_meta["root"]["common"]["gateway_id"] = self.factory.clients_content[self.protocol.clientID]["gateway_id"]

    def run_operations(self):
        if self.factory.not_enough[self.protocol.clientID]:
            self.responses_ack(add_header=False)
            return
        if self.body is None:
            if self.operation == "02":
                return
            self.responses_ack(add_header=False)
            return
        self.operations_event.operations(self)

    def responses(self, data_list, add_header=True):
        result_list = []
        for data in data_list:
            rst_data = data[0]
            rst_action = data[1]
            rst_body = self.get_response_body(rst_data)
            if add_header:
                rst_headers = self.get_response_headers(rst_action, rst_body)
                result = a2b_hex(rst_headers) + rst_body
            else:
                result = rst_body
            result_list.append(result)
        return result_list

    def responses_ack(self, status=True, time=None, add_header=True):
        rsts_with_type = self.ack_ok(status=status, time=time)
        response_list = self.responses(rsts_with_type, add_header=add_header)
        self.protocol.write(response_list[0])

    def get_response_body(self, rst_data):
        if rst_data is not None:
            if self.message_format == "xml":
                result = ''.join(xmltodict.unparse(rst_data).split('\n'))
                result = result.encode()
                return result
            return ''.encode()
        else:
            return ''.encode()

    def get_response_headers(self, rst_action, rst_data):
        rst_action = rst_action or self.operation
        rst_data = rst_data or ''
        headers = ((self.headers[:4] + rst_action) +
                   hex(len(rst_data))[2:].zfill(8))
        return headers

    def body_01(self, body):
        self.original_body = body.decode()

    def body_02(self, body):
        self.original_body = body.decode()

    def body_03(self, body):
        aes_body = b2a_hex(body)
        self.original_body = aes_bcb.decrypt(aes_body)

    def body_f2(self, body):
        aes_body = b2a_hex(body)
        decrypt_body = aes_bcb.decrypt(aes_body)
        decrypt_body = re.search('^(\d\d\d\d\d\|[0-9A-Za-z]+).*', decrypt_body)
        if decrypt_body is not None:
            self.original_body = decrypt_body.groups()[0]

    def ack_ok(self, status=True, time=None):
        rst = copy.deepcopy(self.response_meta)
        if time is None:
            time = ''.join(
                datetime.datetime.now().strftime('%Y%m%d%H%M%S').split("/"))
        if self.operation == '03':
            rst["root"]["common"]["type"] = "energy_data"
            rst["root"]["data"] = {"@operation": "report"}
            rst["root"]["time"] = time
            if status:
                rst["root"]["ack"] = "OK"
            else:
                rst["root"]["ack"] = "fail"
        else:
            rst["root"]["ack"] = "fail"
            self.operation = "03"
        return [(rst, self.operation)]


def create_insert_sql(data):
    monitor_model = {}
    column = []
    content = []
    monitor_model["device_imei"] = '--'.join([data["building_id"], data["gateway_id"], data["meter_id"]])
    monitor_model["building_code"] = data["building_id"]
    monitor_model["gateway_code"] = data["gateway_id"]
    monitor_model["meter_code"] = data["meter_id"]
    monitor_model["day"] = data["day"]
    monitor_model["hour"] = data["hour"]
    monitor_model["device_content"] = ''

    pt = int(data["pt"])  # 电压变比
    ct = int(data["ct"])  # 电流变比
    dpct = bin(int(data["dpct"]))[2:].zfill(16)
    dpt = int(dpct[0:8], base=2)
    dct = int(dpct[8:16], base=2)
    pq = bin(int(data["pq"]))[2:].zfill(16)
    dpq = int(pq[0:8], base=2)
    sign = list(pq[8:16])

    monitor_model["a_dy"] = Decimal(data["ua"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["b_dy"] = Decimal(data["ub"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["c_dy"] = Decimal(data["uc"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["ab_dy"] = Decimal(data["uab"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["bc_dy"] = Decimal(data["ubc"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["ac_dy"] = Decimal(data["uca"] * 10**(dpt-4)).quantize(Decimal("0.00"))
    monitor_model["a_dl"] = Decimal(data["ia"] * 10**(dct-4)).quantize(Decimal("0.00"))
    monitor_model["b_dl"] = Decimal(data["ib"] * 10**(dct-4)).quantize(Decimal("0.00"))
    monitor_model["c_dl"] = Decimal(data["ic"] * 10**(dct-4)).quantize(Decimal("0.00"))
    monitor_model["a_yggl"] = Decimal(data["pa"] * 10**(dpq-7) * -1**int(sign[0])).quantize(Decimal("0.00"))
    monitor_model["b_yggl"] = Decimal(data["pb"] * 10**(dpq-7) * -1**int(sign[1])).quantize(Decimal("0.00"))
    monitor_model["c_yggl"] = Decimal(data["pc"] * 10**(dpq-7) * -1**int(sign[2])).quantize(Decimal("0.00"))
    monitor_model["total_yggl"] = Decimal(data["p"] * 10**(dpq-7) * -1**int(sign[3])).quantize(Decimal("0.00"))
    monitor_model["a_wggl"] = Decimal(data["qa"] * 10**(dpq-7) * -1**int(sign[4])).quantize(Decimal("0.00"))
    monitor_model["b_wggl"] = Decimal(data["qb"] * 10**(dpq-7) * -1**int(sign[5])).quantize(Decimal("0.00"))
    monitor_model["c_wggl"] = Decimal(data["qc"] * 10**(dpq-7) * -1**int(sign[6])).quantize(Decimal("0.00"))
    monitor_model["total_wggl"] = Decimal(data["q"] * 10**(dpq-7) * -1**int(sign[7])).quantize(Decimal("0.00"))
    monitor_model["a_szgl"] = Decimal(data["sa"] * 10**(dpq-7)).quantize(Decimal("0.00"))
    monitor_model["b_szgl"] = Decimal(data["sb"] * 10**(dpq-7)).quantize(Decimal("0.00"))
    monitor_model["c_szgl"] = Decimal(data["sc"] * 10**(dpq-7)).quantize(Decimal("0.00"))
    monitor_model["total_szgl"] = Decimal(data["s"] * 10**(dpq-7)).quantize(Decimal("0.00"))
    monitor_model["a_glys"] = Decimal(data["pfa"]).quantize(Decimal("0.00"))
    monitor_model["b_glys"] = Decimal(data["pfb"]).quantize(Decimal("0.00"))
    monitor_model["c_glys"] = Decimal(data["pfc"]).quantize(Decimal("0.00"))
    monitor_model["total_glys"] = Decimal(data["pf"]).quantize(Decimal("0.00"))
    monitor_model["pl"] = Decimal(data["f"]).quantize(Decimal("0.00"))
    monitor_model["epi"] = Decimal(data["epi"] * 0.001 * pt * ct).quantize(Decimal("0.00"))
    monitor_model["epe"] = Decimal(data["epe"] * 0.001 * pt * ct).quantize(Decimal("0.00"))
    monitor_model["wqp"] = Decimal(data["eqi"] * 0.001 * pt * ct).quantize(Decimal("0.00"))
    monitor_model["wpp"] = Decimal(data["eqe"] * 0.001 * pt * ct).quantize(Decimal("0.00"))

    monitor_model["extend_s1"] = str(data["status"])
    monitor_model["create_date"] = data["time"]
    monitor_model["update_date"] = data["time"]
    monitor_model["create_by"] = 'cc'
    monitor_model["update_by"] = 'cc'

    for k, v in monitor_model.items():
        column.append('`%s`' % k)
        if isinstance(v, str):
            content.append("'%s'" % v)
        else:
            content.append(str(v))
    table = 'ld_device_data_%s' % (data["time"][0:4] + data["time"][5:7])
    cm = "(" + ','.join(column) + ")"
    vl = "(" + ','.join(content) + ")"
    sql = "INSERT INTO {} {} VALUES {} ".format(table, cm, vl)
    return sql
