"""
name: ibisip_service_discovery
title: IBISIP Service Discovery
author: Cooper
date: 06/01/2026

desc:
As all IBISIP services are discovered the same way, it is probably worth breaking out the part that does the looking
so that the same code isn't copied over and over, with multiple versions to maintain if there is a problem.

This needs the ability to find several services and return those if required.

"""
import time
import logging

from hanip.itxpt import DNS_SD

class IBISIPServiceDiscovery(object):
    def __init__(self, config_dict):
        self.config_dict = config_dict

        self.service_found = False
        self.list_of_services = []

    def get_discovery_cycle_timeout(self, timeout):
        """
        Returns the length of a single discovery pass before the browser is restarted.
        """
        if timeout <= 0:
            return 5

        return timeout

    def get_discovery_retry_interval(self):
        """
        Returns the delay between discovery passes when no service is found.
        """
        try:
            retry_interval = float(self.config_dict.get("IBISIP_discovery_retry_interval", 1))
        except (TypeError, ValueError):
            retry_interval = 1

        if retry_interval < 0:
            return 0

        return retry_interval

    """
    ###################################################################################################################
    Service Discovery
    """
    def discover_relevant_services(self, service_name, timeout):
        """
        This will only look for services that respond with the given service name
        """
        try:
            timeout = float(timeout)
        except (TypeError, ValueError):
            timeout = 0

        logging.info("IBISIPServiceDiscovery: Looking for service(s) %s" % service_name)
        discover_timeout = time.time()
        discovery_cycle_timeout = self.get_discovery_cycle_timeout(timeout)
        retry_interval = self.get_discovery_retry_interval()

        while 1:
            service_discover = DNS_SD.DNSSD_Discover("_ibisip_http._tcp.local.", service_name)
            cycle_timeout = time.time()
            discovered_services = []
            service_discover.run()

            try:
                while 1:
                    discovered_services = service_discover.discovered_services

                    if len(discovered_services) < 1:
                        logging.info("IBISIPServiceDiscovery: ...still looking")

                        if timeout > 0 and (time.time() - discover_timeout) > timeout:
                            logging.info("IBISIPServiceDiscovery: timeout reached")
                            self.service_found = False
                            self.list_of_services = []
                            return []

                        if (time.time() - cycle_timeout) >= discovery_cycle_timeout:
                            break

                        time.sleep(1)

                    else:
                        logging.info("IBISIPServiceDiscovery: Services found")
                        self.service_found = True
                        self.list_of_services = discovered_services
                        return discovered_services
            finally:
                service_discover.close()

            if retry_interval > 0:
                time.sleep(retry_interval)

    def select_preferred_service(self, preferred_version):
        """
        This will find the matching service from a list of dictionaries stored above.

        self.discover_relevant_services must be called first!

        Note that this will return the first valid service, so if there are multiple offered under the first version
        then the first to be found will be selected.
        """
        if self.service_found is not False or len(self.list_of_services) > 0:
            selected_service = self.parse_services(self.list_of_services, preferred_version)

            host_name = selected_service["name"]
            service_ip = selected_service["address"]
            service_port = selected_service["port"]
            #Get the version of the selected service:
            try:
                service_version = (selected_service["txt_records"][b"ver"]).decode("utf-8")
            except KeyError:
                # This should never fail if version is always included, but I am not sure the risk of setting it to 1.0 if it does fail
                service_version = "1.0"

            logging.info("IBISIPServiceDiscovery: Found %s:%s ver:%s" % (service_ip,
                                                 service_port,
                                                 service_version))

            return selected_service

        else:
            logging.error("IBISIPServiceDiscovery: No services to choose from")
            return None


    def parse_services(self, services: list, preferred_version: str) -> dict:
        """
        This parses the services found to establish the required one or the best available.

        Given it is looking in the txt records for a specific version of IBISIP it isn't a general parser.
        It will loop through the services that it has been given to look for the default version defined at the top
        of this module, or if its overriden via config.

        If it cannot find a match, it will just return the first one it found.  Perhaps not the ideal, but I don't
        know what would be ideal.

        """
        versions_found = []

        if len(services) == 1:
            logging.info("IBISIPServiceDiscovery: Only one service found")
            return services[0]

        for service in services:
            #Go through each found service in turn and look for the txt_record
            logging.info("IBISIPServiceDiscovery: Found %s" % (service["name"]))
            try:
                txt_record = service["txt_records"]
                version = txt_record[b"ver"].decode("utf-8")
            except (UnicodeDecodeError, KeyError):
                continue

            if version == preferred_version:
                logging.info("IBISIPServiceDiscovery: Using %s" % (service["name"]))
                return service
            else:
                #Append in the original order so that the index is used to pick later
                versions_found.append(version)

        # Goes into this bit if an exact match is not found, I guess find the closest?
        logging.info("IBISIPServiceDiscovery: Preferred version not found, using next best")
        preferred_version = preferred_version.replace(".", "")     #Ditch the dot so that can be converted into an int
        for index, version in enumerate(versions_found):
            try:
                _pref_ver = int(preferred_version)
                _ver = int(version.replace(".", ""))

                if _ver > _pref_ver:
                    continue
                else:
                    logging.info("IBISIPServiceDiscovery: Using version %s" % version)
                    return services[index]
            except ValueError:
                continue

        #Hm if we have reached here then there wasn't a match or a suitable version then what do we do here?
        return services[0]


"""
###################################################################################################################
Testing
"""
if __name__ == "__main__":
    import sys
    logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

    config = {

    }

    service_name = "PassengerCountingService"
    # service_name = "CustomerInformationService"

    ibisipsd = IBISIPServiceDiscovery(config)
    print(ibisipsd.discover_relevant_services(service_name, 5))

    # print(ibisipsd.select_preferred_service("2.1"))




