"""
worker for inkbird ibbq and other equivalent cooking/BBQ thermometers.

Thermometer sends every ~2sec the current temperature.

"""
import struct

from mqtt import MqttMessage
from workers.base import BaseWorker
import logger
import json

_LOGGER = logger.get(__name__)

REQUIREMENTS = ["bluepy"]


class IbbqWorker(BaseWorker):
    def _setup(self):
        _LOGGER.info("Adding %d %s devices", len(self.devices), repr(self))
        for name, mac in self.devices.items():
            _LOGGER.info("Adding %s device '%s' (%s)", repr(self), name, mac)
            self.devices[name] = ibbqThermometer(mac, timeout=self.command_timeout)

    def format_static_topic(self, *args):
        return "/".join([self.topic_prefix, *args])

    def __repr__(self):
        return self.__module__.split(".")[-1]

    def status_update(self):
        for name, ibbq in self.devices.items():
            ret = dict()
            value = list()
            if not ibbq.connected:
                ibbq.device = ibbq.connect()
                ibbq.subscribe()
                bat, value = None, value
            else:
                bat, value = ibbq.update()
            n = 0
            ret["available"] = ibbq.connected
            ret["battery_level"] = bat
            for i in value:
                n += 1
                ret["Temp{}".format(n)] = i
            return [
                MqttMessage(
                    topic=self.format_static_topic(name), payload=json.dumps(ret)
                )
            ]


class ibbqThermometer:
    SettingResult = "fff1"
    AccountAndVerify = "fff2"
    RealTimeData = "fff4"
    SettingData = "fff5"
    Notify = b"\x01\x00"
    realTimeDataEnable = bytearray([0x0B, 0x01, 0x00, 0x00, 0x00, 0x00])
    batteryLevel = bytearray([0x08, 0x24, 0x00, 0x00, 0x00, 0x00])
    KEY = bytearray(
        [
            0x21,
            0x07,
            0x06,
            0x05,
            0x04,
            0x03,
            0x02,
            0x01,
            0xB8,
            0x22,
            0x00,
            0x00,
            0x00,
            0x00,
            0x00,
        ]
    )

    def getBattery(self):
        self.Setting_uuid.write(self.batteryLevel)

    def connect(self, timeout=5):
        from bluepy import btle

        try:
            device = btle.Peripheral(self.mac)
            _LOGGER.debug("%s connected ", self.mac)
            return device
        except btle.BTLEDisconnectError as er:
            _LOGGER.debug("failed connect %s", er)

    def __init__(self, mac, timeout=5):
        self.cnt = 0
        self.batteryPct = 0
        self.timeout = timeout
        self.mac = mac
        self.values = list()
        self.device = self.connect()
        self.offline = 0
        if not self.device:
            return
        self.device = self.subscribe()

    @property
    def connected(self):
        return bool(self.device)

    def subscribe(self, timeout=5):
        from bluepy import btle

        class MyDelegate(btle.DefaultDelegate):
            def __init__(self, caller):
                btle.DefaultDelegate.__init__(self)
                self.caller = caller
                _LOGGER.debug("init mydelegate")

            def handleNotification(self, cHandle, data):
                batMin = 0.95
                batMax = 1.5
                result = list()
                #    safe = data
                if cHandle == 37:
                    if data[0] == 0x24:
                        currentV = struct.unpack("<H", data[1:3])
                        maxV = struct.unpack("<H", data[3:5])
                        self.caller.batteryPct = int(
                            100
                            * ((batMax * currentV[0] / maxV[0] - batMin) / (batMax - batMin))
                        )
                else:
                    while len(data) > 0:
                        v, data = data[0:2], data[2:]
                        result.append(struct.unpack("<H", v)[0] / 10)
                    self.caller.values = result

        #    _LOGGER.debug("called handler %s %s", cHandle, safe)

        if self.device is None:
            return
        try:
            services = self.device.getServices()
            for service in services:
                if "fff0" not in str(service.uuid):
                    continue
                for schar in service.getCharacteristics():
                    if self.AccountAndVerify in str(schar.uuid):
                        self.account_uuid = schar
                    if self.RealTimeData in str(schar.uuid):
                        self.RT_uuid = schar
                    if self.SettingData in str(schar.uuid):
                        self.Setting_uuid = schar
                    if self.SettingResult in str(schar.uuid):
                        self.SettingResult_uuid = schar

            self.account_uuid.write(self.KEY)
            _LOGGER.info("Authenticated %s", self.mac)
            self.RT_uuid.getDescriptors()
            self.device.writeCharacteristic(self.RT_uuid.getHandle() + 1, self.Notify)
            self.device.writeCharacteristic(
                self.SettingResult_uuid.getHandle() + 1, self.Notify
            )
            self.getBattery()
            self.Setting_uuid.write(self.realTimeDataEnable)
            self.device.withDelegate(MyDelegate(self))
            _LOGGER.info("Subscribed %s", self.mac)
            self.offline = 0
        except btle.BTLEException as ex:
            _LOGGER.info("failed %s %s", self.mac, ex)
            self.device = None
            _LOGGER.info("unsubscribe")
        return self.device

    def update(self):
        from bluepy import btle

        if not self.connected:
            return list()
        self.values = list()
        self.cnt += 1
        try:
            if self.cnt > 5:
                self.cnt = 0
                self.getBattery()
            while self.device.waitForNotifications(1):
                pass
            if self.values:
                self.offline = 0
            else:
                _LOGGER.debug("%s is silent", self.mac)
                if self.offline > 3:
                    try:
                        self.device.disconnect()
                    except btle.BTLEInternalError as e:
                        _LOGGER.debug("%s", e)
                    self.device = None
                    _LOGGER.debug("%s reconnect", self.mac)
                else:
                    self.offline += 1
        except btle.BTLEDisconnectError as e:
            _LOGGER.debug("%s", e)
            self.device = None
        finally:
            return (self.batteryPct, self.values)