import socket
import time

import zeroconf


class HostResolver(zeroconf.RecordUpdateListener):
    def __init__(self, name):
        self.name = name
        self.address = None

    def update_record(self, zc, now, record):
        if record is None:
            return
        if record.type == zeroconf._TYPE_A:
            assert isinstance(record, zeroconf.DNSAddress)
            if record.name == self.name:
                self.address = record.address

    def request(self, zc, timeout):
        now = time.time()
        delay = 0.2
        next_ = now + delay
        last = now + timeout

        try:
            zc.add_listener(self, zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY,
                                                       zeroconf._CLASS_IN))
            while self.address is None:
                if last <= now:
                    # Timeout
                    return False
                if next_ <= now:
                    out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY)
                    out.add_question(
                        zeroconf.DNSQuestion(self.name, zeroconf._TYPE_A, zeroconf._CLASS_IN))
                    out.add_answer_at_time(
                        zc.cache.get_by_details(self.name, zeroconf._TYPE_A,
                                                zeroconf._CLASS_IN), now)
                    zc.send(out)
                    next_ = now + delay
                    delay *= 2

                zc.wait(min(next_, last) - now)
                now = time.time()
        finally:
            zc.remove_listener(self)

        return True


def resolve_host(host, timeout=3.0, zeroconf_instance: zeroconf.Zeroconf = None):
    from aioesphomeapi import APIConnectionError

    try:
        zc = zeroconf_instance or zeroconf.Zeroconf()
    except Exception:
        raise APIConnectionError("Cannot start mDNS sockets, is this a docker container without "
                                 "host network mode?")

    try:
        info = HostResolver(host + '.')
        address = None
        if info.request(zc, timeout):
            address = socket.inet_ntoa(info.address)
    except Exception as err:
        raise APIConnectionError("Error resolving mDNS hostname: {}".format(err))
    finally:
        if not zeroconf_instance:
            zc.close()

    if address is None:
        raise APIConnectionError("Error resolving address with mDNS: Did not respond. "
                                 "Maybe the device is offline.")
    return address
