# coding=utf-8

from __future__ import unicode_literals, absolute_import, print_function

import json
import random
import redis
from gm_rpcd.all import RPCDFaultException
from django.conf import settings
from gm_types.error import ERROR as CODES
from gm_types.gaia import PROBLEM_FLAG_CHOICES

from talos.models.topic.topic import Problem
from talos.services import get_user_from_context
from utils.rpc import gen

from talos.cache.base import page_cache
from talos.tasks import fake_view_topic
# page_cache = redis.StrictRedis(**settings.REDIS['page_cache'])


class CacheControl(object):

    def __init__(self, redis_connection, prefix, normal_expire, error_expire, random_renew_range, random_renew_prob):
        self._prefix = prefix
        self._normal_expire = int(normal_expire)
        self._error_expire = int(error_expire)
        self._random_renew_range = int(random_renew_range)
        self._random_renew_prob = float(random_renew_prob)
        self._redis = redis_connection

    def call_raw(self, func, key_func, cond_func, name, args, kwargs):

        use_cache = cond_func and not cond_func(*args, **kwargs)
        full_key = self._prefix + ':' + name + ':' + key_func(*args, **kwargs)
        expire = self._normal_expire

        if use_cache:
            stored_value_json = self._redis.get(full_key)
            if stored_value_json is not None:
                ttl = self._redis.ttl(full_key)
                renew = False
                if ttl < self._random_renew_range:
                    if random.random() < self._random_renew_prob:
                        renew = True
                if not renew:
                    return json.loads(stored_value_json)

        try:
            value = {
                'error': 0,
                'message': '',
                'data': func(*args, **kwargs),
            }
            expire = self._normal_expire
        except RPCDFaultException as e:
            value = {
                "error": e.code,
                "message": e.message,
                "data": None
            }
        except Exception as e:
            value = {
                'error': CODES.UNKNOWN_ERROR,   # todo ， need fix this
                'message': CODES.getDesc(CODES.UNKNOWN_ERROR),
                'data': None,
            }
            expire = self._error_expire

        if use_cache:
            value_json = json.dumps(value)
            self._redis.setex(full_key, expire, value_json)

        return value

    def call(self, func, key_func, cond_func, name, args, kwargs):
        value = self.call_raw(func, key_func, cond_func, name, args, kwargs)
        if value['error'] == 0:
            return value['data']
        raise RPCDFaultException(code=value["error"], message=value["message"])

    def decorate(self, name, key_func, cond_func=None):
        def decorator(func):
            def wrapper(*args, **kwargs):
                return self.call(func, key_func, cond_func, name, args, kwargs)
            return wrapper
        return decorator


cc = CacheControl(
    redis_connection=page_cache,
    prefix='gaia:cache:topic:eM4FCKGKLL8C5hpk',
    normal_expire=120,
    error_expire=10,
    random_renew_range=10,
    random_renew_prob=0.01,
)


def get_topic_info(ctx, id):
    """话题详情
    """
    user = get_user_from_context(ctx)
    return get_topic_info_extracted(user=user, topic_id=id)


def get_topic_info_extracted(user, topic_id):
    from_db = get_topic_info_from_db(topic_id)
    of_user = get_topic_info_of_user(topic_id, user)
    from_redis = Problem.get_topic_info_from_redis(topic_id)
    combined_data = get_topic_info_combined(
        from_db=from_db,
        of_user=of_user,
        from_redis=from_redis,
    )
    return combined_data


@cc.decorate(
    name='get_topic_data_from_db',
    key_func=(lambda topic_id: str(topic_id)),
)
def get_topic_info_from_db(topic_id):

    topic = get_topic(topic_id)
    result = topic.get_topic_info_from_db()
    fake_view_topic.delay(topic_id=topic.id)
    return result


@cc.decorate(
    name='get_topic_info_of_user',
    key_func=(lambda topic_id, user: str(topic_id)),
    cond_func=(lambda topic_id, user: user is not None),
)
# todo 用户存在时，幷未缓存信息
def get_topic_info_of_user(topic_id, user):
    topic = get_topic(topic_id)
    return topic.get_topic_info_of_user(user)


def get_topic_info_combined(from_db, of_user, from_redis):
    return Problem.get_topic_info_combined(
        from_db=from_db,
        of_user=of_user,
        from_redis=from_redis,
    )


def get_topic(topic_id):
    try:
        topic = Problem.objects.get(id=topic_id)
    except Problem.DoesNotExist:
        raise gen(CODES.TOPIC_NOT_FOUND)

    if topic.flag != PROBLEM_FLAG_CHOICES.NORMAL or not topic.is_online:
        raise gen(CODES.TOPIC_HAS_BEEN_DELETED)

    return topic
