# coding=utf-8
import datetime

from django.conf import settings

from talos.models.diary import DiaryRank
# TODO zhangyunyu mv this cache
from api.tool.caches import diary_heat_cache
from gm_types.gaia import DIARY_OPERATION


class DiaryRankTool(object):

    LOSE_RATE = settings.DIARY_HEAT_RANK['lose_rate']
    MAX_SCORE = settings.DIARY_HEAT_RANK['max_score']
    REPLY_NUM_PER_DEVICE = settings.DIARY_HEAT_RANK['reply_num_per_device']

    def __init__(self, diary_id):
        self.diary_id = diary_id
        self.heat_score_map = {
            DIARY_OPERATION.CREATE_TOPIC: settings.DIARY_HEAT_RANK['score_create_topic'],
            DIARY_OPERATION.MODIFY_DIARY: settings.DIARY_HEAT_RANK['score_modify_diary'],
            DIARY_OPERATION.SELF_REPLY:   settings.DIARY_HEAT_RANK['score_self_reply'],
            DIARY_OPERATION.OTHER_REPLY:  settings.DIARY_HEAT_RANK['score_other_reply'],
            DIARY_OPERATION.OTHER_VOTE:   settings.DIARY_HEAT_RANK['score_other_vote'],
        }
        self.heat_restriction_map = {
            DIARY_OPERATION.CREATE_TOPIC: settings.DIARY_HEAT_RANK['restrict_create_topic'],
            DIARY_OPERATION.MODIFY_DIARY: settings.DIARY_HEAT_RANK['restrict_modify_diary'],
            DIARY_OPERATION.SELF_REPLY:   settings.DIARY_HEAT_RANK['restrict_self_reply'],
            DIARY_OPERATION.OTHER_REPLY:  settings.DIARY_HEAT_RANK['restrict_other_reply'],
            DIARY_OPERATION.OTHER_VOTE:   settings.DIARY_HEAT_RANK['restrict_other_vote'],
        }

    def reset_action_map(self):
        diary_heat_cache.reset_action_num_map(self.diary_id)

    def get_diary_heat(self):
        dr = DiaryRank.objects.get(diary_id=self.diary_id)
        return dr.heat_score

    def update_action_num_map(self, action, extra_id=None):
        action_num_map = diary_heat_cache.get_action_num_map(self.diary_id)
        action_num_map[action] += 1
        if extra_id is not None:
            if action == DIARY_OPERATION.SELF_REPLY:
                ids = action_num_map['replied_user_ids']
                if extra_id not in ids:
                    ids.append(extra_id)
            elif action == DIARY_OPERATION.OTHER_REPLY:
                device_map = action_num_map['devices_reply_num_map']
                key = str(extra_id)
                if key in device_map.keys():
                    device_map[key] += 1
                else:
                    device_map[key] = 1
        diary_heat_cache.set_action_num_map(self.diary_id, action_num_map)

    def add_score(self, score):
        dr, _ = DiaryRank.objects.get_or_create(diary_id=self.diary_id)
        original_score = self.get_diary_heat()
        new_score = original_score + score
        if new_score > self.MAX_SCORE:
            new_score = self.MAX_SCORE
        elif new_score < 0:
            new_score = 0
        dr.heat_score = new_score
        dr.save()

    def lose_score(self, current_time):
        score = self.get_diary_heat()
        last_calculated_time = diary_heat_cache.get_last_calculated_time(self.diary_id)
        time_delta = current_time - last_calculated_time
        seconds = time_delta.total_seconds()
        hour = seconds / 3600
        lose_score = self.LOSE_RATE * hour * score
        diary_heat_cache.set_last_calculated_time(self.diary_id, current_time)
        self.add_score(-lose_score)     # 加负数即为减

    def in_restriction(self, action, extra_id):
        current_action_num_map = diary_heat_cache.get_action_num_map(self.diary_id)
        c = current_action_num_map[action]
        max_action_num = self.heat_restriction_map[action]
        if c >= max_action_num:
            return True

        if action == DIARY_OPERATION.SELF_REPLY:
            replied_user_ids = current_action_num_map['replied_user_ids']
            if extra_id in replied_user_ids:
                return True
            else:
                # self.update_action_num_map(action, extra_id=extra_id)
                return False
        elif action == DIARY_OPERATION.OTHER_REPLY:
            devices_reply_num_map = current_action_num_map['devices_reply_num_map']
            extra_id = str(extra_id)
            if extra_id in devices_reply_num_map.keys():
                if devices_reply_num_map[extra_id] >= self.REPLY_NUM_PER_DEVICE:
                    return True
                else:
                    # self.update_action_num_map(action, extra_id=extra_id)
                    return False
            else:
                # self.update_action_num_map(action, extra_id=extra_id)
                return False
        else:
            return False

    def add_heat(self, action, time, extra_id=None):
        previous_day_time = diary_heat_cache.get_previous_day_time(self.diary_id)
        previous_day = previous_day_time.date()
        day_delta = (time.date() - previous_day).days
        if day_delta >= 1:
            diary_heat_cache.set_previous_day_time(self.diary_id, time)
            self.reset_action_map()
        if self.in_restriction(action, extra_id):
            return
        else:
            added_score = self.heat_score_map[action]
            self.update_action_num_map(action, extra_id)
            self.add_score(added_score)
            self.lose_score(time)

    @classmethod
    def get_loss_score_by_time(cls, diary_id, current_score, current_time=None):
        # only read, do not write.es data async will trigger this
        current_time = current_time or datetime.datetime.now()
        last_calculated_time = diary_heat_cache.get_last_calculated_time(diary_id)
        time_delta = current_time - last_calculated_time
        seconds = time_delta.total_seconds()
        hour = seconds / 3600
        lose_score = cls.LOSE_RATE * hour * current_score
        return lose_score
