import traceback

from flask import request

from scripts.config import app_constants
from scripts.config.app_configuration import SERVICE_ENABLE_SECURITY
from scripts.config.db_connection_obj import ConnectionObj
from scripts.utils.AESEnc import AESCipher
from scripts.logging.logger import logger
from scripts.utils.mongo_utility import MongoConnect

enable_security = SERVICE_ENABLE_SECURITY


class DataSecurity(object):
    def __init__(self):
        if ConnectionObj.mongo_connection_obj is None:
            ConnectionObj.mongo_connection_obj = MongoConnect()
        self.metadata = app_constants.MongoMetadata.metadata_db
        self.user_collection = app_constants.MongoMetadata.user
        self.device_instance = app_constants.MongoMetadata.device_instance
        self.physical_device = app_constants.MongoMetadata.physical_device
        self.gateway_instance = app_constants.MongoMetadata.gateway_instance
        self.industry = app_constants.MongoMetadata.industry

    @staticmethod
    def get_user_role_permission():
        status = False
        try:
            if "user_role" in request.cookies and request.cookies.get("user_role") == "system_admin":
                return True
        except Exception as e:
            traceback.print_exc()
            logger.exception(str(e))
        return status

    @staticmethod
    def get_user_id(input_data=None):
        if "user_id" in request.cookies:
            user_id_from_cookies = request.cookies.get("user_id")
            # if enable_security:
            #     session_id = request.cookies.get("session_id")
            #     key = session_id[:16]
            #     user_id = AESCipher(key).decrypt(user_id_from_cookies)
            # else:
            user_id = user_id_from_cookies
            return {"status": "success", "user_id": user_id}
        else:
            if "user_id" in input_data and input_data["user_id"] != "":
                user_id = input_data["user_id"]
                return {"status": "success", "user_id": user_id}
            else:
                return {"status": "failed", "message": "user_id is missing from input"}

    @staticmethod
    def get_user_role(input_data=None):
        if "user_role" in request.cookies:
            user_role_from_cookies = request.cookies.get("user_role")
            # if enable_security:
            #     session_id = request.cookies.get("session_id")
            #     key = session_id[:16]
            #     user_id = AESCipher(key).decrypt(user_id_from_cookies)
            # else:
            user_role = user_role_from_cookies
            return {"status": "success", "user_role": user_role}
        else:
            if "user_role" in input_data and input_data["user_role"] != "":
                user_role = input_data["user_role"]
                return {"status": "success", "user_role": user_role}
            else:
                return {"status": "failed", "message": "user_role is missing from input"}

    def get_devices_for_user(self, user_id, flag=False, client_id=None):
        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            user_access_sites = user_details["AccessLevel"]["sites"]
            parent_id_list = []
            node_id_list = []
            for each_user_access_site in user_access_sites:
                parent_id_list.append(each_user_access_site["parent_id"])
                node_id_list.append(each_user_access_site["node_id"])
            if client_id:
                mongo_device_assign_query = {
                    "general_info.isdeleted": "false",
                    "general_info.assign_ref.parent_id": {"$in": parent_id_list},
                    "general_info.assign_ref.node_id": {"$in": node_id_list},
                    "client_id": client_id}
            else:
                mongo_device_assign_query = {
                    "general_info.isdeleted": "false",
                    "general_info.assign_ref.parent_id": {"$in": parent_id_list},
                    "general_info.assign_ref.node_id": {"$in": node_id_list}}
            associated_devices = ConnectionObj.mongo_connection_obj.find_many(
                db_name=self.metadata,
                collection_name=self.device_instance,
                query_json=mongo_device_assign_query,
                search_option={"_id": 0})
            if flag:
                return associated_devices
            else:
                device_with_access = []
                for each_associated_device in associated_devices:
                    device_with_access.append(each_associated_device["device_instance_id"])

                return list(set(device_with_access))
        else:
            device_with_access = []
            try:
                associated_devices = ConnectionObj.mongo_connection_obj.fetch_all(db_name=self.metadata,
                                                                                  collection_name=self.device_instance)
                for each_associated_device in associated_devices:
                    device_with_access.append(each_associated_device["device_instance_id"])
            except Exception as e:
                logger.exception(str(e))
                traceback.print_exc()
            return list(set(device_with_access))

    def get_physical_devices_for_user(self, user_id, gate_way_id):
        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            device_with_access = []
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                for each_user_access_site in user_access_sites:

                    mongo_phy_device_assign_query = {
                        "general_info.isdeleted": "false",
                        "general_info.gateway_instance_id": gate_way_id,
                        "general_info.assign_ref.parent_id": each_user_access_site["parent_id"],
                        "general_info.assign_ref.node_id": each_user_access_site["node_id"]

                    }
                    associated_devices = ConnectionObj.mongo_connection_obj.search_record_by_query2(
                        db_name=self.metadata,
                        collection_name=self.physical_device,
                        query_json=mongo_phy_device_assign_query)
                    for each_associated_device in associated_devices:
                        device_with_access.append(each_associated_device["device_instance_id"])

            return list(set(device_with_access))
        else:
            device_with_access = []
            try:
                associated_devices = ConnectionObj.mongo_connection_obj.fetch_all(db_name=self.metadata,
                                                                                  collection_name=self.physical_device)
                for each_associated_device in associated_devices:
                    device_with_access.append(each_associated_device["device_instance_id"])
            except Exception as e:
                logger.exception(str(e))
                traceback.print_exc()
            return list(set(device_with_access))

    def get_sites_for_user(self, user_id):

        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            sites_with_access = []
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                for each_user_access_site in user_access_sites:
                    sites_with_access.append(each_user_access_site["parent_id"])
            return list(set(sites_with_access))
        else:
            user_details_i = ConnectionObj.mongo_connection_obj.fetch_all(self.metadata, self.user_collection)
            sites_with_access = []
            for user_details in user_details_i:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                for each_user_access_site in user_access_sites:
                    sites_with_access.append(each_user_access_site["parent_id"])
            return list(set(sites_with_access))

    def get_device_group_for_user(self, user_id):
        device_group = []
        try:
            users_devices = self.get_devices_for_user(user_id)

            for each_devices in users_devices:
                try:
                    device_detail = ConnectionObj.mongo_connection_obj. \
                        search_record_by_query2(self.metadata, self.device_instance,
                                                {"device_instance_id": each_devices})
                    for each_device_detail in device_detail:
                        try:
                            device_group_detail = each_device_detail["general_info"]["belongs_ref"]
                            for each_device_group_detail in device_group_detail:
                                try:
                                    device_group.append(each_device_group_detail["parent_id"])
                                except Exception as e:
                                    logger.exception(str(e))
                        except Exception as e:
                            logger.exception(str(e))
                except Exception as e:
                    logger.exception(str(e))
        except Exception as e:
            logger.exception(str(e))
        return list(set(device_group))

    def get_gateway_for_user(self, user_id):
        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            gateway_with_access = []
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                for each_user_access_site in user_access_sites:
                    mongo_gateway_assign_query = {
                        "isdeleted": "false",
                        "assign_ref.parent_id": each_user_access_site["parent_id"],
                        "assign_ref.node_id": each_user_access_site["node_id"]}
                    associated_gateway = ConnectionObj.mongo_connection_obj.search_record_by_query2(
                        db_name=self.metadata,
                        collection_name=self.gateway_instance,
                        query_json=mongo_gateway_assign_query)
                    for each_associated_gateway in associated_gateway:
                        if each_associated_gateway["gateway_instance_id"] not in gateway_with_access:
                            gateway_with_access.append(each_associated_gateway["gateway_instance_id"])
            return gateway_with_access
        else:
            gateway_with_access = []
            try:
                associated_gateway = ConnectionObj.mongo_connection_obj. \
                    fetch_all(db_name=self.metadata, collection_name=self.gateway_instance)
                for each_associated_gateway in associated_gateway:
                    if each_associated_gateway["gateway_instance_id"] not in gateway_with_access:
                        gateway_with_access.append(each_associated_gateway["gateway_instance_id"])
            except Exception as e:
                logger.exception(str(e))
                traceback.print_exc()
            return gateway_with_access

    def get_industry_for_user(self, user_id, delete_status=None):
        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            industry_with_access = []
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                for each_user_access_site in user_access_sites:
                    # if not delete_status:
                    #     mongo_gateway_assign_query = {
                    #         "isdeleted": "false",
                    #         "industry_id": each_user_access_site["parent_id"],
                    #         "site_structure.node_id": each_user_access_site["node_id"]}
                    # else:
                    #     mongo_gateway_assign_query = {
                    #         "industry_id": each_user_access_site["parent_id"],
                    #         "site_structure.node_id": each_user_access_site["node_id"]}
                    # associated_industry = ConnectionObj.mongo_connection_obj.search_record_by_query2(
                    #     db_name=self.metadata,
                    #     collection_name=self.industry,
                    #     query_json=mongo_gateway_assign_query)
                    # for each_associated_industry in associated_industry:
                    #     if each_associated_industry["industry_id"] not in industry_with_access:
                    #         industry_with_access.append(each_associated_industry["industry_id"])
                    industry_with_access.append(each_user_access_site["parent_id"])
            return list(set(industry_with_access))
        else:
            industry_with_access = []
            try:
                associated_industry = ConnectionObj.mongo_connection_obj.fetch_all(db_name=self.metadata,
                                                                                   collection_name=self.industry)
                for each_associated_industry in associated_industry:
                    if each_associated_industry["industry_id"] not in industry_with_access:
                        industry_with_access.append(each_associated_industry["industry_id"])
            except Exception as e:
                logger.exception(str(e))
                traceback.print_exc()
            return industry_with_access

    def get_user_site_data(self, user_id):
        if not self.get_user_role_permission():
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                temp_node_data = []
                children = []
                for each_user_access_site in user_access_sites:
                    try:
                        each_industry = ConnectionObj.mongo_connection_obj. \
                            find_one(self.metadata, self.industry,
                                     {"industry_id": each_user_access_site["parent_id"]},
                                     {"_id": 0})
                        try:
                            for sub_record in each_industry["site_structure"]:
                                if each_user_access_site["node_id"] == sub_record["node_id"]:
                                    children.append(sub_record)
                        except Exception as e:
                            logger.exception(str(e))
                        temp_node_data.append({"node_id": each_industry["industry_id"],
                                               "parent_id": each_industry["industry_id"], "type": "site",
                                               "name": each_industry["industry_name"],
                                               "children": children})
                    except Exception as e:
                        logger.exception(str(e))
                return temp_node_data
        else:
            user_details_i = ConnectionObj.mongo_connection_obj.fetch_all(self.metadata, self.user_collection)
            temp_node_data = []
            for user_details in user_details_i:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
                children = []
                for each_user_access_site in user_access_sites:
                    try:
                        each_industry = ConnectionObj.mongo_connection_obj. \
                            find_one(self.metadata, self.industry,
                                     {"industry_id": each_user_access_site["parent_id"]},
                                     {"_id": 0})
                        try:
                            for sub_record in each_industry["site_structure"]:
                                if each_user_access_site["node_id"] == sub_record["node_id"]:
                                    children.append(sub_record)
                        except Exception as e:
                            logger.exception(str(e))
                        temp_node_data.append({"node_id": each_industry["industry_id"],
                                               "parent_id": each_industry["industry_id"], "type": "site",
                                               "name": each_industry["industry_name"],
                                               "children": children})
                    except Exception as e:
                        logger.exception(str(e))
            return temp_node_data

    def get_user_data(self):
        if not self.get_user_role_permission():
            user_response = self.get_user_id({})
            user_access_sites = []
            if "status" in user_response and user_response["status"] == "success":
                user_id = user_response["user_id"]
            else:
                return user_access_sites
            user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                       {"user_id": user_id})
            if user_details is not None:
                user_access_level = user_details["AccessLevel"]
                user_access_sites = user_access_level["sites"]
            return user_access_sites
        else:
            user_access_sites = []
            user_details_i = ConnectionObj.mongo_connection_obj.fetch_all(self.metadata, self.user_collection)
            for user_details in user_details_i:
                user_access_level = user_details["AccessLevel"]
                if user_access_level["sites"] not in user_access_sites:
                    user_access_sites += user_access_level["sites"]
            return user_access_sites

    @staticmethod
    def get_client_id(input_data=None):
        if "client_id" in request.cookies:
            client_id_from_cookies = request.cookies.get("client_id")
            # if enable_security:
            #     session_id = request.cookies.get("session_id")
            #     key = session_id[:16]
            #     client_id = AESCipher(key).decrypt(client_id_from_cookies)
            # else:
            client_id = client_id_from_cookies
            # on login service itself we are checking client that user belongs has disabled or not
            # if client_id:
            #     client_record = ConnectionObj.mongo_connection_obj.find_one(app_constants.MongoMetadata.metadata_db,
            #                                                                 app_constants.MongoMetadata.client,
            #                                                                 {"client_id": client_id})
            #     if client_record["isdeleted"] == "true":
            #         return {"status": "failed", "message": "Disabled client"}
            return {"status": "success", "client_id": client_id}
        else:
            if "client_id" in input_data:
                client_id = input_data["client_id"]
                # if client_id:
                #     client_record = ConnectionObj.mongo_connection_obj.find_one(
                #     app_constants.MongoMetadata.metadata_db,
                #                                                                 app_constants.MongoMetadata.client,
                #                                                                 {"client_id": client_id})
                #     if client_record["isdeleted"] == "true":
                #         return {"status": "failed", "message": "Disabled client"}
                return {"status": "success", "client_id": client_id}
            else:
                return {"status": "failed", "message": "Unauthorized client"}

    def get_gateway_for_client(self, client_id):
        user_details = ConnectionObj.mongo_connection_obj.find_one(self.metadata, self.user_collection,
                                                                   {"client_id": client_id})
        gateway_with_access = []
        if user_details is not None:
            user_access_level = user_details["AccessLevel"]
            user_access_sites = user_access_level["sites"]
            for each_user_access_site in user_access_sites:
                try:
                    mongo_gateway_assign_query = {
                        "isdeleted": "false",
                        "assign_ref.parent_id": each_user_access_site["parent_id"],
                        "assign_ref.node_id": each_user_access_site["node_id"],
                        "client_id": client_id}
                    associated_gateway = ConnectionObj.mongo_connection_obj.search_record_by_query2(
                        db_name=self.metadata,
                        collection_name=self.gateway_instance,
                        query_json=mongo_gateway_assign_query)
                    for each_associated_gateway in associated_gateway:
                        if each_associated_gateway["gateway_instance_id"] not in gateway_with_access:
                            gateway_with_access.append(each_associated_gateway["gateway_instance_id"])
                except Exception as e:
                    logger.exception(str(e))
                    continue
        return gateway_with_access

    def get_client_data(self, input_data):
        user_response = self.get_client_id(input_data)
        user_access_sites = []
        if "status" in user_response and user_response["status"] == "success":
            client_id = user_response["client_id"]
        else:
            return user_access_sites
        if not client_id:
            user_details = ConnectionObj.mongo_connection_obj.fetch_all(self.metadata, self.user_collection)
        else:
            user_details = ConnectionObj.mongo_connection_obj.search_record_by_query(self.metadata,
                                                                                     self.user_collection,
                                                                                     {"client_id": client_id})
        # print("user_details", user_details)
        if user_details is not None:
            for each_user in user_details:
                user_access_level = each_user["AccessLevel"]
                user_access_sites += user_access_level["sites"]
        return user_access_sites

    def get_industry_for_client(self, user_id):
        user_details = ConnectionObj.mongo_connection_obj.search_record_by_query(self.metadata, self.user_collection,
                                                                                 {"user_id": user_id})
        industry_with_access = []
        if user_details is not None:
            user_access_level = user_details["AccessLevel"]
            user_access_sites = user_access_level["sites"]
            for each_user_access_site in user_access_sites:
                mongo_gateway_assign_query = {
                    "isdeleted": "false",
                    "industry_id": each_user_access_site["parent_id"],
                    "site_structure.node_id": each_user_access_site["node_id"]}
                associated_industry = ConnectionObj.mongo_connection_obj. \
                    search_record_by_query2(db_name=self.metadata, collection_name=self.industry,
                                            query_json=mongo_gateway_assign_query)
                for each_associated_industry in associated_industry:
                    if each_associated_industry["industry_id"] not in industry_with_access:
                        industry_with_access.append(each_associated_industry["industry_id"])
        return industry_with_access

    def get_user_name_for_user_id(self, user_id):
        user_name = ""
        try:
            user_name_object = ConnectionObj.mongo_connection_obj.find_one(db_name=self.metadata,
                                                                           collection_name=self.user_collection,
                                                                           query={"user_id": user_id},
                                                                           search_json={"_id": 0, "username": 1})
            if user_name_object is not None:
                return user_name_object["username"]
            else:
                return user_name
        except Exception as e:
            logger.exception("exception while fetching user name for the given user_id:" + str(e))
            return user_name

    def get_site_hierarchy_for_user(self, input_data, user_id):
        site_hierarchy = []
        try:
            logger.info("Inside the get_site_hierarchy_for_user definition")
            user_details = ConnectionObj.mongo_connection_obj.find_one(db_name="ilens_configuration",
                                                                       collection_name="user",
                                                                       query={"user_id": user_id})
            if user_details is not None:
                sites_with_access = dict()
                if "AccessLevel" in user_details and user_details["AccessLevel"] and "sites" in \
                        user_details["AccessLevel"] and user_details["AccessLevel"]["sites"]:
                    for each_site in user_details["AccessLevel"]["sites"]:
                        try:
                            if each_site["parent_id"] not in sites_with_access:
                                sites_with_access[each_site["parent_id"]] = []
                            if each_site["node_id"] == each_site["parent_id"]:
                                continue
                            if each_site["node_id"] not in sites_with_access[each_site["parent_id"]]:
                                sites_with_access[each_site["parent_id"]].append(each_site["node_id"])
                        except Exception as e:
                            logger.exception("Exception in the for loop" + str(e))

                if sites_with_access:
                    for each_key_site, each_value_site in sites_with_access.items():
                        try:
                            site_data = ConnectionObj.mongo_connection_obj.find_one(db_name="ilens_configuration",
                                                                                    collection_name="site_conf",
                                                                                    query={"site_id": each_key_site,"customer_project_id":input_data["customer_project_id"]})
                            if site_data:
                                temp_site_data = {"node_id": each_key_site,
                                                  "parent_id": each_key_site,
                                                  "type": "site",
                                                  "name": site_data["site_name"],
                                                  "is_visible": True}
                                site_hierarchy.append(temp_site_data)
                        except Exception as e:
                            logger.exception("Exception in the for loop" + str(e))

            else:
                logger.debug("User not found.")
            logger.info("get_site_hierarchy_for_user definition returns")
        except Exception as e:
            logger.exception("Exception in the get_site_hierarchy_for_user definition" + str(e))
            traceback.print_exc()
        return site_hierarchy

    @staticmethod
    def get_data(final_json, data, selected_node, parent_id):
        is_visible = False
        for each_data in data:
            if "children" not in each_data:
                if each_data["node_id"] in selected_node:
                    is_visible = True
                final_json.append({"node_id": each_data["node_id"], "name": each_data["name"],
                                   "id": each_data["id"], "data": each_data["data"], "parent_id": parent_id,
                                   "type": each_data["type"], "is_visible": is_visible})
            else:
                temp_list = DataSecurity().get_data([], each_data["children"], selected_node, parent_id)
                if temp_list:
                    if each_data["node_id"] in selected_node:
                        is_visible = True
                    tmp_json = {"node_id": each_data["node_id"], "name": each_data["name"],
                                "id": each_data["id"], "data": each_data["data"], "parent_id": parent_id,
                                "type": each_data["type"], "is_visible": is_visible, "children": []}
                    tmp_json["children"] += temp_list
                    final_json.append(tmp_json)
                else:
                    if each_data["node_id"] in selected_node:
                        is_visible = True
                    final_json.append({"node_id": each_data["node_id"], "name": each_data["name"],
                                       "id": each_data["id"], "data": each_data["data"], "parent_id": parent_id,
                                       "type": each_data["type"], "is_visible": is_visible})
        return final_json

    def get_client_and_user_details(self):
        try:
            client_and_user_details = dict()
            if "client_id" in request.cookies:
                client_data = self.get_client_id()
                client_id = client_data["client_id"]
                if client_id:
                    client_details = ConnectionObj.mongo_connection_obj.find_one(
                        db_name=app_constants.MongoMetadata.metadata_db,
                        collection_name=app_constants.MongoMetadata.client,
                        query={"client_id": client_id},
                        search_json={"_id": 0})
                    if "isdeleted" in client_details and client_details["isdeleted"] == "true":
                        return {"status": "failed", "message": "Disabled client"}
                client_and_user_details.update({"client_id": client_id})
            else:
                return {"status": "failed", "message": "client not authorized"}
            if "user_id" in request.cookies:
                user_data = self.get_user_id()
                user_id = user_data["user_id"]
                if user_id:
                    user_record = ConnectionObj.mongo_connection_obj.find_one(
                        db_name=app_constants.MongoMetadata.metadata_db,
                        collection_name=app_constants.MongoMetadata.user,
                        query={"user_id": user_id},
                        search_json={"_id": 0})
                    if "isdeleted" in user_record and user_record["isdeleted"] == "true":
                        return {"status": "failed", "message": "Disabled user"}
                client_and_user_details.update({"user_id": user_id})
            else:
                return {"status": "failed", "message": "user_id is missing from input"}
            return client_and_user_details
        except Exception as e:
            logger.exception(" exception occurred while fetching user and client details: " + str(e))
            return {"status": "failed", "message": " unable to fetch client_id and user_id"}

    @staticmethod
    def duplicate_name_check(edit=False, input_data=None):
        try:
            if input_data is None:
                input_data = {}
            if edit:
                # on edit we need to get unique_id key which has to be searched and it's value
                # on edit we need to get name key which has to be searched and it's value
                # on edit we need to get collection_name with collection_name as key in input_data
                # on edit we need to get database_name with database_name as key in input_data
                query_json = {input_data["unique_id_key"]: {"$nin": [input_data["unique_id_value"]]},
                              input_data["name_key"]: input_data["name_value"]}
                count = ConnectionObj.mongo_connection_obj.find_record_count(db_name=input_data["database_name"],
                                                                             collection_name=input_data[
                                                                                 "collection_name"],
                                                                             query_json=query_json)
                if count > 0:
                    return {"status": "failed",
                            "message": "{name} name already exists".format(name=input_data["name_key"])}
            else:
                query_json = {input_data["name_key"]: input_data["name_value"]}
                count = ConnectionObj.mongo_connection_obj.find_record_count(db_name=input_data["database_name"],
                                                                             collection_name=input_data[
                                                                                 "collection_name"],
                                                                             query_json=query_json)
                if count > 0:
                    return {"status": "failed",
                            "message": "{name} name already exists".format(name=input_data["name_key"])}
            return {"status": "success"}
        except Exception as e:
            logger.exception(" exception while duplicating name check: " + str(e))
            return {"status": "failed", "message": "name already exists"}
