import datetime
import traceback


from scripts.config import app_constants
from scripts.utils.ilens_metadata_util import CommonUtils
from scripts.config.db_connection_obj import ConnectionObj
from scripts.utils.get_new_id import GetNewId
from scripts.logging.logger import logger
from scripts.utils.mongo_utility import MongoConnect

cmn_utils_obj = CommonUtils()
property_group_collection = app_constants.DBMapping.property_group
db_ilens_hmi = app_constants.DBMapping.ilens_hmi_db
property_collection = app_constants.DBMapping.property


class PropertyGroupHandler(object):
    def __init__(self):
        if ConnectionObj.mongo_connection_obj is None:
            ConnectionObj.mongo_connection_obj = MongoConnect()
        self.common_utils_obj = CommonUtils()

    def fetch_property_group(self, input_json):
        try:
            final_json = {"status": "success", "data": {}}
            if "property_group_id" in input_json:
                validation_status = self.validate_property_group_id(input_json["property_group_id"])
                if validation_status["status"] == "success":
                    property_group_record = ConnectionObj.mongo_connection_obj.\
                        find_one(db_name=db_ilens_hmi, collection_name=property_group_collection,
                                 query={"property_group_id": input_json["property_group_id"]},
                                 search_json={"_id": 0,
                                              "property_group_id": 1,
                                              "property_group_name": 1,
                                              "property_list": 1})
                    property_id_list_from_property_group_record = []
                    for each_property_in_property_group in property_group_record["property_list"]:
                        property_id_list_from_property_group_record.append(
                            each_property_in_property_group["property_id"])
                    query_json = {"is_deleted": "false",
                                  "property_id": {"$in": property_id_list_from_property_group_record}}
                    property_list = ConnectionObj.mongo_connection_obj.find_many(db_name=db_ilens_hmi,
                                                                                 collection_name=property_collection,
                                                                                 query_json=query_json,
                                                                                 search_option={"_id": 0,
                                                                                                "property_id": 1,
                                                                                                "property_name": 1,
                                                                                                "uom": 1,
                                                                                                "attribute_list": 1})
                    property_group_record["property_list"] = property_list
                    final_json["data"] = property_group_record
                else:
                    return {"status": "failed",
                            "message": "no property_group exists with the given property_group_id",
                            "data": {}}
            else:
                return {"status": "failed", "message": "property_group_id is missing in input"}
            return final_json
        except Exception as e:
            logger.exception("Exception while fetching property" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to fetch property group"}

    def save_property_group(self, input_json):
        """

        :param input_json: input json from UI
        :return:
        """
        try:
            if "edit" in input_json and input_json["edit"] is True:
                field_list = app_constants.RequiredFields.property_group_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(input_json=input_json, edit=True):
                    return {"status": "failed", "message": "property group name already exists"}
                return_message = self.update_property_group_and_return_message(input_json=input_json)
                return return_message
            field_list = app_constants.RequiredFields.property_group_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(input_json=input_json):
                return {"status": "failed", "message": "property group name already exists"}
            new_id = "property_group_" + GetNewId.get_next_id("property_group")
            input_json["property_group_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["property_group_name"] = input_json["property_group_name"].strip(" ")
            # input_json["client_id"] = client_id
            input_json["is_deleted"] = "false"
            input_json["property_count"] = str(len(input_json["property_list"]))
            ConnectionObj.mongo_connection_obj.database_insertion(db_name=db_ilens_hmi,
                                                                  collection_name=property_group_collection,
                                                                  query_json=input_json)
            return {"status": "success", "message": "property group created successfully",
                    "data": {"property_group_name": input_json["property_group_name"],
                             "property_group_id": new_id}}
        except Exception as e:
            logger.exception("Exception while saving property group:" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to create property group"}

    def list_property_group(self, input_json):
        """
        this method will fetch list of property groups 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_group_list
            required_filed_validator_status = self.common_utils_obj.field_validator_with_empty_space(field_list, input_json)
            if required_filed_validator_status is False:
                return {"status": "failed", "data": [], "message": "required keys are 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 = ConnectionObj.mongo_connection_obj.find_with_sort_and_skip(
                db_name=db_ilens_hmi,
                collection_name=property_group_collection,
                query_json=query_json,
                search_option={"_id": 0, "property_group_name": 1, "property_group_id": 1, "property_count": 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"] = ConnectionObj.mongo_connection_obj.\
                find_record_count(db_name=db_ilens_hmi, collection_name=property_group_collection,
                                  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 delete_property_group(input_json):
        """
        this service will delete the property based on property_id
        :param input_json: property_id
        :return:
        """
        try:
            field_list = app_constants.RequiredFields.property_group_delete
            required_filed_validator_status = cmn_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"}
            if ConnectionObj.mongo_connection_obj.update_one(db_name=db_ilens_hmi,
                                                             collection_name=property_group_collection,
                                                             query={
                                                                 "property_group_id": input_json["property_group_id"]},
                                                             set_json={"is_deleted": "true"}):
                return {"status": "success", "message": "property deleted successfully"}
            else:
                return {"status": "failed", "message": "no property exists with the given property_id"}
        except Exception as e:
            logger.exception("Exception" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to delete property"}

    @staticmethod
    def fetch_properties(input_json):
        """

        :return: list of property types available
        """
        try:
            final_json = {"data": [], "status": "success"}
            search_value = None
            if "search_value" not in input_json:
                return {"status": "failed", "message": "search_value is missing"}
            if "search_value" in input_json:
                input_json["search_value"] = str(input_json["search_value"])
            if "search_value" in input_json and input_json["search_value"].strip(" ") != "":
                search_value = input_json["search_value"].strip(" ")
            if search_value:
                query_json = {"is_deleted": "false",
                              "property_name": {"$regex": search_value, "$options": "i"}}
            else:
                query_json = {"is_deleted": "false"}
            properties_available = ConnectionObj.mongo_connection_obj.find_many(db_name=db_ilens_hmi,
                                                                                collection_name=property_collection,
                                                                                query_json=query_json,
                                                                                search_option={"property_id": 1,
                                                                                               "property_name": 1,
                                                                                               "_id": 0},
                                                                                limit=10)
            final_json["data"] = properties_available
            return final_json

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

    @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

    def update_property_group_and_return_message(self, input_json):
        try:
            input_json["property_group_name"] = input_json["property_group_name"].strip(" ")
            query_json = {"property_group_id": input_json["property_group_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 = {
                "property_group_name": input_json["property_group_name"],
                "property_list": input_json["property_list"],
                "updated_on": datetime.datetime.now().strftime("%d %b, %Y %I:%M %p"),
                "updated_by": user_name,
                "property_count": str(len(input_json["property_list"]))
            }
            validation_status = self.validate_property_group_id(input_json["property_group_id"])
            if validation_status["status"] == "success":
                ConnectionObj.mongo_connection_obj.update_one(db_name=db_ilens_hmi,
                                                              collection_name=property_group_collection,
                                                              query=query_json,
                                                              set_json=update_json,
                                                              upsert=True)
                return {"status": "success", "message": "property group updated successfully",
                        "data": {"property_group_name": input_json["property_group_name"],
                                 "property_group_id": input_json["property_group_id"]}}
            else:
                return {"status": "failed",
                        "message": "no property group exists with the given property_group_id",
                        "data": {}}
        except Exception as e:
            return {"status": "failed",
                    "message": "failed to update property group:" + str(e),
                    "data": {}}

    @staticmethod
    def validate_property_group_id(property_id):
        try:
            query_json = {"property_group_id": property_id}
            if ConnectionObj.mongo_connection_obj.find_record_count(db_name=db_ilens_hmi,
                                                                    collection_name=property_group_collection,
                                                                    query_json=query_json,
                                                                    ) > 0:
                return {"status": "success"}
            else:
                return {"status": "failed"}
        except Exception as e:
            logger.exception("exception while validating the property group id:" + str(e))
            return {"status": "failed"}

    @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"}
                    })
        except Exception as e:
            logger.exception("exception while forming query json:" + str(e))
            return query_json
        return query_json

    @staticmethod
    def duplicate_name_check(input_json, edit=False):
        """

        :param input_json:
        :param edit:
        :return:
        """
        try:
            if edit:
                query = {"property_group_name": input_json["property_group_name"],
                         "property_group_id": {"$nin": [input_json["property_group_id"]]},
                         "is_deleted": "false"}
            else:
                query = {"property_group_name": input_json["property_group_name"],
                         "is_deleted": "false"}
            record_count = ConnectionObj.mongo_connection_obj.\
                find_record_count(db_name=db_ilens_hmi, collection_name=property_group_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
