# ----------------------------------------------------------
#  Copyright (c) Microsoft Corporation. All rights reserved. 
#  Licensed under the MIT License. See License.txt in the 
#  project root for license information.  If License.txt is 
#  missing, see https://opensource.org/licenses/MIT
# ----------------------------------------------------------

""" Malibu Service API helper """
import sys
import json
from time import sleep
try:
    import requests
except ImportError:
    print """You need to install the 'requests' library.
            Try: pip install requests"""
    sys.exit(code=1)
from requests.auth import HTTPBasicAuth

def retry_policy(max_tries=45, start_delay=2, backoff_base=2, max_exp=5):
    """
    Decorator that retries service calls if a Timeout or Connection exception occurs
    or the status code is greater than 500.
    The default values result in 5 retries within a minute
    and subsequent retries at a 1.06 min interval for ~44 minutes
    """
    def decorator(func):
        """
        Decorator
        """
        def func_wrapper(*args, **kwargs):
            """
            Function wrapper
            """
            waitfor = start_delay
            retry = True
            tries = 0
            res = None
            while retry:
                try:
                    retry = tries < max_tries
                    res = func(*args, **kwargs)
                    if isinstance(res, int):
                        response_code = res
                    else:
                        response_code = int(res[0])
                    #handling 403 specifically, APIM returns this code when the service is down
                    if response_code < 500 and response_code != 403:
                        return res
                    print "Response: {0}".format(response_code)
                except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as exc:
                    if not retry:
                        raise
                    print "Error: {0}".format(str(exc))
                if retry:
                    print "Retrying in {0}s, {1} of {2} retries ".format(str(waitfor),
                                                                         str(tries+1),
                                                                         str(max_tries))
                    sleep(waitfor)
                if tries < max_exp:
                    waitfor = waitfor * backoff_base
                tries = tries + 1
            return res
        return func_wrapper
    return decorator

class MalibuService(object):
    """ Malibu Service API helper class """

    def __init__(self, api_url, subscription_key):
        """ Constructor """
        self.api_url = api_url
        self.subscription_key = subscription_key

    @classmethod
    def handle_response(cls, response_code, response_data):
        """
        Handles service response.
        Returns the message, status or workflow id, if applicable
        """
        message = ""
        workflow_id = 0
        status = ""

        client_error = response_code >= 400 and response_code < 500
        success = response_code >= 200 and response_code < 300

        if client_error or success:
            try:
                response_content = json.loads(response_data)
                if "message" in response_content:
                    message = response_content["message"]
                if "Message" in response_content:
                    message = response_content["Message"]
                if "Id" in response_content:
                    workflow_id = response_content["Id"]
                if "Status" in response_content:
                    status = response_content["Status"]
            except ValueError, exc:
                message = "Invalid response content"

        return message, workflow_id, status

    @classmethod
    def get_headers(cls, subscription_key):
        """ Gets request headers """
        headers = {"content-type": "application/json",
                   "User-Agent": "Microsoft Genomics Command-line Client",
                   "Ocp-Apim-Subscription-Key": subscription_key}
        return headers

    def get_workflow_url(self, workflow_id):
        """ parses workflow status url """
        return self.api_url + str(workflow_id)

    @classmethod
    def get_workflow_item_request_body(cls,
                                       process_name,
                                       process_args,
                                       input_storage_account_type,
                                       output_storage_account_type,
                                       input_dictionary,
                                       output_dictionary):

        """ parses the HTTP request's body to create a workflow item """
        body = {"Process": process_name,
                "ProcessArgs": process_args,
                "InputStorageType": input_storage_account_type,
                "OutputStorageType": output_storage_account_type,
                "InputArgs": input_dictionary,
                "OutputArgs": output_dictionary}

        return body
    @retry_policy()
    def create_workflow_item(self, process_name,
                             process_args, input_storage_account_type,
                             output_storage_account_type, input_dictionary,
                             output_dictionary):
        """ submits POST HTTP request to create a workflow item """
        body = MalibuService.get_workflow_item_request_body(
            process_name,
            process_args,
            input_storage_account_type,
            output_storage_account_type,
            input_dictionary,
            output_dictionary)
        headers = MalibuService.get_headers(self.subscription_key)

        response = requests.post(self.api_url,
                                 data=json.dumps(body),
                                 headers=headers,
                                 timeout=300)

        response_code = response.status_code
        message = ""
        workflow_id = 0

        message, workflow_id, status = MalibuService.handle_response(response_code,
                                                                     response.content)

        return response_code, message, workflow_id
    @retry_policy()
    def cancel_workflow_item(self, workflow_id):
        """ submits a DELETE HTTP request to cancel a workflow item """
        headers = MalibuService.get_headers(self.subscription_key)
        url = self.get_workflow_url(workflow_id)
        response = requests.delete(url,
                                   headers=headers)
        response_code = response.status_code
        data = MalibuService.handle_response(response_code,
                                             response.content)
        #return response code and message
        return response_code, data[0]
    @retry_policy()
    def get_workflow_list(self):
        """ submits a GET HTTP request to get the list of workflow items
        for the current account """
        headers = MalibuService.get_headers(self.subscription_key)
        response = requests.get(self.api_url,
                                headers=headers)
        response_code = response.status_code

        workflow_data = ""
        try:
            workflow_data = json.loads(response.text)
        except ValueError, exc:
            workflow_data = response.text

        return response_code, workflow_data
    @retry_policy()
    def get_workflow_status(self, workflow_id):
        """ submits a GET HTTP request to retrieve the status of a workflow """
        headers = MalibuService.get_headers(self.subscription_key)
        url = self.get_workflow_url(workflow_id)

        response = requests.get(url,
                                headers=headers)

        response_code = response.status_code
        workflow_status = "NA"

        message, workflow_id, workflow_status = MalibuService.handle_response(response_code,
                                                                              response.text)

        return response_code, workflow_status, message

class ClientHTTPException(BaseException):
    pass