import json
import time
from distutils.version import LooseVersion

import requests
from requests.exceptions import RequestException
from requests.packages.urllib3.exceptions import InsecureRequestWarning

from . import exceptions, utils


def _requests_compatible_true():
    if LooseVersion(requests.__version__) >= LooseVersion('2.11.0'):
        return 'True'
    else:
        return True


class RequestCacheException(Exception):
    pass


class RequestCacheMissingException(RequestCacheException):
    pass


class RequestCacheInvalidException(RequestCacheException):
    pass


class RequestCache(object):

    def __init__(self, valid_time=60):
        self._valid_time = valid_time
        self._cache = {}

    def put(self, key, value):
        self._cache[key] = {
            'time': time.time(),
            'value': value
        }

    def remove(self, key):
        if key in self._cache:
            del self._cache[key]

    def get(self, key):
        if key not in self._cache:
            raise RequestCacheMissingException()
        if time.time() > self._cache[key]['time'] + self._valid_time:
            self.remove(key)
            raise RequestCacheInvalidException()
        return self._cache[key]['value']


class RequestMakerException(Exception):
    pass


class RequestMaker(object):

    def __init__(self,
                 api_path, host,
                 token,
                 token_type='Bearer',
                 tls_verify=True,
                 enable_pagination=True
                 ):
        self.api_path = api_path
        self.host = host
        self.token = token
        self.token_type = token_type
        self.tls_verify = tls_verify
        self.enable_pagination = enable_pagination
        self._cache = RequestCache()
        if not self.tls_verify:
            requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

    @property
    def cache(self):
        return self._cache

    def is_bad_response(self, response):
        return 400 <= response.status_code <= 500

    def headers(self, paginate=True):
        headers = {
            'Content-type': 'application/json',
            'Authorization': '{0} {1}'.format(self.token_type, self.token),
        }
        if self.enable_pagination and paginate:
            headers['x-lazy-pagination'] = _requests_compatible_true()
        else:
            headers['x-disable-pagination'] = _requests_compatible_true()
        return headers

    def urljoin(self, *parts):
        return utils.urljoin(*parts)

    def get_full_url(self, uri, query={}, **parameters):
        full_url = self.urljoin(
            self.host, self.api_path,
            uri.format(**parameters)
        )
        return full_url

    def get(self, uri, query={}, cache=False, paginate=True, **parameters):
        try:
            full_url = self.urljoin(
                self.host, self.api_path,
                uri.format(**parameters)
            )

            result = None

            if cache:
                try:
                    result = self._cache.get(full_url)
                except RequestCacheException:
                    pass

            if not result:
                result = requests.get(
                    full_url,
                    headers=self.headers(paginate),
                    params=query,
                    verify=self.tls_verify
                )
            if cache:
                self._cache.put(full_url, result)
        except RequestException:
            raise exceptions.TaigaRestException(
                full_url, 400,
                'Network error!', 'GET'
            )
        if not self.is_bad_response(result):
            return result
        else:
            raise exceptions.TaigaRestException(
                full_url, result.status_code,
                result.text, 'GET'
            )

    def post(self, uri, payload=None, query={}, files={}, **parameters):
        if files:
            headers = {
                'Authorization': '{0} {1}'.format(self.token_type, self.token),
                'x-disable-pagination': _requests_compatible_true()
            }
            data = payload
        else:
            headers = self.headers()
            data = json.dumps(payload)
        try:
            full_url = self.urljoin(
                self.host, self.api_path,
                uri.format(**parameters)
            )
            result = requests.post(
                full_url,
                headers=headers,
                data=data,
                params=query,
                files=files,
                verify=self.tls_verify
            )
        except RequestException:
            raise exceptions.TaigaRestException(
                full_url, 400,
                'Network error!', 'POST'
            )
        if not self.is_bad_response(result):
            return result
        else:
            raise exceptions.TaigaRestException(
                full_url, result.status_code,
                result.text, 'POST'
            )

    def delete(self, uri, query={}, **parameters):
        try:
            full_url = self.urljoin(
                self.host, self.api_path,
                uri.format(**parameters)
            )
            result = requests.delete(
                full_url,
                headers=self.headers(),
                params=query,
                verify=self.tls_verify
            )
        except RequestException:
            raise exceptions.TaigaRestException(
                full_url, 400,
                'Network error!', 'DELETE'
            )
        if not self.is_bad_response(result):
            return result
        else:
            raise exceptions.TaigaRestException(
                full_url, result.status_code,
                result.text, 'DELETE'
            )

    def put(self, uri, payload=None, query={}, **parameters):
        try:
            full_url = self.urljoin(
                self.host, self.api_path,
                uri.format(**parameters)
            )
            result = requests.put(
                full_url,
                headers=self.headers(),
                data=json.dumps(payload),
                params=query,
                verify=self.tls_verify
            )
        except RequestException:
            raise exceptions.TaigaRestException(
                full_url, 400,
                'Network error!', 'PUT'
            )
        if not self.is_bad_response(result):
            return result
        else:
            raise exceptions.TaigaRestException(
                full_url, result.status_code,
                result.text, 'PUT'
            )

    def patch(self, uri, payload=None, query={}, **parameters):
        try:
            full_url = self.urljoin(
                self.host, self.api_path,
                uri.format(**parameters)
            )
            result = requests.patch(
                full_url,
                headers=self.headers(),
                data=json.dumps(payload),
                params=query,
                verify=self.tls_verify
            )
        except RequestException:
            raise exceptions.TaigaRestException(
                full_url, 400,
                'Network error!', 'PATCH'
            )
        if not self.is_bad_response(result):
            return result
        else:
            raise exceptions.TaigaRestException(
                full_url, result.status_code,
                result.text, 'PATCH'
            )