import rfc3987
from pymongo import ReturnDocument
import secrets
import requests
from .responses import bad_request_error, not_found_error, forbidden_error, internal_server_error, make_response
from flask import current_app, Flask, Response
import json
from datetime import datetime
from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case
from .auth_manager import AuthManager
from .resources import Resource
from ..config import Config
from api_invoker_management.models.api_invoker_enrolment_details import APIInvokerEnrolmentDetails
from .redis_event import RedisEvent
from .publisher import Publisher

publisher_ops = Publisher()
class InvokerManagementOperations(Resource):

    def __check_api_invoker_id(self, api_invoker_id):

        current_app.logger.debug("Cheking api invoker id")
        mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details)
        my_query = {'api_invoker_id':api_invoker_id}
        old_values = mycol.find_one(my_query)

        if old_values is None:
            current_app.logger.error("Not found api invoker id")
            return not_found_error(detail="Please provide an existing Network App ID", cause= "Not exist Network App ID" )

        return old_values

    def __sign_cert(self, publick_key, invoker_id):

        url = f"http://{self.config['ca_factory']['url']}:{self.config['ca_factory']['port']}/v1/pki_int/sign/my-ca"
        headers = {'X-Vault-Token': self.config['ca_factory']['token']}
        data = {
            'format':'pem_bundle',
            'ttl': '43000h',
            'csr': publick_key,
            'common_name': invoker_id
        }

        response = requests.request("POST", url, headers=headers, data=data, verify = self.config["ca_factory"].get("verify", False))
        print(response)
        response_payload = json.loads(response.text)

        return response_payload

    def __init__(self):
        Resource.__init__(self)
        self.auth_manager = AuthManager()
        self.config = Config().get_config()


    def add_apiinvokerenrolmentdetail(self, apiinvokerenrolmentdetail, username, uuid):

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

        #try:
        current_app.logger.debug("Creating invoker resource")
        res = mycol.find_one({'onboarding_information.api_invoker_public_key': apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key})

        if res is not None:
            current_app.logger.error("Generating forbbiden error, invoker registered")
            return forbidden_error(detail= "Invoker already registered", cause = "Identical invoker public key")

        if rfc3987.match(apiinvokerenrolmentdetail.notification_destination, rule="URI") is None:
            current_app.logger.error("Bad url format")
            return bad_request_error(detail="Bad Param", cause = "Detected Bad formar of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}])

        current_app.logger.debug("Signing Certificate")

        api_invoker_id = 'INV'+str(secrets.token_hex(15))
        cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, api_invoker_id)

        apiinvokerenrolmentdetail.api_invoker_id = api_invoker_id
        current_app.logger.debug(cert)
        apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate']

        # Onboarding Date Record
        invoker_dict = apiinvokerenrolmentdetail.to_dict()
        invoker_dict["onboarding_date"] = datetime.now()
        invoker_dict["username"]=username
        invoker_dict["uuid"]=uuid

        mycol.insert_one(invoker_dict)

        current_app.logger.debug("Invoker inserted in database")
        current_app.logger.debug("Netapp onboarded sucessfuly")

        self.auth_manager.add_auth_invoker(cert['data']['certificate'], api_invoker_id)

        res = make_response(object=serialize_clean_camel_case(apiinvokerenrolmentdetail), status=201)
        res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + str(api_invoker_id)

        if res.status_code == 201:
            current_app.logger.info("Invoker Created")
            RedisEvent("API_INVOKER_ONBOARDED", ["apiInvokerIds"], [[str(api_invoker_id)]]).send_event()
        return res

    def update_apiinvokerenrolmentdetail(self, onboard_id, apiinvokerenrolmentdetail):

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

        try:
            current_app.logger.debug("Updating invoker resource")
            result = self.__check_api_invoker_id(onboard_id)

            if isinstance(result, Response):
                return result

            if apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key != result["onboarding_information"]["api_invoker_public_key"]:
                cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, result["api_invoker_id"])
                apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate']
                self.auth_manager.update_auth_invoker(cert['data']["certificate"], onboard_id)

            apiinvokerenrolmentdetail_update = apiinvokerenrolmentdetail.to_dict()
            apiinvokerenrolmentdetail_update = {
                key: value for key, value in apiinvokerenrolmentdetail_update.items() if value is not None
            }

            result = mycol.find_one_and_update(result, {"$set":apiinvokerenrolmentdetail_update}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False)

            result = {
                key: value for key, value in result.items() if value is not None
            }

            current_app.logger.debug("Invoker Resource inserted in database")

            invoker_updated = APIInvokerEnrolmentDetails().from_dict(dict_to_camel_case(result))

            res = make_response(object=serialize_clean_camel_case(invoker_updated), status=200)
            if res.status_code == 200:
                current_app.logger.info("Invoker Updated")
                RedisEvent("API_INVOKER_UPDATED", ["apiInvokerIds"], [[onboard_id]]).send_event()
            return res

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

    def remove_apiinvokerenrolmentdetail(self, onboard_id):

        mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details)
        try:
            current_app.logger.debug("Removing invoker resource")
            result = self.__check_api_invoker_id(onboard_id)

            if isinstance(result, Response):
                return result

            mycol.delete_one({'api_invoker_id':onboard_id})
            self.auth_manager.remove_auth_invoker(onboard_id)

            current_app.logger.debug("Invoker resource removed from database")
            current_app.logger.debug("Netapp offboarded sucessfuly")
            out =  "The Network App matching onboardingId  " + onboard_id + " was offboarded."
            res = make_response(out, status=204)
            if res.status_code == 204:
                current_app.logger.info("Invoker Removed")
                RedisEvent("API_INVOKER_OFFBOARDED", ["apiInvokerIds"], [[onboard_id]]).send_event()
                publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboard_id}")
            return res

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


