import requests
from django.conf import settings
import logging
import time
import requests.exceptions

logger = logging.getLogger(__name__)


class HttpTimeoutError(Exception):
    pass


class HttpError(Exception):
    def __init__(self, target_url, response_code, response_body, response_headers):
        self.target_url = target_url
        self.response_code = response_code
        self.response_body = response_body
        self.response_headers = response_headers

    def __str__(self):
        return u"{0} error accessing {1}".format(self.response_code, self.target_url)


class VSCommunicator(object):
    """
    simple class to handle communication with VS
    """
    def __init__(self):
        self.retry_wait = 5 #in seconds
        self.max_retries = 10
        self.verify = settings.SSL_VERIFY if hasattr(settings,"SSL_VERIFY") else True

    def do_get(self, urlpath):
        """
        perform a GET request to VS, with retries. Raises on error, for details see `do_generic`
        :param urlpath:
        :return:
        """
        return self.do_generic(urlpath, lambda full_url:
                               requests.get(full_url,auth=(settings.VIDISPINE_ADMIN_USER,settings.VIDISPINE_ADMIN_PASSWORD),
                                                      headers={
                                                          "Accept": "application/json"
                                                      }, verify=self.verify))

    def do_post(self, urlpath, body_content):
        """
        perform a POST request, with retries. Raises on error, for details see `do_generic`
        :param urlpath:
        :param body_content:
        :return:
        """
        return self.do_generic(urlpath, lambda full_url:
                               requests.post(full_url, json=body_content,
                                             auth=(settings.VIDISPINE_ADMIN_USER, settings.VIDISPINE_ADMIN_PASSWORD),
                                             headers={"Accept":"application/json"}, verify=self.verify))

    def do_put(self, urlpath, run_as=None):
        """
        perform a bare PUT request with no body. With retries. Raises on error, for details see `do_generic`
        :param urlpath: URL to put
        :return:
        """
        headers = {
            "Accept": "application/json"
        }
        if run_as:
            headers["RunAs"] = run_as
            logger.info("Running PUT {0} as {1}".format(urlpath, run_as))

        return self.do_generic(urlpath, lambda full_url:
                               requests.put(full_url, auth=(settings.VIDISPINE_ADMIN_USER,settings.VIDISPINE_ADMIN_PASSWORD),
                                            headers=headers, verify=self.verify))

    def do_generic(self, urlpath, requestlambda, attempt=0):
        """
        error-catching wrapper for an http request. Recursively retries if a timeout is received
        :param urlpath: API path to hit, with a leading /. /API is not necessary, it's added automatically if it does not exist.
        :param requestlambda: lambda function that performs the actual request. This is passed the full_url as a parameter
                            and is expected to return a Requests response object
        :param attempt: attempt counter, don't set this when calling externally
        :return: the json content returned as a dictionary.
            If the request errors then an HttpError is raised, if it succeeds but the content won't parse as json then
            a ParseException is raised
        """
        if urlpath.startswith("/API"):
            api_url_path = urlpath
        else:
            api_url_path = "/API" + urlpath

        full_url = settings.VIDISPINE_BASE_URL + api_url_path

        result = requestlambda(full_url)
        if result.status_code==503 or result.status_code==502 or result.status_code==501:
            if attempt>=self.max_retries:
                logger.error("Vidispine is still not available, giving up")
                raise HttpTimeoutError("Timed out accessing {0}".format(full_url))
            logger.warning("Vidispine not available on attempt {0}, received {1}".format(attempt, result.status_code))
            time.sleep(self.retry_wait)
            return self.do_generic(urlpath, requestlambda, attempt+1)
        elif result.status_code!=200 and result.status_code!=201:
            raise HttpError(full_url, result.status_code, result.text, result.headers)
        else:
            return result.json()
