from .clients import CacheClient
import logging
from functools import wraps
import time
from django.http import HttpResponseForbidden, HttpResponseGone
from django.shortcuts import redirect


def log_time(func):
    """
    used for debugging
    :param func:
    :return:
    """
    def wrapped(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        logging.error('TIMER %s: %s', func.__name__, time.time() - start)
        return res

    return wrapped


class Memoize(object):
    """
    decorator for class property values caching
    """

    def __init__(self, prop):
        self.prop = prop

    def __call__(self, self1, *args, **kwargs):
        try:
            cache = self1.__method_results_cache
        except AttributeError:
            cache = self1.__method_results_cache = {}

        if self.prop not in cache:
            cache[self.prop] = self.prop(self1, *args, **kwargs)
        return cache[self.prop]

    @staticmethod
    def clear(inst):
        try:
            del inst.__method_results_cache
        except AttributeError:
            pass


def cache_lock(func):
    """
    Grabs lock for specified function,
    So that other processes on this server won't be able to call it
    TODO: think of cross server lock
    :param func:
    """

    def wrapper(*args, **kwargs):
        cache = CacheClient()
        cache_key = 'lock_%s' % func.__name__
        if not cache.get(cache_key):
            cache.set(cache_key, True)
            try:
                result = func(*args, **kwargs)
            finally:
                cache.delete(cache_key)
            return result

    return wrapper


def cached(key, bypass=False, expire=2592000, fmt='json'):  # a month
    """
    Looks for func result in cache
    Sets result if none found in cache
    :param key: string
    :param bypass: don't use cache
    :param expire: seconds till cache expires
    :param fmt: str 'json' or 'b64'
    :return: func
    """

    def wrapper(func):
        if bypass:
            return func

        @wraps(func)
        def wrapped(*args, **kwargs):
            mc = CacheClient(expire=expire, fmt=fmt)
            value = mc.get(key)
            if value:
                logging.info("FOUND %s IN CACHE" % key)
                return value
            else:
                result = func(*args, **kwargs)
                mc.set(key, result)
                return result

        return wrapped

    return wrapper


def upload_token_required(func):
    def wrapper(*args, **kwargs):
        request = args[1]
        if request.META['PATH_INFO'].endswith('push_data.json'):
            job_n = int(request.META['PATH_INFO'].split('/')[3])
        else:  # if request.META['PATH_INFO'].startswith('/api/monitoring'):
            job_n = request.GET.get('job_id')
        try:
            from ..models import Job
            job = Job.check_job(Job.objects.get(n=job_n))
            assert not job.td
        except AssertionError:
            return HttpResponseGone('Job has gone offline')
        upload_token = request.GET.get('upload_token')
        try:
            from ..models import UploadToken
            assert upload_token
            UploadToken.objects.get(token=upload_token, job=job.id)
            return func(*args, **kwargs)
        except (AssertionError, Job.DoesNotExist, Job.Deleted):
            return HttpResponseForbidden('Token is not approved')

    return wrapper


def api_token_required(func):
    def wrapper(*args, **kwargs):
        request = args[1]
        try:
            job = args[2]
        except IndexError:
            job = None  # for api.views.job_create
        api_token = request.GET.get('api_token')
        from ..models import ApiToken
        try:
            assert api_token
            api_token = ApiToken.objects.get(token=api_token.strip())
            if job:
                assert api_token.user == job.user
            assert api_token.approved
            return func(*args, **kwargs)
        except (AssertionError, ApiToken.DoesNotExist):
            return HttpResponseForbidden('Token is not approved')

    return wrapper


def approve_required(func):
    def wrapper(*args, **kwargs):
        request = args[0]
        try:
            job = args[1]
        except IndexError:
            job = request.GET.get('job_n') or request.GET.get('job_id') or request.GET.get('job') \
                or request.POST.get('job_n') or request.POST.get('job_id') or request.POST.get('job')
        from ..models import Job
        if not isinstance(job, Job) and str(job).isdigit():
            job = Job.objects.get(n=job)

        try:
            from ..models import ApiToken
            token = ApiToken.objects.get_or_create(user=request.user)[0]
            if job:
                assert token.user == job.user
            approved = token.approved
        except:
            logging.exception('')
            approved = False
        if not approved:
            return redirect('/mainpage/guide/')
        else:
            return func(*args, **kwargs)

    return wrapper
