import functools
import hashlib
import json

from django.core.cache import cache
from django.utils.encoding import iri_to_uri


def cached_method(_func=None, *, prefix_key='', timeout=300, use_kwargs=False, use_url_params=True):
    """
    this decorator created for caching data from methods
    not worked for multi language and urls has params
    need to complete like : django.utils.cache.get_cache_key
    :param use_kwargs: can use kwargs to separate caches for one method. useful in websocket methods
    :param use_url_params: can use url params to separate caches for one method.
    :param _func: the function that decorator comes before
    :param prefix_key: if developer want key to search in caches from it
    :param timeout: timeout of cache. default is 300
    :return:
    """

    def decorator_cached_method(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            ctx = hashlib.md5()
            if len(args) > 0:
                if hasattr(args[0], 'request'):
                    if use_url_params:
                        url = hashlib.md5(iri_to_uri(args[0].request.build_absolute_uri()).encode('ascii'))
                    else:
                        url = hashlib.md5(iri_to_uri(args[0].request.path).encode('ascii'))
                    prefix_name = '%s.%s.%s.%s.%s' % (
                    prefix_key, func.__name__, args[0].__class__.__name__, url.hexdigest(), ctx.hexdigest())
                elif isinstance(args[0], object):
                    prefix_name = '%s.%s.%s.%s' % (
                        prefix_key, func.__name__, args[0].__class__.__name__, ctx.hexdigest())
                else:
                    prefix_name = '%s.%s.%s' % (prefix_key, func.__name__, ctx.hexdigest())
            else:
                prefix_name = '%s.%s.%s' % (prefix_key, func.__name__, ctx.hexdigest())
            if use_kwargs:
                try:
                    hashed_kwargs = hash(json.dumps(kwargs, sort_keys=True))
                    prefix_name = prefix_name + ('.%s' % (hashed_kwargs,))
                except Exception as error:
                    print('warning : kwargs can`t be json serializable because : %s' % (error,))
            cached_data = cache.get('method.decorators.cache.%s' % (prefix_name,), None)
            if cached_data is None:
                cached_data = func(*args, **kwargs)
                cache.set('method.decorators.cache.%s' % (prefix_name,), cached_data, timeout=timeout)
            return cached_data

        return wrapper

    if _func is None:
        return decorator_cached_method
    else:
        return decorator_cached_method(_func)
