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
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

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):

        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()
            rec.update(serviceapidescription.to_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=serialize_clean_camel_case(serviceapidescription), 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")
                RedisEvent("SERVICE_API_AVAILABLE", "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 = 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")

            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)
            if res.status_code == 204:
                current_app.logger.info("Removed service published")
                RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds",
                           [service_api_id]).send_event()
                publisher_ops.publish_message(
                    "internal-messages", f"service-removed:{service_api_id}")
            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)
            if response.status_code == 200:
                RedisEvent("SERVICE_API_UPDATE", "serviceAPIDescriptions", [
                           service_api_description_updated]).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))
