from pymongo import ReturnDocument
import secrets
from flask import current_app, Flask, Response

from .resources import Resource
from datetime import datetime
from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case,clean_n_camel_case
from .responses import internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response
from .auth_manager import AuthManager
from .redis_event import RedisEvent
from .publisher import Publisher
from ..models.service_api_description import ServiceAPIDescription
import json
from ..encoder import CustomJSONEncoder
publisher_ops = Publisher()


service_api_not_found_message = "Service API not found"


class PublishServiceOperations(Resource):

    def __check_apf(self, apf_id):
        providers_col = self.db.get_col_by_name(self.db.capif_provider_col)

        current_app.logger.debug("Checking apf id")
        provider = providers_col.find_one(
            {"api_prov_funcs.api_prov_func_id": apf_id})

        if provider is None:
            current_app.logger.error("Publisher not exist")
            return unauthorized_error(detail="Publisher not existing", cause="Publisher id not found")

        list_apf_ids = [func["api_prov_func_id"]
                        for func in provider["api_prov_funcs"] if func["api_prov_func_role"] == "APF"]
        if apf_id not in list_apf_ids:
            current_app.logger.debug("This id not belongs to APF")
            return unauthorized_error(detail="You are not a publisher", cause="This API is only available for publishers")

        return None

    def __init__(self):
        Resource.__init__(self)
        self.auth_manager = AuthManager()

    def get_serviceapis(self, apf_id):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)

        try:

            current_app.logger.debug("Geting service apis")

            result = self.__check_apf(apf_id)

            if result != None:
                return result

            service = mycol.find({"apf_id": apf_id}, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1,
                                 "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1})
            current_app.logger.debug(service)
            if service is None:
                current_app.logger.error("Not found services for this apf id")
                return not_found_error(detail="Not exist published services for this apf_id", cause="Not exist service with this apf_id")

            json_docs = []
            for serviceapi in service:
                my_service_api = dict_to_camel_case(serviceapi)
                my_service_api = clean_empty(my_service_api)
                json_docs.append(my_service_api)

            current_app.logger.debug("Obtained services apis")
            current_app.logger.debug(json_docs)

            res = make_response(object=json_docs, status=200)
            return res

        except Exception as e:
            exception = "An exception occurred in get services"
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))

    def add_serviceapidescription(self, apf_id, serviceapidescription, vendor_specific):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)

        try:

            current_app.logger.debug("Publishing service")
            result = self.__check_apf(apf_id)

            if result != None:
                return result

            service = mycol.find_one(
                {"api_name": serviceapidescription.api_name})
            if service is not None:
                current_app.logger.error(
                    "Service already registered with same api name")
                return forbidden_error(detail="Already registered service with same api name", cause="Found service with same api name")

            api_id = secrets.token_hex(15)
            serviceapidescription.api_id = api_id
            rec = dict()
            rec['apf_id'] = apf_id
            rec['onboarding_date'] = datetime.now()
            serviceapidescription_dict = serviceapidescription.to_dict()
            if vendor_specific:
                for vend_spec in vendor_specific:
                    for (profile,i) in zip(serviceapidescription_dict['aef_profiles'], range(len(serviceapidescription_dict['aef_profiles']))):
                        if i == vend_spec[0]:
                            profile[vend_spec[1]] = vend_spec[2]
            rec.update(serviceapidescription_dict)

            mycol.insert_one(rec)

            self.auth_manager.add_auth_service(api_id, apf_id)

            current_app.logger.debug("Service inserted in database")

            res = make_response(object=clean_n_camel_case(serviceapidescription_dict), status=201)
            res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + \
                          str(apf_id) + "/service-apis/" + str(api_id)

            if res.status_code == 201:
                current_app.logger.info("Service published")
                if serviceapidescription.api_status is None or len(serviceapidescription.api_status.aef_ids) > 0:
                    if serviceapidescription.supported_features is not None:
                        if serviceapidescription.return_supp_feat_dict(serviceapidescription.supported_features)["ApiStatusMonitoring"]:
                            current_app.logger.info(f"Service available")
                            RedisEvent("SERVICE_API_AVAILABLE", ["serviceAPIDescriptions", "apiIds"],
                                    [[clean_n_camel_case(serviceapidescription.to_dict())],[str(api_id)]]).send_event()
                        else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"],
                                    [str(api_id)]).send_event()
                    else:
                        current_app.logger.info("Service available")
                        RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"],
                                [str(api_id)]).send_event()
                else:
                    if serviceapidescription.supported_features is not None:
                        if serviceapidescription.return_supp_feat_dict(serviceapidescription.supported_features)["ApiStatusMonitoring"]:
                            current_app.logger.info(f"Service unavailable")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["serviceAPIDescriptions", "apiIds"],
                                    [[clean_n_camel_case(serviceapidescription.to_dict())],[str(api_id)]]).send_event()
                        else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"],
                                    [str(api_id)]).send_event()
                    else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"],
                                    [str(api_id)]).send_event()

            return res

        except Exception as e:
            exception = "An exception occurred in add services"
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))

    def get_one_serviceapi(self, service_api_id, apf_id):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)

        try:
            current_app.logger.debug(
                "Geting service api with id: " + service_api_id)
            result = self.__check_apf(apf_id)

            if result != None:
                return result

            my_query = {'apf_id': apf_id, 'api_id': service_api_id}
            service_api = mycol.find_one(my_query, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1,
                                         "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1})
            if service_api is None:
                current_app.logger.error(service_api_not_found_message)
                return not_found_error(detail=service_api_not_found_message, cause="No Service with specific credentials exists")

            my_service_api = dict_to_camel_case(service_api)
            my_service_api = clean_empty(my_service_api)

            current_app.logger.debug("Obtained service api")
            res = make_response(object=my_service_api, status=200)
            return res

        except Exception as e:
            exception = "An exception occurred in get one service"
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))

    def delete_serviceapidescription(self, service_api_id, apf_id):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)

        try:

            current_app.logger.debug(
                "Removing api service with id: " + service_api_id)
            result = self.__check_apf(apf_id)

            if result != None:
                return result

            my_query = {'apf_id': apf_id, 'api_id': service_api_id}
            serviceapidescription_dict = mycol.find_one(my_query, {"_id": 0, "onboarding_date": 0, "apf_id": 0})

            if serviceapidescription_dict is None:
                current_app.logger.error(service_api_not_found_message)
                return not_found_error(detail="Service API not existing", cause="Service API id not found")

            mycol.delete_one(my_query)

            self.auth_manager.remove_auth_service(service_api_id, apf_id)

            current_app.logger.debug("Removed service from database")
            out = "The service matching api_id " + service_api_id + " was deleted."
            res = make_response(out, status=204)
            serviceapidescription= clean_empty(dict_to_camel_case(serviceapidescription_dict))
            if res.status_code == 204:
                is_supported = serviceapidescription.get("supportedFeatures") and \
               ServiceAPIDescription.return_supp_feat_dict(serviceapidescription["supportedFeatures"]).get("ApiStatusMonitoring")

                if is_supported:
                    current_app.logger.info("Service unavailable")
                    RedisEvent(
                        "SERVICE_API_UNAVAILABLE",
                        ["serviceAPIDescriptions", "apiIds"],
                        [[serviceapidescription], [str(service_api_id)]]
                    ).send_event()

                else:
                    status_message = "Service available" if serviceapidescription.get("supportedFeatures") is None else "Service unavailable"
                    current_app.logger.info(status_message)
                    RedisEvent(
                        "SERVICE_API_UNAVAILABLE",
                        ["apiIds"],
                        [str(service_api_id)]
                    ).send_event()

            return res

        except Exception as e:
            exception = "An exception occurred in delete service"
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))

    def update_serviceapidescription(self, service_api_id, apf_id, service_api_description):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)

        try:

            current_app.logger.debug(
                "Updating service api with id: " + service_api_id)

            result = self.__check_apf(apf_id)

            if result != None:
                return result

            my_query = {'apf_id': apf_id, 'api_id': service_api_id}
            serviceapidescription = mycol.find_one(my_query)

            if serviceapidescription is None:
                current_app.logger.error(service_api_not_found_message)
                return not_found_error(detail="Service API not existing", cause="Service API id not found")

            service_api_description = service_api_description.to_dict()
            service_api_description = clean_empty(service_api_description)

            result = mycol.find_one_and_update(serviceapidescription, {"$set": service_api_description}, projection={"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1,
                                               "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}, return_document=ReturnDocument.AFTER, upsert=False)

            result = clean_empty(result)

            current_app.logger.debug("Updated service api")

            service_api_description_updated = dict_to_camel_case(result)

            response = make_response(
                object=service_api_description_updated, status=200)
            
            service_api_description = ServiceAPIDescription.from_dict(json.dumps(service_api_description_updated, cls=CustomJSONEncoder))
            if response.status_code == 200:
                RedisEvent("SERVICE_API_UPDATE", ["serviceAPIDescriptions"], [[
                           service_api_description_updated]]).send_event()
                if service_api_description.api_status  is None or len(service_api_description.api_status.aef_ids) > 0:
                    if service_api_description.supported_features is not None:
                        if service_api_description.return_supp_feat_dict(service_api_description.supported_features)["ApiStatusMonitoring"]:
                            current_app.logger.info(f"Service available")
                            RedisEvent("SERVICE_API_AVAILABLE", ["serviceAPIDescriptions", "apiIds"],
                                    [[service_api_description],[str(service_api_id)]]).send_event()
                        else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"],
                                    [str(service_api_id)]).send_event()
                    else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"],
                                    [str(service_api_id)]).send_event()
                else:
                    if service_api_description.supported_features is not None:
                        if service_api_description.return_supp_feat_dict(service_api_description.supported_features)["ApiStatusMonitoring"]:
                            current_app.logger.info(f"Service unavailable")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["serviceAPIDescriptions", "apiIds"],
                                    [[service_api_description],[str(service_api_id)]]).send_event()
                        else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"],
                                    [str(service_api_id)]).send_event()
                    else:
                            current_app.logger.info("Service available")
                            RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"],
                                    [str(service_api_id)]).send_event()
                    

            return response

        except Exception as e:
            exception = "An exception occurred in update service"
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))
