import base64
import datetime
import os
import traceback

from scripts.config import app_constants
from scripts.config.app_constants import DBMapping as Dbm
from scripts.logging.logger import logger
from scripts.utils.get_new_id import GetNewId
from scripts.utils.ilens_metadata_util import CommonUtils
from scripts.utils.mongo_utility import MongoConnect

cmn_utils_obj = CommonUtils()
thing_collection = app_constants.DBMapping.thing
db_ilens_hmi = app_constants.DBMapping.ilens_hmi_db
svg_file_details_collection = app_constants.DBMapping.svg_file_details
scada_folder_details = app_constants.DBMapping.scada_folder_details


class ThingHandler(object):
    def __init__(self):
        self.mongo_obj = MongoConnect()
        self.common_utils_obj = CommonUtils()

    def fetch_thing_details(self, input_json):
        try:
            final_json = {"status": "success", "data": {}}
            if "thing_id" in input_json:
                validation_status = self.validate_thing_id(input_json["thing_id"])
                if validation_status["status"] == "success":
                    thing_record = self.mongo_obj.find_one(db_name=db_ilens_hmi,
                                                           collection_name=thing_collection,
                                                           query={"metadata.thing_id": input_json["thing_id"]},
                                                           search_json={"_id": 0,
                                                                        "metadata": 1
                                                                        }
                                                           )
                    asset_id = thing_record["metadata"]["image_content"]["asset_file_id"]
                    current_path = os.getcwd()
                    if not str(thing_record["metadata"]["path"]["key"]).startswith("/"):
                        final_path = current_path + "/" + thing_record["metadata"]["path"]["key"]
                    else:
                        final_path = current_path + thing_record["metadata"]["path"]["key"]
                    logger.debug("final path to fetch asset for a thing: " + str(final_path))
                    if not os.path.exists(final_path):
                        return {"status": "failed",
                                "message": "unable to fetch thing because the asset path not found"}
                    if "assetType" in thing_record["metadata"] and \
                            thing_record["metadata"]["assetType"] in ['image/svg+xml', 'image/png',
                                                                      'image/jpg', 'image/gif',
                                                                      'image/jpeg']:
                        if thing_record["metadata"]["assetType"] == "image/svg+xml":
                            file_name = asset_id + ".svg"
                        elif thing_record["metadata"]["assetType"] == "image/png":
                            file_name = asset_id + ".png"
                        elif thing_record["metadata"]["assetType"] == "image/jpg":
                            file_name = asset_id + ".jpg"
                        elif thing_record["metadata"]["assetType"] == "image/gif":
                            file_name = asset_id + ".gif"
                        elif thing_record["metadata"]["assetType"] == "image/jpeg":
                            file_name = asset_id + ".jpg"
                        else:
                            return {"status": "failed",
                                    "message": "unable to fetch thing as asset extension is not valid"}
                    else:
                        return {"status": "failed", "message": "unable to fetch thing, no asset extension found"}
                    if not final_path.endswith("/"):
                        file_path = final_path + "/" + file_name
                    else:
                        file_path = final_path + file_name
                    with open(file_path, "r") as f:
                        file_content = f.read()
                        file_content = base64.b64encode(file_content.encode()).decode()
                    thing_record.update(thing_record["metadata"])
                    thing_record.pop("metadata")
                    thing_record.update({"preview_image": file_content})
                    final_json["data"] = thing_record
                else:
                    return {"status": "failed",
                            "message": "no thing exists with the given thing_id",
                            "data": {}}
            else:
                return {"status": "failed", "message": "thing_id is missing in input"}
            return final_json
        except Exception as e:
            logger.exception("Exception while fetching thing" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to fetch thing"}

    def fetch_property_list(self, input_json):
        try:
            final_json = {"status": "success", "data": {}}
            if "thing_id" in input_json and "property_category_id" in input_json:
                validation_status = self.validate_thing_id(input_json["thing_id"])
                if validation_status["status"] == "success":
                    search_json = {"_id": 0,
                                   "categories." + input_json["property_category_id"] + ".properties": 1}
                    thing_record = self.mongo_obj.find_one(db_name=db_ilens_hmi,
                                                           collection_name=thing_collection,
                                                           query={"metadata.thing_id": input_json["thing_id"]},
                                                           search_json=search_json
                                                           )
                    if input_json["property_category_id"] in thing_record["categories"]:
                        thing_record = thing_record["categories"][input_json["property_category_id"]]
                    else:
                        return {"status": "failed", "message": "property category doesn't exists in the given thing"}
                    final_json["data"] = thing_record
                else:
                    return {"status": "failed",
                            "message": "no thing exists with the given thing_id",
                            "data": {}}
            else:
                return {"status": "failed", "message": "thing_id or property_category_id is missing in input"}
            return final_json
        except Exception as e:
            logger.exception("Exception while fetching thing" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to fetch thing"}

    def save_thing(self, input_json):
        """
        :param input_json: input json from UI
        :return:
        """
        try:
            if "edit" in input_json["metadata"] and input_json["metadata"]["edit"] is True:
                field_list = app_constants.RequiredFields.thing_edit
                # doing field and space validation for the keys which is having value of type "str" only
                required_filed_validator_status = self.common_utils_obj.field_validator_with_empty_space(field_list,
                                                                                                         input_json)
                if required_filed_validator_status is not None:
                    return {"status": "failed", "message": required_filed_validator_status + " key is missing",
                            "data": {}}
                if not self.duplicate_name_check(metadata=input_json["metadata"], edit=True):
                    return {"status": "failed", "message": "thing name already exists"}
                return_message = self.update_thing_and_return_message(input_json=input_json)
                return return_message
            field_list = app_constants.RequiredFields.thing_create
            # doing field and space validation for the keys which is having value of type "str" only
            required_filed_validator_status = self.common_utils_obj.field_validator_with_empty_space(field_list,
                                                                                                     input_json)
            if required_filed_validator_status is not None:
                return {"status": "failed", "message": required_filed_validator_status + " key is missing",
                        "data": {}}
            if not self.duplicate_name_check(metadata=input_json["metadata"]):
                return {"status": "failed", "message": "thing name already exists"}
            new_id = "thing_" + GetNewId.get_next_id("thing")
            input_json["metadata"]["thing_id"] = new_id
            response_to_check_user_id_in_cookies = self.common_utils_obj.get_user_id(input_json)
            if response_to_check_user_id_in_cookies["status"] == "success":
                user_id = response_to_check_user_id_in_cookies["user_id"]
                user_name = self.common_utils_obj.get_user_name_for_user_id(user_id=user_id)
            else:
                return {"status": "failed", "message": "user not authorized"}
            input_json["created_by"] = user_id
            input_json["updated_by"] = user_name
            input_json["created_on"] = input_json["updated_on"] = datetime.datetime.now().strftime("%d %b, %Y %I:%M %p")
            # response_to_check_client_id_in_cookies = self.data_security_obj.get_client_id(input_json)
            # if response_to_check_client_id_in_cookies["status"] == "success":
            #     client_id = response_to_check_client_id_in_cookies["client_id"]
            # else:
            #     return {"status": "failed", "message": "user not authorized"}
            input_json["metadata"]["name"] = input_json["metadata"]["name"].strip(" ")
            # input_json["client_id"] = client_id
            input_json["is_deleted"] = "false"
            if self.asset_create(meta_data=input_json["metadata"]):
                if "preview_image" in input_json["metadata"]:
                    input_json["metadata"].pop("preview_image")
                self.mongo_obj.database_insertion(db_name=db_ilens_hmi,
                                                  collection_name=thing_collection,
                                                  query_json=input_json)
                return {"status": "success", "message": "thing created successfully",
                        "data": {"thing_name": input_json["metadata"]["name"],
                                 "thing_id": new_id}}
            else:
                return {"status": "failed", "message": "unable to create asset for this svg"}
        except Exception as e:
            logger.exception("Exception while saving thing:" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to create thing"}

    def update_thing_and_return_message(self, input_json):
        try:
            input_json["metadata"]["name"] = input_json["metadata"]["name"].strip(" ")
            query_json = {"metadata.thing_id": input_json["metadata"]["thing_id"]}
            response_to_check_user_id_in_cookies = self.common_utils_obj.get_user_id(input_json)
            if response_to_check_user_id_in_cookies["status"] == "success":
                user_id = response_to_check_user_id_in_cookies["user_id"]
                user_name = self.common_utils_obj.get_user_name_for_user_id(user_id=user_id)
            else:
                user_name = ""
            update_json = {
                "metadata": input_json["metadata"],
                "categories": input_json["categories"],
                "updated_on": datetime.datetime.now().strftime("%d %b, %Y %I:%M %p"),
                "updated_by": user_name,
            }
            validation_status = self.validate_thing_id(input_json["metadata"]["thing_id"])
            if validation_status["status"] == "success":
                if self.asset_create(meta_data=input_json["metadata"]):
                    if "preview_image" in input_json["metadata"]:
                        input_json["metadata"].pop("preview_image")
                else:
                    return {"status": "failed",
                            "message": "unable to update the asset",
                            "data": {}}
                self.mongo_obj.update_one(db_name=db_ilens_hmi,
                                          collection_name=thing_collection,
                                          query=query_json,
                                          set_json=update_json,
                                          upsert=True)
                return {"status": "success", "message": "thing updated successfully",
                        "data": {"thing_name": input_json["metadata"]["name"],
                                 "thing_id": input_json["metadata"]["thing_id"]}}
            else:
                return {"status": "failed",
                        "message": "no thing exists with the given thing_id",
                        "data": {}}
        except Exception as e:
            return {"status": "failed",
                    "message": "failed to update thing:" + str(e),
                    "data": {}}

    def validate_thing_id(self, thing_id):
        try:
            query_json = {"metadata.thing_id": thing_id}
            if self.mongo_obj.find_record_count(db_name=db_ilens_hmi,
                                                collection_name=thing_collection,
                                                query_json=query_json,
                                                ) > 0:
                return {"status": "success"}
            else:
                return {"status": "failed"}
        except Exception as e:
            logger.exception("exception while validating the thing id:" + str(e))
            return {"status": "failed"}

    def asset_create(self, meta_data):
        """
        this will create a new asset in the required path and it will insert the record in the svg_file_details record
        :param meta_data: meta information about thing
        :return: status about asset creation
        """
        try:
            if "asset_file_id" not in meta_data["image_content"] or meta_data["image_content"]["asset_file_id"] == "":
                file_content = meta_data["preview_image"]
                file_path = meta_data["path"]["key"]
                asset_file_id = "asset_file_" + GetNewId.get_next_id("svg_file")
                asset_file_name = self.get_asset_file_name(meta_data, asset_file_id)
                if asset_file_name is None:
                    return False
                meta_data["image_content"]["asset_file_id"] = asset_file_id
                current_path = os.getcwd()
                if not file_path.endswith("/"):
                    file_path += "/"
                final_path = current_path + "/" + file_path + asset_file_name
                data = base64.b64decode(file_content)
                if not os.path.exists(current_path + file_path):
                    os.mkdir(current_path + file_path)
                with open(final_path, "wb") as f:
                    f.write(data)
                final_json = self.get_asset_json(meta_data)
                if final_json is None:
                    return False
                self.mongo_obj.database_insertion(db_name=db_ilens_hmi,
                                                  collection_name=svg_file_details_collection,
                                                  query_json=final_json)
                return True
            else:
                asset_data = self.mongo_obj.find_one(db_name="ilens_hmi", collection_name=svg_file_details_collection,
                                                     query={
                                                         "asset_file_id": meta_data["image_content"]["asset_file_id"]},
                                                     search_json={"_id": 0})
                if asset_data is not None:
                    if "asset_path" in asset_data:
                        old_path = asset_data["asset_path"]
                        new_path = meta_data["path"]["key"]
                        if old_path == new_path:
                            final_json = self.get_asset_json(meta_data)
                            if final_json is None:
                                return False
                            self.mongo_obj.update_one(db_name=db_ilens_hmi,
                                                      collection_name=svg_file_details_collection,
                                                      query={
                                                          "asset_file_id": meta_data["image_content"]["asset_file_id"]},
                                                      set_json=final_json
                                                      )
                            return True
                        else:
                            file_content = meta_data["preview_image"]
                            file_path = meta_data["path"]["key"]
                            asset_file_id = asset_data["asset_file_id"]
                            asset_file_name = self.get_asset_file_name(meta_data, asset_file_id)
                            if asset_file_name is None:
                                return False
                            current_path = os.getcwd()
                            if not file_path.endswith("/"):
                                file_path += "/"
                            final_path = current_path + "/" + file_path + asset_file_name
                            old_asset_file_name = self.get_asset_file_name(meta_data, asset_file_id)
                            if not str(old_path).endswith("/"):
                                old_path += "/"
                            old_path_to_delete_old_asset = current_path + "/" + old_path + old_asset_file_name
                            os.remove(old_path_to_delete_old_asset)
                            data = base64.b64decode(file_content)
                            if not os.path.exists(current_path + file_path):
                                os.mkdir(current_path + file_path)
                            with open(final_path, "wb") as f:
                                f.write(data)
                            final_json = self.get_asset_json(meta_data)
                            if final_json is None:
                                return False
                            self.mongo_obj.update_one(db_name=db_ilens_hmi,
                                                      collection_name=svg_file_details_collection,
                                                      query={
                                                          "asset_file_id": meta_data["image_content"]["asset_file_id"]},
                                                      set_json=final_json
                                                      )
                            return True
                    else:
                        logger.debug("received an asset_file_id from request payload which exists in mongodb but don't"
                                     " have path key")
                        return False

                else:
                    logger.debug("received an asset_file_id from request payload which doesn't exists in mongodb")
                    return False
        except Exception as e:
            logger.exception("exception while creating the asset in the things studio:" + str(e))
            traceback.print_exc()
            return False

    def generate_left_side_bar_json(self, input_data):
        """this function will frame left side bar JSON by taking the path from UI in order to display the folders and
        things
        in that particular path
        :param input_data: path
        :return: left side bar JSON"""
        try:
            search = None
            if "search" in input_data and input_data["search"] != "":
                search = input_data["search"]
            if "folder_path" in input_data and input_data["folder_path"] != "":
                folders_and_assets_json = self.get_folder_and_asset_list(
                    input_data=input_data, search_value=search)
                return folders_and_assets_json
            else:
                return {"status": "failed", "message": "folder path should not be empty"}

        except Exception as e:
            logger.exception("exception while generating left side bar for things studio portal:" + str(e))
            return {"status": "failed", "message": str(e)}

    def get_folder_and_asset_list(self, input_data, search_value):
        """

        :param input_data:
        :param search_value:
        :return:
        """
        assets_list = []
        folder_list = []
        final_json = {"folder": folder_list,
                      "assets": assets_list,
                      "status": "success", "message": "Subdirectories listed successfully"}
        try:
            folder_id_list = []
            asset_id_list = []
            current_path = os.getcwd()
            final_path = current_path + input_data["folder_path"]  # path to search for folders and assets
            folder_and_asset_list = os.listdir(final_path)  # list of folders and assets in the given path
            file_path = str(input_data["folder_path"]).replace("/KLAssets", "")
            for each_folder_or_asset in folder_and_asset_list:
                try:
                    if each_folder_or_asset.startswith("folder"):
                        folder_id_list.append(each_folder_or_asset)
                    else:
                        asset_file_id = str(each_folder_or_asset).split(".")[0]
                        asset_id_list.append(asset_file_id)
                except Exception as e:
                    logger.exception("exception while iterating folders and assets in the given path:" + str(e))
                    continue
            # if search path is root path, we will search among all folders and assets with the search string
            if search_value is None:
                query_json_folder = {"folder_id": {"$in": folder_id_list}}
                query_json_svg = {"asset_file_id": {"$in": asset_id_list}}
            else:
                search_val = search_value.strip(" ")
                query_json_folder = {"folder_name": {"$regex": search_val, "$options": "i"},
                                     "folder_id": {"$in": folder_id_list}}
                query_json_svg = {"asset_name": {"$regex": search_val, "$options": "i"},
                                  "asset_file_id": {"$in": asset_id_list}}
            for each_folder in self.mongo_obj.search_record_by_query2(db_name=db_ilens_hmi,
                                                                      collection_name=scada_folder_details,
                                                                      query_json=query_json_folder,
                                                                      search_option={"_id": 0}):
                try:
                    folder_list.append({"folder_name": each_folder["folder_name"],
                                        "folder_id": each_folder["folder_id"],
                                        "folder_path": file_path})
                except Exception as e:
                    logger.exception("exception while iterating folder list:" + str(e))
                    continue

            for each_asset in self.mongo_obj.search_record_by_query2(db_name=db_ilens_hmi,
                                                                     collection_name=svg_file_details_collection,
                                                                     query_json=query_json_svg,
                                                                     search_option={"_id": 0}):
                try:
                    temp_json = {
                        "asset_file_id": each_asset["asset_file_id"],
                        "asset_name": each_asset["asset_name"],
                        "description": ""}
                    if not ("type" in each_asset and "typeid" in each_asset and "configuration" in each_asset and \
                            "endPoint" in each_asset):
                        continue
                    if "asset_path" in each_asset:
                        asset_file_name = self.get_asset_name(each_asset["asset_file_id"],
                                                              each_asset["asset_extension"])
                        if not each_asset["asset_path"].endswith("/"):
                            path_to_check_asset = current_path + each_asset["asset_path"] + "/" + asset_file_name
                        else:
                            path_to_check_asset = current_path + each_asset["asset_path"] + asset_file_name
                        if not os.path.exists(path_to_check_asset):
                            logger.debug("asset exists in mongodb, but it does'nt exist physically")
                            continue
                        temp_json["path"] = each_asset["asset_path"]
                    else:
                        logger.debug("asset_path not found for :" + str(each_asset["asset_file_id"]))
                        continue
                    if "thing_id" in each_asset:
                        temp_json["thing_id"] = each_asset["thing_id"]
                    else:
                        logger.debug("thing_id not found for :" + str(each_asset["asset_file_id"]))
                        continue
                    assets_list.append(temp_json)
                except Exception as e:
                    logger.exception("exception while iterating asset list:" + str(e))
                    continue
            return final_json
        except Exception as e:
            logger.exception("exception while fetching folder and assets with search option:" + str(e))
            return final_json

    @staticmethod
    def get_asset_file_name(meta_data, asset_file_id):
        """

        :param meta_data:
        :param asset_file_id:
        :return:
        """
        if "assetType" in meta_data and meta_data["assetType"] in ['image/svg+xml', 'image/png',
                                                                   'image/jpg', 'image/gif',
                                                                   'image/jpeg']:
            if meta_data["assetType"] == "image/svg+xml":
                file_name = asset_file_id + ".svg"
            elif meta_data["assetType"] == "image/png":
                file_name = asset_file_id + ".png"
            elif meta_data["assetType"] == "image/jpg":
                file_name = asset_file_id + ".jpg"
            elif meta_data["assetType"] == "image/gif":
                file_name = asset_file_id + ".gif"
            elif meta_data["assetType"] == "image/jpeg":
                file_name = asset_file_id + ".jpg"
            else:
                return None
        else:
            return None
        return file_name

    @staticmethod
    def get_asset_json(meta_data):
        """

        :param meta_data:
        :return:
        """
        try:
            final_json = {
                "asset_name": meta_data["name"],
                "asset_extension": meta_data["assetType"],
                "asset_file_id": meta_data["image_content"]["asset_file_id"],
                "configuration": meta_data["image_content"]["configuration"],
                "endPoint": meta_data["image_content"]["endPoint"],
                "type": meta_data["image_content"]["type"],
                "typeid": meta_data["image_content"]["typeid"],
                "thing_id": meta_data["thing_id"],
                "asset_path": meta_data["path"]["key"]
            }
        except Exception as e:
            logger.exception("exception while forming asset json to insert into mongodb:" + str(e))
            return None
        return final_json

    def duplicate_name_check(self, metadata, edit=False):
        """

        :param metadata:
        :param edit:
        :return:
        """
        try:
            if edit:
                current_path = os.getcwd()
                final_path = current_path + metadata["path"]["key"]
                asset_id_list = []
                for each in os.listdir(final_path):
                    try:
                        if not each.startswith("folder_"):
                            asset_id = each.split(".")[0]
                            if asset_id == metadata["image_content"]["asset_file_id"]:
                                continue
                            asset_id_list.append(asset_id)
                    except Exception as e:
                        logger.exception(str(e))
                        continue
                if len(asset_id_list) == 0:
                    return True
                else:
                    query = {"metadata.name": metadata["name"],
                             "is_deleted": "false",
                             "metadata.image_content.asset_file_id": {"$in": asset_id_list}}
            else:
                current_path = os.getcwd()
                final_path = current_path + metadata["path"]["key"]
                asset_id_list = []
                for each in os.listdir(final_path):
                    try:
                        if not each.startswith("folder_"):
                            asset_id_list.append(each.split(".")[0])
                    except Exception as e:
                        logger.exception(str(e))
                        continue
                if len(asset_id_list) == 0:
                    return True
                else:
                    query = {"metadata.name": metadata["name"],
                             "is_deleted": "false",
                             "metadata.image_content.asset_file_id": {"$in": asset_id_list}}
            record_count = self.mongo_obj.find_record_count(db_name=db_ilens_hmi,
                                                            collection_name=thing_collection,
                                                            query_json=query)
            if record_count > 0:
                return False
            else:
                return True
        except Exception as e:
            logger.exception("exception while validating the duplicate name check:" + str(e))
            return False

    @staticmethod
    def get_asset_name(name, asset_type):
        if asset_type == "image/svg+xml":
            file_name = name + ".svg"
        elif asset_type == "image/png":
            file_name = name + ".png"
        elif asset_type == "image/jpg":
            file_name = name + ".jpg"
        elif asset_type == "image/gif":
            file_name = name + ".gif"
        elif asset_type == "image/jpeg":
            file_name = name + ".jpg"
        else:
            return None
        return file_name

    def list_tag_category(self, input_json):
        """
        this service will fetch list of tag category based on the user requirement
        :param input_json: which will consist of following keys ["columns","order","start","length"] and etc.,
        :return:
        """
        try:
            field_list = app_constants.RequiredFields.tag_category_list  # required fields
            required_filed_validator_status = self.common_utils_obj.field_validator_with_empty_space(field_list,
                                                                                                     input_json)
            if required_filed_validator_status is not None:
                return {"status": "failed", "data": [], "message": required_filed_validator_status + " key is missing"}
            list_of_tuples_to_sort_while_querying_mongo = self.fetch_column_order(
                columns=input_json["columns"], order=input_json["order"])
            if list_of_tuples_to_sort_while_querying_mongo is None:
                return {"status": "failed", "data": [],
                        "message": "failed to list tag categories as order is not proper"}
            final_json = {"status": "success", "data": [], "message": "Tag category list fetched successfully",
                          "records_filtered": 0, "records_total": 0}
            query_json = self.form_query_json(input_json=input_json)
            fetched_records = self.mongo_obj.find_with_sort_and_skip(
                db_name=Dbm.mongo_db_name,
                collection_name=Dbm.tag_category,
                query_json=query_json,
                search_option={"_id": 0, "tag_category_name": 1, "tag_category_id": 1, "description": 1,
                               "tag_category_icon": 1},
                sort_json=list_of_tuples_to_sort_while_querying_mongo,
                skip=int(input_json["start"]),
                limit=int(input_json["length"])
            )
            final_json["data"] = fetched_records
            final_json["records_total"] = self.mongo_obj.find_record_count(
                db_name=Dbm.mongo_db_name,
                collection_name=Dbm.tag_category,
                query_json={})
            if "$and" in query_json and len(query_json["$and"]) > 1:
                final_json["records_filtered"] = len(fetched_records)
            else:
                final_json["records_filtered"] = final_json["records_total"]
            return final_json
        except Exception as e:
            logger.exception(f"Failed to list tag category {e}")
            return {"status": "failed", "message": "unable to list tag category"}

    def fetch_tag_category(self, input_data):
        """
        Definition to save tag category
        :param input_data: will have tag category data
        :return: success response
        """
        final_json = {
            "status": "success",
            "message": " Tag category details fetched successfully",
            "data": {}}
        try:
            logger.debug("Inside the save_tags function")
            if "tag_category_id" in input_data and input_data["tag_category_id"]:
                tag_category_data = self.mongo_obj.find_one(
                    db_name=Dbm.mongo_db_name,
                    collection_name=Dbm.tag_category,
                    query={"tag_category_id": input_data["tag_category_id"]},
                    search_json={"_id": 0})
                # new_category_data=
                final_json["data"].update({"tag_category_name": tag_category_data["tag_category_name"],
                                           "description": tag_category_data.get("description"),
                                           "tag_category_id": tag_category_data["tag_category_id"],
                                           "tag_category_icon": tag_category_data["tag_category_icon"],
                                           "tagsList": []})
                tag_id_list = []
                for each_tag in tag_category_data["tagsList"]:
                    tag_id_list.append(each_tag["tag_id"])
                tag_data = self.mongo_obj.find_many(
                    db_name=Dbm.mongo_db_name,
                    collection_name=Dbm.tags,
                    query_json={"$or": [{"tag_category_id": input_data["tag_category_id"]},
                                        {"id": {"$in": tag_id_list}}]},
                    search_option={"_id": 0})
                unit_data = self.mongo_obj.find_many(
                    db_name=Dbm.mongo_db_name,
                    collection_name=Dbm.units,
                    query_json={},
                    search_option={"_id": 0})
                unit_mapping = {}
                for each_unit in unit_data:
                    try:
                        unit_mapping.update({each_unit["id"]: each_unit["name"]})
                    except Exception as e:
                        logger.exception(f" while iterating units {e}")
                for each_tag_data in tag_data:
                    try:
                        temp_json = {"tag_name": each_tag_data["tag_name"],
                                     "tag_id": each_tag_data["id"],
                                     "unit": unit_mapping.get(each_tag_data["unit"]),
                                     "attributes": []}
                        attribute_list = []
                        category_attribute_list = None
                        for each_tag_in_categ in tag_category_data["tagsList"]:
                            if each_tag_in_categ["tag_id"] == each_tag_data["id"]:
                                category_attribute_list = each_tag_in_categ["attributes"]
                                break
                        if category_attribute_list:
                            for each_attribute in each_tag_data["attributes"]:
                                if each_attribute["attribute_id"] in category_attribute_list:
                                    each_attribute.update(
                                        {"value": category_attribute_list[each_attribute["attribute_id"]]})
                                    attribute_list.append(each_attribute)
                                else:
                                    attribute_list.append(each_attribute)
                        else:
                            attribute_list = each_tag_data.get("attributes", [])
                        temp_json["attributes"].extend(attribute_list)
                        final_json["data"]["tagsList"].append(temp_json)
                    except Exception as e:
                        logger.exception(f" while iterating the tag data of tag category {e}")
                return final_json
            else:
                return {"status": "failed", "message": "Required keys were missing in input"}
        except Exception as e:
            logger.exception(f"Failed to fetch tag category {e}")
            return {"status": "failed", "message": "Failed to fetch tag category"}

    def list_property(self, input_json):
        """
        this method will fetch list of property types based on the user requirement
        :param input_json: which will consist of following keys ["columns","order","start","length"] and etc.,
        :return:
        """
        try:
            field_list = app_constants.RequiredFields.property_list
            required_filed_validator_status = self.common_utils_obj.field_validator_with_empty_space(field_list, input_json)
            if required_filed_validator_status is not None:
                return {"status": "failed", "data": [], "message": required_filed_validator_status + " key is missing"}
            list_of_tuples_to_sort_while_querying_mongo = self.fetch_column_order(
                columns=input_json["columns"], order=input_json["order"])
            if list_of_tuples_to_sort_while_querying_mongo is None:
                return {"status": "failed", "data": [],
                        "message": "failed to list property types as order is not proper"}
            final_json = {"status": "success", "data": [], "message": "property type list fetched successfully",
                          "records_filtered": 0, "records_total": 0}
            query_json = self.form_query_json(input_json=input_json)
            fetched_records = self.mongo_obj.find_with_sort_and_skip(
                db_name=Dbm.mongo_db_name,
                collection_name=Dbm.tags,
                query_json=query_json,
                search_option={"_id": 0, "property_name": 1, "property_type_id": 1, "uom": 1,
                               "property_id": 1, "attribute_list": 1},
                sort_json=list_of_tuples_to_sort_while_querying_mongo,
                skip=int(input_json["start"]),
                limit=int(input_json["length"])
            )
            final_json["data"] = fetched_records
            final_json["records_total"] = self.mongo_obj.find_record_count(
                db_name=Dbm.mongo_db_name, collection_name=Dbm.tags,
                                  query_json={"is_deleted": "false"})
            if len(query_json["$and"]) > 1:
                final_json["records_filtered"] = len(fetched_records)
            else:
                final_json["records_filtered"] = final_json["records_total"]

            return final_json
        except Exception as e:
            logger.exception("Exception" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to list property"}

    @staticmethod
    def fetch_column_order(columns, order):
        """
        it will form json to sort the records while querying mongo to fetch property type list
        :param columns: list of dict where each dict corresponds to each column and it's fields
        :param order: list of single dict like [{"column": 0,"dir": "asc"}]
        :return: {column_name:1} or {column_name:-1} based on the "dir" in the "order"
        """
        try:
            column_name_to_sort = columns[int(order[0]["column"])]["name"]
            if order[0]["dir"] == "asc":
                return_list = [(column_name_to_sort, 1)]
            elif order[0]["dir"] == "desc":
                return_list = [(column_name_to_sort, -1)]
            else:
                return_list = [(column_name_to_sort, 1)]
            return return_list
        except Exception as e:
            logger.exception("failed to fetch column order:" + str(e))
            return None

    @staticmethod
    def form_query_json(input_json):
        query_json = {"$and": []}
        # query_json["$and"].append({"is_deleted": "false"})
        try:
            search_columns = input_json["column_search"]
            for each_column_name, search_value in search_columns.items():
                if search_value.strip(" ") != "":
                    query_json["$and"].append({
                        each_column_name: {"$regex": search_value.strip(" "), "$options": "i"}
                    })
            if not query_json["$and"]:
                query_json = {}
        except Exception as e:
            logger.exception(f"exception while forming query json: {e}")
            return query_json
        return query_json
