from pymongo import ReturnDocument
import secrets
from flask import current_app

from .resources import Resource
from datetime import datetime
from ..util import dict_to_camel_case, clean_empty, 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

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

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

            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:
                    current_app.logger.info("Service available")
                    RedisEvent("SERVICE_API_AVAILABLE", "apiIds",
                               [str(api_id)]).send_event()
                else:
                    current_app.logger.info("Service unavailable")
                    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)

            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)

            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)

            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()
                if "apiStatus" not in service_api_description_updated or len(service_api_description_updated["apiStatus"]["aefIds"]) > 0:
                    current_app.logger.info("Service available")
                    RedisEvent("SERVICE_API_AVAILABLE", "apiIds",
                               [str(service_api_id)]).send_event()
                else:
                    current_app.logger.info("Service unavailable")
                    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))
