import datetime
import json
import traceback
from copy import deepcopy

import requests
from redis import Redis

from scripts.config import app_configuration
from scripts.config.app_constants import DBMapping as Dbm
from scripts.config.db_connection_obj import ConnectionObj
from scripts.logging.logger import logger
from scripts.utils.config_utlity import NewConfigUtility
from scripts.utils.get_new_id import GetNewId
from scripts.utils.mongo_utility import MongoConnect


class RuleConfigurationHandler(object):
    def __init__(self):
        self.mongo_obj = ConnectionObj.mongo_connection_obj
        if ConnectionObj.mongo_connection_obj is None:
            self.mongo_obj = ConnectionObj.mongo_connection_obj = MongoConnect()
        self.next_id = GetNewId()
        self.utility = NewConfigUtility()
        self.rules_redis = Redis(host=app_configuration.redis_host, port=app_configuration.redis_port,
                                 db=app_configuration.rules_redis_db)

    def create_rule_engine(self, input_data):
        """
        this will function will save rule meta data
        """
        try:
            user_id = self.utility.get_usr_id(input_data=input_data)
            if not user_id:
                return {"status": "failed", "message": "Unauthorized user"}
            edit_flag = True
            if "rule_engine_id" in input_data and input_data["rule_engine_id"] == "":
                rule_name_exists = self.duplicate_rule_name_check(input_data=input_data)
                if rule_name_exists:
                    return {"status": "failed", "message": "Rule name already exists!!"}
                input_data["rule_engine_id"] = "rule_engine_" + GetNewId.get_next_id("rule_engine")
                edit_flag = False
                input_data["created_by"] = user_id
            if "calcFormulaList" in input_data:
                counter = 1
                for each_calc_formula_list in input_data["calcFormulaList"]:
                    each_calc_formula_list.update({"block_id": "block_" + str(counter)})
                    json_to_redis = self.make_json_to_push_into_redis(rule_block_data=each_calc_formula_list,
                                                                      input_data=input_data)
                    if counter >= 2 and edit_flag:
                        edit_flag = False
                    if "previousTags" in each_calc_formula_list:
                        previous_tags = each_calc_formula_list["previousTags"]
                        status = self.remove_removed_tags(previous_tags, each_calc_formula_list["selectedTags"],
                                                          input_data["rule_engine_id"])
                        if status:
                            logger.debug("Removed the unwanted tags from redis")
                        else:
                            logger.debug("Error while removing unwanted tags")
                    self.insert_into_redis(json_to_redis=json_to_redis, rule_edit=edit_flag)
                    if input_data["transformation_type"] != "route":
                        query = {"id": each_calc_formula_list["completeTagId"]}
                        result = self.mongo_obj.find_one(db_name=Dbm.mongo_db_name,
                                                         collection_name=Dbm.tags,
                                                         query=query,
                                                         search_json={"_id": 0})
                        if result is not None:
                            if "output_devices" in result:
                                result["output_devices"].extend(each_calc_formula_list.get("output_devices"))
                            else:
                                result["output_devices"] = list(set(each_calc_formula_list["output_devices"]))
                            result["output_devices"] = list(set(result["output_devices"]))
                            self.mongo_obj.update_one(db_name=Dbm.mongo_db_name,
                                                      collection_name=Dbm.tags,
                                                      query=query,
                                                      set_json=result,
                                                      upsert=True)
                        else:
                            return {"status": "failed", "message": "rule output tag doesn't exists"}
                    counter += 1
            input_data["created_on"] = (datetime.datetime.now()).strftime("%d/%m/%Y")
            input_data["last_updated"] = (datetime.datetime.now()).strftime("%d/%m/%Y")
            new_values = input_data
            self.mongo_obj.update_one(db_name=Dbm.mongo_db_name,
                                      collection_name=Dbm.rule_configuration,
                                      query={"rule_engine_id": input_data["rule_engine_id"]},
                                      set_json={"$set": new_values},
                                      upsert=True)
            try:
                if input_data["Selected_ruleType"] == "Schedule":
                    requests.post(app_configuration.SCHEDULER_BASE_URL,
                                  data=json.dumps(input_data), verify=False)
            except Exception as e:
                logger.exception("Exception in the scheduler rule engine call:" + str(e))
            logger.debug("Rule saved successfully")
            return {"status": "success", "message": "rule  saved successfully", "rule_id": input_data["rule_engine_id"]}

        except Exception as e:
            logger.exception("Exception in rule creation:" + str(e))
            # traceback.print_exc()
            return {"status": "failed", "message": "Unable to save rule"}

    def delete_rule_engine(self, input_data):
        """ this function will delete rule data in mongo and REDIS"""
        try:
            rule_data = self.mongo_obj.find_one(db_name=Dbm.mongo_db_name,
                                                collection_name=Dbm.rule_configuration,
                                                query={
                                                    "rule_engine_id": input_data["rule_engine_id"]},
                                                search_json={"_id": 0})
            if rule_data["Selected_ruleType"] == "Schedule":
                job_data = self.mongo_obj.find_one(db_name=Dbm.db_scheduled_rule,
                                                   collection_name=Dbm.scheduled_metadata,
                                                   query={
                                                       "rule_engine_id": input_data["rule_engine_id"]},
                                                   search_json={"_id": 0})
                if job_data:
                    job_id = job_data["jobId"]
                    self.mongo_obj.delete_one_record(query_json={"_id": job_id},
                                                     db_name=Dbm.db_scheduled_rule,
                                                     collection_name=Dbm.scheduled_jobs)
                self.mongo_obj.delete_one_record(
                    query_json={"rule_engine_id": input_data["rule_engine_id"]},
                    db_name=Dbm.db_scheduled_rule, collection_name=Dbm.scheduled_metadata)
                self.mongo_obj.delete_one_record(db_name=Dbm.mongo_db_name,
                                                 collection_name=Dbm.rule_configuration,
                                                 query_json={
                                                     "rule_engine_id": input_data["rule_engine_id"]})
            else:
                self.remove_rule_from_redis(rule_data)
                self.mongo_obj.delete_one_record(db_name=Dbm.mongo_db_name,
                                                 collection_name=Dbm.rule_configuration,
                                                 query_json={
                                                     "rule_engine_id": input_data["rule_engine_id"]})
            logger.debug("rule deleted successfully")
            return {"status": "success", "message": "rule deleted successfully"}
        except Exception as e:
            logger.exception("exceptions while deleting rule:" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "Unable to remove rule"}

    def get_rule_engine_list(self, input_data):
        try:

            if "rule_engine_id" in input_data:
                data = self.mongo_obj.find_one(db_name=Dbm.mongo_db_name,
                                               collection_name=Dbm.rule_configuration,
                                               query={
                                                   "rule_engine_id": input_data["rule_engine_id"]},
                                               search_json={"_id": 0, "created_on": 0,
                                                            "last_updated": 0})
                data["status"] = "success"
                return data
            return_json = {
                "status": "success",
                "headerContent": self.mongo_obj.find_one(db_name=Dbm.mongo_db_name,
                                                         collection_name=Dbm.header,
                                                         query={"header_id": "rule_engine"},
                                                         search_json={"_id": 0})[
                    "data"],
                "bodyContent": []
            }
            # rule_engine_data = self.filter_obj.client_filter_for_client(rule_engine_collection,
            #                                                             client_json["client_id"])
            rule_engine_data = self.mongo_obj.find_many(
                db_name=Dbm.mongo_db_name,
                collection_name=Dbm.rule_configuration,
                query_json={},
                search_option={"_id": 0})
            for each_rule_engine in rule_engine_data:
                try:
                    if "rule_display" in each_rule_engine and each_rule_engine["rule_display"] is False:
                        continue
                    else:
                        return_json["bodyContent"].append({
                            "rule_engine_id": each_rule_engine["rule_engine_id"],
                            "rule_name": each_rule_engine["ruleName"],
                            "description": each_rule_engine.get("deviceDescription", ""),
                            "trigger_type": each_rule_engine["Selected_ruleType"],
                            "created_on": each_rule_engine.get("created_on", ""),
                            "last_updated": each_rule_engine.get("last_updated", "")})
                except Exception as e:
                    logger.exception("Exception while iterating rule data list:" + str(e))
            return return_json

        except Exception as e:
            logger.exception("exceptions while listing rules:" + str(e))
            # traceback.print_exc()
            return {"status": "failed", "message": "Unable to list rules"}

    def lookup_meta_data_for_rule_engine(self):
        try:
            final_json = {"status": "success", "data": []}
            lookup_table_data = self.mongo_obj.find_many(db_name=Dbm.mongo_db_name,
                                                         collection_name=Dbm.lookup_table,
                                                         query_json={},
                                                         search_option={"_id": 0})
            for each_lookup_table in lookup_table_data:
                try:
                    temp_json = {"detail": "", "documentation": "",
                                 "id": each_lookup_table["lookup_id"] + "." + each_lookup_table["lookup_table_id"],
                                 "insertText": {"value": ""}, "kind": 5, "label": ""}
                    lookup_record = self.mongo_obj.find_one(db_name=Dbm.mongo_db_name,
                                                            collection_name=Dbm.lookup,
                                                            query={"lookup_id": each_lookup_table[
                                                                "lookup_id"]},
                                                            search_json={"_id": 0})
                    if lookup_record is not None:
                        lookup_name = lookup_record["name"]
                    else:
                        lookup_name = ""
                    lookup_table_name = each_lookup_table["content"]["name"]
                    temp_json["insertText"]["value"] = lookup_name + " - " + lookup_table_name
                    temp_json["label"] = lookup_name + " - " + lookup_table_name
                    final_json["data"].append(temp_json)
                except Exception as e:
                    logger.exception("exception while fetching lookup tables:" + str(e))
                    traceback.print_exc()
                    continue
            return final_json
        except Exception as e:
            logger.exception("Exception while fetching lookup meta data:" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "Unable to fetch lookup meta data"}

    def filter_data(self, input_data):
        final_json = {"data": [], "status": "failed", "message": "Failed to get manual or derived tags."}
        try:
            site_data = self.mongo_obj.search_record_by_query2(
                db_name=Dbm.mongo_db_name, collection_name=Dbm.site_conf,
                query_json={"site_id": {"$in": list(input_data["data"].keys())}})
            tag_mapping = {}
            query = {}
            for each_tag in self.mongo_obj.search_record_by_query(Dbm.mongo_db_name, Dbm.tags, query, {"_id": 0}):
                try:
                    # if "system_tag_type" in each_tag and each_tag["system_tag_type"] == "derived_tag":
                    #     final_json["data"].append(
                    #         {"value": each_tag["id"],
                    #          "label": each_tag["tag_name"] + "(Derived)"})
                    tag_mapping[each_tag["id"]] = {"name": each_tag["tag_name"],
                                                   "system_tag_type": each_tag.get("system_tag_type")}
                except Exception as e:
                    logger.exception(str(e))

            # tag_mapping = self.utility.fetch_tag()
            for each_site in site_data:
                try:
                    main_list = ["site"]
                    compo_map = {each_site["site_id"]: each_site["site_name"]}
                    for each_component in input_data["data"][each_site["site_id"]]:
                        try:
                            if each_component["node_id"].split("_")[0] == "site":
                                for each_tag in each_site["site_info"]["tags"]:
                                    if tag_mapping[each_tag["value"]]["system_tag_type"] == "derived_tag":
                                        final_json["data"].append(
                                            {"value": each_tag["value"],
                                             "label": tag_mapping[each_tag["value"]]["name"] + "(Derived)"})
                            else:
                                for i in each_site[each_component["node_id"].split("_")[0]]:
                                    try:
                                        if i[each_component["node_id"].split("_")[0] + "_id"] == \
                                                each_component["node_id"]:
                                            component = each_component["id"].split("$")
                                            tag_name = []
                                            for each in component:
                                                try:
                                                    if each.split("_")[0] not in main_list:
                                                        main_list.append(each.split("_")[0])
                                                        for j in each_site[each.split("_")[0]]:
                                                            compo_map[j[each.split("_")[0] + "_id"]] = j[
                                                                each.split("_")[0] + "_name"]
                                                    tag_name.append(compo_map[each])
                                                except Exception as e:
                                                    logger.exception(str(e))
                                            for each_tag in i["tags"]:
                                                try:
                                                    if tag_mapping[each_tag["value"]]["system_tag_type"] == \
                                                            "derived_tag":
                                                        final_json["data"].append(
                                                            {"value": each_tag["value"],
                                                             "label": tag_mapping[each_tag["value"]][
                                                                          "name"] + "(Derived)"})
                                                except Exception as e:
                                                    logger.exception(str(e))
                                    except Exception as e:
                                        logger.exception("Exception->%s" % str(e))
                        except Exception as e:
                            logger.exception(str(e))
                except Exception as e:
                    logger.exception(str(e))
            final_json["status"] = "success"
            final_json["message"] = "Tags fetched successfully"
        except Exception as e:
            logger.exception("Exception->%s" % str(e))
        return final_json

    def fetch_tags_data(self, input_data):
        final_json = {"data": [], "status": "failed", "message": "Failed to get the alarm configuration tags."}
        try:
            site_data = self.mongo_obj.search_record_by_query2(
                db_name=Dbm.mongo_db_name, collection_name=Dbm.site_conf,
                query_json={"site_id": {"$in": list(input_data["data"].keys())}})
            tag_mapping = {}
            for each in self.mongo_obj.search_record_by_query(Dbm.mongo_db_name, Dbm.tags, {}, {"_id": 0}):
                if "tag_name" not in each or "system_tag_type" not in each:
                    continue
                try:
                    tag_mapping[each["id"]] = {"name": each["tag_name"],
                                               "type": each["system_tag_type"]}
                except Exception as e:
                    logger.exception(str(e))
            for each_site in site_data:
                try:
                    main_list = ["site"]
                    compo_map = {each_site["site_id"]: each_site["site_name"]}
                    for each_component in input_data["data"][each_site["site_id"]]:
                        try:
                            if each_component["node_id"].split("_")[0] == "site":
                                for each_tag in each_site["site_info"]["tags"]:
                                    if each_tag["value"] in tag_mapping and tag_mapping[each_tag["value"]]["type"] in ["raw_tag", "derived_tag"]:
                                        final_json["data"].append(
                                            {"id": each_component["node_id"] + "$" + each_tag["value"],
                                             "label": each_site["site_name"] + ":" +
                                                      tag_mapping[each_tag["value"]]["name"],
                                             "kind": 13, "detail": "",
                                             "documentation": "",
                                             "insertText": {
                                                 "value": each_site["site_name"] + ":" +
                                                          tag_mapping[each_tag["value"]]["name"]}
                                             })
                            else:
                                for i in each_site[each_component["node_id"].split("_")[0]]:
                                    try:
                                        if i[each_component["node_id"].split("_")[0] + "_id"] == \
                                                each_component["node_id"]:
                                            component = each_component["id"].split("$")
                                            tag_name = []
                                            for each in component:
                                                try:
                                                    if each.split("_")[0] not in main_list:
                                                        main_list.append(each.split("_")[0])
                                                        for j in each_site[each.split("_")[0]]:
                                                            compo_map[j[each.split("_")[0] + "_id"]] = j[
                                                                each.split("_")[0] + "_name"]
                                                    tag_name.append(compo_map[each])
                                                except Exception as e:
                                                    logger.exception(str(e))
                                            for each_tag in i["tags"]:
                                                try:
                                                    if each_tag["value"] in tag_mapping and \
                                                            tag_mapping[each_tag["value"]]["type"] in ["raw_tag",
                                                                                                       "derived_tag"]:
                                                        final_json["data"].append(
                                                            {"id": each_component["id"].replace(".", "$") + "$" +
                                                                   each_tag["value"],
                                                             "label": ">".join(tag_name) + ":" +
                                                                      tag_mapping[each_tag["value"]]["name"],
                                                             "kind": 13, "detail": "",
                                                             "documentation": "",
                                                             "insertText": {"value": ">".join(tag_name) + ":" +
                                                                                     tag_mapping[each_tag["value"]][
                                                                                         "name"]}
                                                             })
                                                except Exception as e:
                                                    logger.exception(str(e))
                                    except Exception as e:
                                        logger.exception("Exception->%s" % str(e))
                        except Exception as e:
                            logger.exception(str(e))
                except Exception as e:
                    logger.exception(str(e))
            final_json["status"] = "success"
            final_json["message"] = "Tags fetched successfully"
        except Exception as e:
            logger.exception("Exception->%s" % str(e))
        return final_json

    def fetch_tags_for_routing_rules(self, input_data):
        """
        this function will fetch tags for routing rules
        """
        final_json = {"data": [], "status": "failed", "message": "Failed to get tags for routing rules"}
        try:
            site_data = self.mongo_obj.search_record_by_query2(
                db_name=Dbm.mongo_db_name, collection_name=Dbm.site_conf,
                query_json={"site_id": {"$in": list(input_data["data"].keys())}})
            tag_mapping = {}
            for each in self.mongo_obj.search_record_by_query(Dbm.mongo_db_name, Dbm.tags, {}, {"_id": 0}):
                try:
                    tag_mapping[each["id"]] = {"name": each["tag_name"],
                                               "type": each["system_tag_type"]}
                except Exception as e:
                    logger.exception(str(e))
            for each_site in site_data:
                try:
                    main_list = ["site"]
                    compo_map = {each_site["site_id"]: each_site["site_name"]}
                    for each_component in input_data["data"][each_site["site_id"]]:
                        try:
                            if each_component["node_id"].split("_")[0] == "site":
                                for each_tag in each_site["site_info"]["tags"]:
                                    if each_tag["value"] in tag_mapping and \
                                            tag_mapping[each_tag["value"]]["type"] == "raw_tag":
                                        final_json["data"].append(
                                            {"value": each_component["node_id"] + "$" + each_tag["value"],
                                             "label": each_site["site_name"] + ":" +
                                                      tag_mapping[each_tag["value"]]["name"]})

                            else:
                                for i in each_site[each_component["node_id"].split("_")[0]]:
                                    try:
                                        if i[each_component["node_id"].split("_")[0] + "_id"] == \
                                                each_component["node_id"]:
                                            component = each_component["id"].split("$")
                                            tag_name = []
                                            for each in component:
                                                try:
                                                    if each.split("_")[0] not in main_list:
                                                        main_list.append(each.split("_")[0])
                                                        for j in each_site[each.split("_")[0]]:
                                                            compo_map[j[each.split("_")[0] + "_id"]] = j[
                                                                each.split("_")[0] + "_name"]
                                                    tag_name.append(compo_map[each])
                                                except Exception as e:
                                                    logger.exception(str(e))
                                            for each_tag in i["tags"]:
                                                try:
                                                    if each_tag["value"] in tag_mapping and \
                                                            tag_mapping[each_tag["value"]]["type"] == "raw_tag":
                                                        final_json["data"].append(
                                                            {"value": each_component["id"] + "$" + each_tag["value"],
                                                             "label": ":".join(tag_name) + ":" +
                                                                      tag_mapping[each_tag["value"]]["name"]})
                                                except Exception as e:
                                                    logger.exception(str(e))
                                    except Exception as e:
                                        logger.exception("Exception while iterating each node:" + str(e))
                        except Exception as e:
                            logger.exception("Exception while iterating each component:" + str(e))
                except Exception as e:
                    logger.exception("Exception while iterating site data from input:" + str(e))
            final_json["status"] = "success"
            final_json["message"] = "Tags fetched successfully"
        except Exception as e:
            logger.exception("Exception while fetching tags for routing rules:" + str(e))
        return final_json

    @staticmethod
    def make_json_to_push_into_redis(rule_block_data, input_data):
        """
        it will make jsong to push into redis
        """
        try:
            each_block_dict = {"rule_id": input_data["rule_engine_id"],
                               "rule": {"code": rule_block_data.get("code"),
                                        "parsedCode": rule_block_data.get("parsedCode")
                                        },
                               "result_tag_id": rule_block_data.get("completeTagId"),
                               "selectedTags": rule_block_data.get("selectedTags"),
                               "output_devices": rule_block_data.get("output_devices"),
                               "ruleName": input_data.get("ruleName"),
                               "transformation_type": input_data["transformation_type"],
                               "output_type": rule_block_data.get("output_type"),
                               "mqtt": rule_block_data.get("mqtt"),
                               "kafka": rule_block_data.get("kafka"),
                               "rest": rule_block_data.get("rest"),
                               "data_store": rule_block_data.get("data_store"),
                               "output_tags": rule_block_data.get("output_tags"),
                               "block_id": rule_block_data.get("block_id"),
                               "execute": rule_block_data.get("disable", False)
                               }
            return each_block_dict
        except Exception as e:
            logger.exception("unable to make json to push to redis:" + str(e))

    def insert_into_redis(self, json_to_redis, rule_edit=False):
        """
        this function will insert rule data into redis
        """
        try:
            if json_to_redis["transformation_type"] == "route":
                if len(json_to_redis["output_tags"]) > 1:
                    json_to_redis["multi_tag"] = True
                else:
                    json_to_redis["multi_tag"] = False
                tags_used_in_rule = json_to_redis["output_tags"]
            else:
                if len(json_to_redis["selectedTags"]) > 1:
                    json_to_redis["multi_tag"] = True
                else:
                    json_to_redis["multi_tag"] = False
                tags_used_in_rule = json_to_redis["selectedTags"]
            conn_obj = self.rules_redis
            for each_selected_tag in tags_used_in_rule:
                if conn_obj.exists(each_selected_tag):
                    rules_available = conn_obj.hgetall(each_selected_tag)
                    if json_to_redis["rule_id"].encode() in rules_available.keys():
                        if rule_edit:
                            conn_obj.hset(each_selected_tag, json_to_redis["rule_id"], json.dumps([json_to_redis]))
                        else:
                            available_rule = rules_available.get(json_to_redis["rule_id"].encode())
                            available_rule = json.loads(available_rule.decode())
                            rule_list = deepcopy(available_rule)
                            # to update rule block data if it already exists
                            for each_rule in available_rule:
                                if each_rule["rule_id"] == json_to_redis["rule_id"] and each_rule["block_id"] == \
                                        json_to_redis["block_id"]:
                                    rule_list.remove(each_rule)
                            rule_list.append(json_to_redis)
                            rule_list = self.remove_dupe_dicts(list_of_dicts=rule_list)
                            conn_obj.hset(each_selected_tag, json_to_redis["rule_id"], json.dumps(rule_list))

                    else:
                        conn_obj.hset(each_selected_tag, json_to_redis["rule_id"], json.dumps([json_to_redis]))

                else:
                    conn_obj.hset(each_selected_tag, json_to_redis["rule_id"], json.dumps([json_to_redis]))
        except Exception as e:
            logger.exception("unable to insert rule into redis:" + str(e))

    def duplicate_rule_name_check(self, input_data):
        """
        this will check whether that rule already exists while creations
        """
        exists = self.mongo_obj.find_one(query={"ruleName": input_data["ruleName"]},
                                         db_name=Dbm.mongo_db_name,
                                         collection_name=Dbm.rule_configuration)
        if exists is not None:
            return True
        else:
            return False

    @staticmethod
    def remove_dupe_dicts(list_of_dicts):
        list_of_strings = [
            json.dumps(d, sort_keys=True)
            for d in list_of_dicts
        ]
        list_of_strings = set(list_of_strings)
        return [
            json.loads(s)
            for s in list_of_strings
        ]

    def remove_rule_from_redis(self, rule_data):
        """

        """
        try:
            for each_calc_formula_list in rule_data["calcFormulaList"]:
                if rule_data["transformation_type"] == "route":
                    tags_used_in_rule = each_calc_formula_list["output_tags"]
                else:
                    tags_used_in_rule = each_calc_formula_list["selectedTags"]
                conn_obj = self.rules_redis
                for each_selected_tag in tags_used_in_rule:
                    if conn_obj.exists(each_selected_tag):
                        rules_available = conn_obj.hgetall(each_selected_tag)
                        if rule_data["rule_engine_id"].encode() in rules_available.keys():
                            conn_obj.hdel(each_selected_tag, rule_data["rule_engine_id"])
                    else:
                        logger.debug(" tag doesn't exists in REDIS, no need to remove")
        except Exception as e:
            logger.exception("Unable to clear REDIS cache:" + str(e))

    def remove_removed_tags(self, previous_tags, selected_tags, rule_id):
        try:
            removed_tags_from_rule = []
            conn_obj = self.rules_redis
            for each_previous_tag in previous_tags:
                if each_previous_tag not in selected_tags:
                    removed_tags_from_rule.append(each_previous_tag)
            for each_tag in removed_tags_from_rule:
                conn_obj.hdel(each_tag, rule_id)
            return True
        except Exception as e:
            logger.exception("Exception while removing tags from redis :" + str(e))
            return False
