"""
name: ibisip_service_subscriber
title: IBISIP Service Subscriber
author: Cooper
date: 06/01/2026

desc:
As all IBISIP services are subscribed in the same way this new module removes the need to copy and paste the same code over
and over.

"""
import logging
import time
import hashlib
import http.client
import xmltodict

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

        self.subscribed = False     #When a subscirbe active message is returned
        self.subscription_timeout = self.config_dict.get("IBISIP_subscription_timeout", 300)    #This is the timeout for CIS subscribe
        self.subscription_timer = 0

    def get_data(self, target_ip, target_port, path):
        """
        Uses HTTP GET to obtain data from a particular
        """
        logging.info("%s Requesting: %s:%s%s" % (self.service_name, target_ip, target_port, path))

        conn = http.client.HTTPConnection(target_ip, target_port)

        # The header is for completeness sake, not entirely necessary but for this packet to show up in wireshark as HTTP
        # then it is needed
        header = {
            "Content-type": "text/xml"
        }

        try:
            conn.request("GET", path, headers=header)
            response = conn.getresponse()
            logging.info("%s %s" % (response.status, response.reason))
        except ConnectionRefusedError as e:
            logging.error(e)
            return None
        except TimeoutError as e:
            logging.error(e)
            return None
        except OSError as e:  # For when connecting to a host that doesn't exist
            logging.error(e)
            return None

        if response.status == 404:  # HTTP Not Found
            logging.error("%s: 404 NOT FOUND" % self.service_name)
            return None
        else:
            data = response.read().decode("utf-8")
            return data

    def post_data(self, target_ip, target_port, path, data):
        """
        General purpose data poster, this just assumes that all data posted is XML based and will convert a dict into
        one accordingly.
        """
        if isinstance(data, dict):
            post_xml = xmltodict.unparse(data, pretty=True)
        else:
            post_xml = data

        header = {
            "Content-type": "text/xml"
        }

        logging.info("%s Sending request to %s:%s%s" % (self.service_name, target_ip, target_port, path))

        try:
            conn = http.client.HTTPConnection("%s:%s" % (target_ip, target_port))
            conn.request("POST", path, post_xml, header)
            reply = conn.getresponse()
        except ConnectionRefusedError as e:
            logging.error(e)
            return None, None
        except TimeoutError as e:
            logging.error(e)
            return None, None
        else:
            status = reply.status
            body = reply.read().decode("utf-8")

            return status, body

    def send_subscription_request(self, service_ip, service_port, service_path, subscription_dict, blocking=True):
        """
        This sends a subscription request (Although can also function as an unsubscribe as its dependent on the
        subscription dict.

        :return: None only if there is an error
        """
        subscribe_xml = xmltodict.unparse(subscription_dict, pretty=True)

        logging.info("Sending request to %s:%s%s" % (service_ip, service_port, service_path))

        #The header is for completeness sake, not entirely necessary but for this packet to show up in wireshark as HTTP
        #then it is needed
        header = {
            "Content-type": "text/xml"
        }

        #Keep sending subscription messages till something happens
        # start_time = time.time()
        retry_counter = 0
        while 1:
            try:
                conn = http.client.HTTPConnection("%s:%s" % (service_ip, service_port))
                conn.request("POST", service_path, subscribe_xml, header)
                reply = conn.getresponse()
            except ConnectionRefusedError as e:
                logging.error(e)
                continue

            except TimeoutError as e:
                logging.error(e)
                continue

            status = reply.status
            body = reply.read().decode("utf-8")

            if reply.status == 200:     #200 is HTTP for OK
                if self.parse_subscription_response(body):
                    self.subscribed = True
                    self.subscription_timer = time.time()
                else:
                    self.subscribed = False

                break
            else:
                logging.info(status)

            #TODO Perhaps only try so many times to subscribe,
            # but then again is there much point in doing anything without a service?
            # if (time.time() - start_time) > 120:
            #     break
            if not blocking:
                if retry_counter > 3:
                    break
                else:
                    retry_counter += 1

            time.sleep(1)

        return self.subscribed, self.subscription_timer

    def parse_subscription_response(self, raw_xml_data: str) -> bool:
        """
        Parses the Subscription Response

        Need to handle other states:
        {'SubscribeResponse': {'OperationErrorMessage': {'Value': 'Subscription already known'}}}

        """
        try:
            raw_xml_dict = xmltodict.parse(raw_xml_data, "utf-8", dict_constructor=dict)
        except Exception as e:  # Exception is xml.parsers.expat.ExpatError but cannot use it directly
            logging.warning("XML issue: %s" % e)
            return False
        else:
            subscription_response = raw_xml_dict["SubscribeResponse"]

            active_response = subscription_response.get("Active", False)
            error_response = subscription_response.get("OperationErrorMessage", False)

            if active_response is not False:
                active_value = active_response.get("Value", False)

                if active_value is not False:
                    if active_value.lower() == "true" or active_value == "1":
                        return True
                    else:
                        return False

            if error_response is not False:
                non_errors = ["Subscription already known"]
                actual_errors = [""]

                error_value = error_response.get("Value", False)

                if error_value in non_errors:
                    return True
                elif error_value in actual_errors:
                    return False
                else:
                    return  False
