import datetime
import traceback

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

cmn_utils_obj = CommonUtils()
property_type_collection = app_constants.DBMapping.property_type
property_collection = app_constants.DBMapping.property
db_ilens_hmi = app_constants.DBMapping.ilens_hmi_db


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

    def fetch_property_type(self, input_json):
        try:
            final_json = {"status": "success", "data": {}}
            if "property_type_id" in input_json:
                validation_status = self.validate_property_type_id(input_json["property_type_id"])
                if validation_status:
                    property_type_record = self.mongo_obj.find_one(db_name=db_ilens_hmi,
                                                                   collection_name=property_type_collection,
                                                                   query={
                                                                       "property_type_id": input_json[
                                                                           "property_type_id"],
                                                                       "is_deleted": "false"},
                                                                   search_json={"_id": 0,
                                                                                "property_type_id": 1,
                                                                                "property_type_name": 1,
                                                                                "attribute_list": 1})
                    final_json["data"] = property_type_record
                else:
                    return {"status": "failed",
                            "message": "no property type exists with the given property_type_id",
                            "data": {}}
            else:
                return {"status": "failed", "message": "property_type_id is missing in input"}
            return final_json
        except Exception as e:
            logger.exception("Exception while fetching property type" + str(e))
            return {"status": "failed", "message": "unable to fetch property type"}

    def save_property_type(self, input_json):
        """
        it will save property_type in ilens_hmi database of property_type collection
        :param input_json: entire property type details
        :return: property type id
        """
        try:
            if "edit" in input_json and input_json["edit"] is True:
                if "property_type_id" not in input_json or input_json["property_type_id"] == "":
                    return {"status": "failed", "message": "property_type_id is missing",
                            "data": {}}
                field_list = app_constants.RequiredFields.property_type_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 type name already exists"}
                input_json["attribute_list"] = self.generate_attribute_id(input_json["attribute_list"])
                return_message = self.update_property_type_and_return_message(input_json=input_json)
                return return_message
            field_list = app_constants.RequiredFields.property_type_create
            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 type name already exists"}
            new_id = "property_type_" + GetNewId.get_next_id("property_type")
            input_json["property_type_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"]
            else:
                return {"status": "failed", "message": "user not authorized"}
            user_name = self.common_utils_obj.get_user_name_for_user_id(user_id=user_id)
            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.common_utils_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_type_name"] = input_json["property_type_name"].strip(" ")
            # input_json["client_id"] = client_id
            input_json["attribute_count"] = str(len(input_json["attribute_list"]))
            input_json["attribute_list"] = self.generate_attribute_id(input_json["attribute_list"])
            input_json["is_deleted"] = "false"
            self.mongo_obj.database_insertion(db_name=db_ilens_hmi,
                                              collection_name=property_type_collection,
                                              query_json=input_json)
            return {"status": "success", "message": "property type created successfully",
                    "data": {"property_type_name": input_json["property_type_name"],
                             "property_type_id": new_id}}
        except Exception as e:
            logger.exception("Exception" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to create property type"}

    def list_property_type(self, input_json):
        """
        this service 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_type_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=db_ilens_hmi,
                collection_name=property_type_collection,
                query_json=query_json,
                search_option={"_id": 0, "property_type_name": 1, "property_type_id": 1, "attribute_count": 1,
                               "updated_by": 1, "updated_on": 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=db_ilens_hmi,
                                                                           collection_name=property_type_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 types"}

    def delete_property_type(self, input_json):
        """
        this service will delete the property based on property_type_id
        :param input_json: property_type_id
        :return:
        """
        try:
            field_list = app_constants.RequiredFields.property_type_delete
            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"}
            if self.mongo_obj.find_record_count(db_name=db_ilens_hmi,
                                                collection_name=property_collection,
                                                query_json={"is_deleted": "false",
                                                            "property_type_id": input_json["property_type_id"]}) > 0:
                return {"status": "failed", "message": "this property type is already being used in some property"}
            if self.mongo_obj.update_one(db_name=db_ilens_hmi,
                                         collection_name=property_type_collection,
                                         query={"property_type_id": input_json["property_type_id"]},
                                         set_json={"is_deleted": "true"}):
                return {"status": "success", "message": "property type deleted successfully"}
            else:
                return {"status": "failed", "message": "no property type exists with the given property_type_id"}
        except Exception as e:
            logger.exception("Exception" + str(e))
            traceback.print_exc()
            return {"status": "failed", "message": "unable to delete property types"}

    @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_type_and_return_message(self, input_json):
        try:
            input_json["property_type_name"] = input_json["property_type_name"].strip(" ")
            query_json = {"property_type_id": input_json["property_type_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"}
            update_json = {
                "property_type_name": input_json["property_type_name"],
                "attribute_list": input_json["attribute_list"],
                "updated_on": datetime.datetime.now().strftime("%d %b, %Y %I:%M %p"),
                "updated_by": user_name,
                "attribute_count": str(len(input_json["attribute_list"]))
            }
            validation_status = self.validate_property_type_id(input_json["property_type_id"])
            if validation_status:
                self.mongo_obj.update_one(db_name=db_ilens_hmi,
                                          collection_name=property_type_collection,
                                          query=query_json,
                                          set_json=update_json,
                                          upsert=True)
                return {"status": "success", "message": "property type updated successfully",
                        "data": {"property_type_name": input_json["property_type_name"],
                                 "property_type_id": input_json["property_type_id"]}}
            else:
                return {"status": "failed",
                        "message": "no property type exists with the given property_type_id",
                        "data": {}}
        except Exception as e:
            return {"status": "failed",
                    "message": "no property type exists with the given property_type_id:" + str(e),
                    "data": {}}

    def validate_property_type_id(self, property_type_id):
        try:
            query_json = {"property_type_id": property_type_id, "is_deleted": "false"}
            if self.mongo_obj.find_record_count(db_name=db_ilens_hmi,
                                                collection_name=property_type_collection,
                                                query_json=query_json,
                                                ) > 0:
                return True
            else:
                return False
        except Exception as e:
            logger.exception("exception while validating the property type id:" + str(e))
            return False

    @staticmethod
    def generate_attribute_id(attribute_list):
        final_attribute_list = []
        try:
            for each_attribute in attribute_list:
                try:
                    if "attribute_id" not in each_attribute or each_attribute["attribute_id"] == "":
                        each_attribute["attribute_id"] = "attribute_" + GetNewId.get_next_id("attribute")
                    final_attribute_list.append(each_attribute)
                except Exception as e:
                    logger.exception("exception while iterating attribute list:" + str(e))
                    continue
        except Exception as e:
            logger.exception("exception while generating attribute id's:" + str(e))
            return final_attribute_list
        return final_attribute_list

    @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

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

        :param input_json:
        :param edit:
        :return:
        """
        try:
            if edit:
                query = {"property_type_name": input_json["property_type_name"],
                         "property_type_id": {"$nin": [input_json["property_type_id"]]},
                         "is_deleted": "false"}
            else:
                query = {"property_type_name": input_json["property_type_name"],
                         "is_deleted": "false"}
            record_count = self.mongo_obj.find_record_count(db_name=db_ilens_hmi,
                                                            collection_name=property_type_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
