import logging
import time
from collections import namedtuple

from zeroconf import IPVersion
from zeroconf import ServiceBrowser
from zeroconf import Zeroconf

log = logging.getLogger(__name__)

MokuInfo = namedtuple(
    "MokuInfo",
    [
        "name",
        "netver",
        "fwver",
        "hwver",
        "serial",
        "colour",
        "bootmode",
        "ipv4_addr",
        "ipv6_addr",
    ],
)


class Finder(object):
    def __init__(self, on_add=None, on_remove=None):
        self.moku_list = []
        self.finished = False
        self.filter = None
        self.timeout = 5
        self.zero_conf = Zeroconf(ip_version=IPVersion.V4Only)
        self.browser = None
        self.on_add = on_add
        self.on_remove = on_remove

    @staticmethod
    def _get_parsed_addresses(info):
        ipv4 = info.parsed_addresses()[0]
        # TODO
        ipv6 = ""
        return [ipv4, ipv6]

    def _parse_05(self, info):
        name = info.name.split("." + info.type)[0]
        p = info.properties
        addresses = self._get_parsed_addresses(info)
        return MokuInfo(
            name=name,
            netver=int(p[b"netver"]),
            fwver=int(p[b"fwver"]),
            hwver=float(p[b"hwver"]),
            serial=int(p[b"serial"]),
            colour=(p.get(b"colour", p.get(b"color")) or b"").decode("utf8"),
            bootmode=p[b"bootmode"].decode("utf8"),
            ipv4_addr=addresses[0],
            ipv6_addr=addresses[1],
        )

    def _parse_04(self, info):
        name = info.name.split("." + info.type)[0]
        p = info.properties
        addresses = self._get_parsed_addresses(info)
        return MokuInfo(
            name=name,
            netver=int(p[b"netver"]),
            fwver=int(p[b"device.fw_version"]),
            hwver=float(p[b"device.hw_version"]),
            serial=int(p[b"device.serial"]),
            colour=p[b"device.colour"].decode("utf8"),
            bootmode=p[b"system.bootmode"].decode("utf8"),
            ipv4_addr=addresses[0],
            ipv6_addr=addresses[1],
        )

    def _parse_02(self, info):
        name = info.name.split("." + info.type)[0]
        p = info.properties
        addresses = self._get_parsed_addresses(info)
        return MokuInfo(
            name=name,
            netver=int(p[b"netver"]),
            fwver=int(p[b"system.micro"]),
            hwver=float(p[b"device.hw_version"]),
            serial=int(p[b"device.serial"]),
            colour=p[b"device.colour"].decode("utf8"),
            bootmode=p[b"system.bootmode"].decode("utf8"),
            ipv4_addr=addresses[0],
            ipv6_addr=addresses[1],
        )

    def add_service(self, zeroconf, service_type, name):
        info = zeroconf.get_service_info(service_type, name)
        if info is None:
            return

        try:
            parsers = {0.2: self._parse_02, 0.4: self._parse_04, 0.5: self._parse_05}
            record = parsers[float(info.properties[b"txtver"])](info)
        except Exception as e:
            log.error(e)
            return

        if self.filter is None or self.filter(record):
            self.moku_list.append(record)

            if self.on_add:
                self.on_add(name, record)

    def remove_service(self, zeroconf, service_type, name):
        if self.on_remove:
            self.on_remove(name)

    def update_service(self, zeroconf, service_type, name):
        pass

    def start(self):
        self.browser = ServiceBrowser(self.zero_conf, "_moku._tcp.local.", listener=self)

    def close(self):
        self.zero_conf.close()

    def find_all(self, timeout=5, filter=None):
        self.timeout = timeout
        self.filter = filter
        self.start()
        start = time.time()
        try:
            while time.time() - start < timeout and not self.browser.done and (not self.finished):
                time.sleep(0.1)
        except KeyboardInterrupt:
            pass
        finally:
            self.zero_conf.close()
        return self.moku_list
