class ProtocolFilterHandler(object):
    PROTOCOLS_FILTER_QUEUES_NAME = {
        "MQTT": "devices/mqtt",
        "COAP": "devices/coap",
    }

    def __init__(self, logger):
        self.logger = logger

        self.walrus = Walrus(REDIS_HOSTNAME, REDIS_PORT)

    def parse(self, message):
        protocol = message["device_info"]["protocol"]
        protocol_queue_name = self.PROTOCOLS_FILTER_QUEUES_NAME[protocol]
        protocol_queue = self.walrus.List(protocol_queue_name)
        protocol_queue.append(json.dumps(message))

        self.logger.info("Pushed the message {} to queue {}".format(
            message, protocol_queue_name))
class DevicesMessagesProcessor(object):
    MAX_WORKERS = 50

    GASS_SERVER_GATEWAY_DEVICES_PERFORMED_ACTIONS = "{}/gateways/{}/devices/actions" \
        .format(GASS_SERVER_API_URL, GATEWAY_UUID)
    GASS_SERVER_GATEWAY_DEVICES_RECEIVED_ACTIONS = "{}/gateways/{}/devices/actions/test" \
        .format(GASS_SERVER_API_URL, GATEWAY_UUID)

    def __init__(self):
        self.logger = retrieve_logger("messages_processor")

        self.walrus = Walrus(host=REDIS_HOSTNAME, port=REDIS_PORT)
        self.devices_messages = self.walrus.List('devices_messages')

        self.thread_pool_executor = ThreadPoolExecutor(
            max_workers=self.MAX_WORKERS, )

        self.devices_service = DevicesService(logger=self.logger)
        self.rules_service = RulesService(logger=self.logger)
        self.rules_executor = RulesExecutor(logger=self.logger)
        self.protocol_filter_handler = ProtocolFilterHandler(self.logger)

    def _rule_is_inside_activation_interval(self, interval_start,
                                            interval_end):
        current_date = datetime.datetime.utcnow()
        weekday_inside_interval = interval_start[
            "weekday"] <= current_date.weekday() <= interval_end["weekday"]
        if not weekday_inside_interval:
            return False

        hour_inside_interval = interval_start[
            "hour"] <= current_date.hour <= interval_end["hour"]
        if not hour_inside_interval:
            return False

        minute_inside_interval = interval_start[
            "minute"] <= current_date.minute <= interval_end["minute"]
        if not minute_inside_interval:
            return False

        return True

    def _filter_rules_by_active_interval(self, rules):
        if not rules:
            return []

        valid_rules = []
        for rule in rules:
            if not rule.get("interval"):
                valid_rules.append(rule)
                continue

            interval_start = rule["interval"]["start"]
            interval_end = rule["interval"]["end"]
            if not self._rule_is_inside_activation_interval(
                    interval_start, interval_end):
                self.logger.error(
                    "The rule {} should not be verified because at the moment rule isn't active"
                    .format(str(rule["_id"])))
                continue

            valid_rules.append(rule)

        return valid_rules

    def _send_performed_actions(self, performed_actions):
        body = performed_actions
        attempts = 0
        while True:
            self.logger.info(
                "Attempts {} to send to GaaS-Server the performed actions".
                format(attempts))
            try:
                response = requests.post(
                    self.GASS_SERVER_GATEWAY_DEVICES_PERFORMED_ACTIONS,
                    json=body)
                if response.status_code == HTTPStatusCodes.CREATED:
                    self.logger.debug(
                        "The performed actions has been sent successfully to the Gass Server"
                    )
                    return

                attempts += 1
            except Exception as err:
                self.logger.error(
                    "Failed to send the performed actions to the GaaS Server. Reason: {}"
                    .format(err), )

                self.logger.info("Sleeping...")
                time.sleep(5)
                attempts += 1
                self.logger.info(
                    "Retry sending the performed actions to the GaSS Server")

    def _test_forward_receive_actions(self, actions):
        body = actions
        attempts = 0

        while True:
            self.logger.info(
                "Attempts {} to send to GaaS-Server the received actions")
            try:
                response = requests.post(
                    self.GASS_SERVER_GATEWAY_DEVICES_RECEIVED_ACTIONS,
                    json=body)
                if response.status_code == HTTPStatusCodes.CREATED:
                    self.logger.debug(
                        "The performed actions has been sent successfully to the Gass Server"
                    )
                    return

                attempts += 1
            except Exception as err:
                self.logger.error(
                    "Failed to send the performed actions to the GaaS Server. Reason: {}"
                    .format(err),
                    exc_info=True,
                )

                self.logger.info("Sleeping...")
                time.sleep(5)
                attempts += 1
                self.logger.info(
                    "Retry sending the performed actions to the GaSS Server")

    def _send_devices_new_values(self, devices_new_values):
        devices_ids = [device['id'] for device in devices_new_values]
        devices = list(self.devices_service.find_multiple_devices(devices_ids))
        device_id_to_device_info = {device["id"]: device for device in devices}

        for device_new_value in devices_new_values:
            device_id, device_value = device_new_value["id"], device_new_value[
                "value"]
            device_info = device_id_to_device_info[device_id]
            message = {
                "device_info": {
                    "device_uuid": device_id,
                    "protocol": device_info["protocol"],
                    "ip": device_info["ip"],
                    "port": device_info.get("port", 0)
                },
                "value": device_value
            }
            self.protocol_filter_handler.parse(message)

    def _parse_message(self, message):
        try:
            self.logger.debug("Received message: {}".format(message))

            device_id, new_value = message["id"], message["v"]
            updated = self.devices_service.update(device_id, new_value)
            if not updated:
                self.logger.debug("Failed to update the sensor's value")
                return

            performed_actions = [{
                "type": ACTIONS_TYPES.CHANGE_VALUE,
                "device": device_id,
                "value": new_value,
                "timestamp": get_utc_timestamp(),
            }]
            self.logger.debug("Device has been updated")

            rules_to_check = list(
                self.rules_service.find_that_involves_devices([device_id]))
            rules_to_check = self._filter_rules_by_active_interval(
                rules_to_check)
            self.logger.debug("Have to check {} rules".format(
                len(rules_to_check)))

            devices_new_values, performed_actions_by_rules = self.rules_executor.execute(
                rules_to_check)
            performed_actions.extend(performed_actions_by_rules)
            self.logger.debug(
                "Performed actions: {}".format(performed_actions))

            self.logger.debug("Send the performed actions to the server")
            self._send_performed_actions(performed_actions)

            # Send the new values to devices to update their state
            self.logger.debug(
                "Devices new values: {}".format(devices_new_values))
            if not devices_new_values:
                return

            self._send_devices_new_values(devices_new_values)
            self.logger.debug("The new values have been sent to the devices")

        except Exception as err:
            self.logger.error(
                "Some error occurred while the message received from device was parsed. Reason: {}"
                .format(err),
                exc_info=True)

    def _write_request_latency(self, emit_time):
        now = time.time()
        latency = now - emit_time

        with open("latency.txt", mode="a") as file_handler:
            file_handler.write("{}\n".format(latency))

        self.logger.info("Current Average Latency: {}".format(latency))

    def start(self):
        self.logger.debug("Waiting for messages from devices")

        while True:
            message = self.devices_messages.bpopleft(timeout=120)
            if not message:
                continue

            message = json.loads(message.decode())
            self.logger.info("Received message: {}".format(message))
            self.thread_pool_executor.submit(self._parse_message, message)