# -*- coding: utf-8 -*-
# @author:gao mingming
# @modifytime:2019.05.15

from __future__ import unicode_literals, absolute_import, print_function

import traceback
import random
from collections import defaultdict

from django.db import models, transaction
from django.db.models import F

from .base_model import BaseModel
from hippo.models import Doctor
from rpc.cache import young_doc_cache


cache_key_prefix = 'yd:rank_'
water_key = 'yd:water_list'
_cache = young_doc_cache
ALL_AWARD_IDS = list(range(0, 18))
MAX_COUNT = 999999999

def get_cache_key(_id):
    return cache_key_prefix + str(_id)

def get_water_cache_key(doctor_id, award_id):
    return '_'.join([doctor_id, str(award_id)])

def is_in_watered(doctor_id, award_id):
    w_cache_key = get_water_cache_key(doctor_id, award_id)
    return _cache.sismember(water_key, w_cache_key)

def stop_watered(doctor_id, award_id):
    w_cache_key = get_water_cache_key(doctor_id, award_id)
    _cache.srem(water_key, w_cache_key)

class YoungDoctorRegistrationer(BaseModel):
    class Meta:
        verbose_name="1905青年医生大赛报名表"
        db_table="api_youngdoctor_registrationer"
        app_label="api"

    award_id=models.IntegerField(verbose_name=u'奖项id')
    doctor=models.ForeignKey(Doctor, verbose_name=u'医生或机构id')
    vote_count=models.IntegerField(verbose_name=u'票数统计', default=0)
    final_count=models.IntegerField(verbose_name=u'读取数量(真实数、灌水数量)',default=0)
    doctor_type=models.IntegerField(verbose_name=u'医生或机构')

    @classmethod
    def clear_enter_record(cls, doctor_id):
        '''
        清除医生的报名记录
        '''
        dw = cls.objects.filter(doctor_id=doctor_id).all()
        award_ids = []
        for d in dw:
            award_ids.append(d.award_id)
            d.delete()
        for aid in award_ids:
            rank_key = get_cache_key(aid)
            _cache.zrem(rank_key, doctor_id)
            stop_watered(doctor_id, aid)


    @classmethod
    def watering_vote_count(cls, doctor_id, award_id, full=False):
        '''
        灌水-------  dun~dun~dun~~~ dun!!!
        '''
        with transaction.atomic():
            reg = cls.objects.select_for_update().filter(doctor_id=doctor_id, award_id=award_id).first()
            if not reg:
                return True
            full_count = reg.vote_count * 7
            water_count = full_count - reg.final_count
            if water_count <= 0:
                return True
            if not full:
                water_count = random.randint(1, water_count)
            reg.final_count += water_count
            reg.save()
        cls.set_cache_vote_count(doctor_id, award_id, reg.final_count)
        _full = reg.final_count >= full_count
        if _full:
            stop_watered(doctor_id, award_id)
        return _full

    @classmethod
    def set_cache_vote_count(cls, doc_id, award_id, final_count):
        '''
        设置缓存中的票数
        '''
        cache_key = get_cache_key(award_id)
        _cache.zadd(cache_key, final_count, doc_id)

    @classmethod
    def flush_rank_cache(cls):
        '''
        全量刷新缓存
        '''
        reg_list = cls.objects.all()
        rank_info = defaultdict(list)
        for reg in reg_list:
            award_id = reg.award_id
            d_id = reg.doctor_id
            score = reg.final_count
            rank_info[str(award_id)].extend([score, str(d_id)])
        for k,v in rank_info.items():
            cache_key = get_cache_key(k)
            _cache.zadd(cache_key, *v)

    @classmethod
    def rank_and_gap(cls, doctor_id, award_id):
        '''
        根据医生id, 及奖项id获取排名 及与上一名的差距
        return: tuple(rank, gap)
            eg: (5, 100)   # 第五名, 距离上一名差距100票
        '''
        cache_key = get_cache_key(award_id)
        d_vote_count = _cache.zscore(cache_key, doctor_id)
        bigger_score_count = _cache.zcount(cache_key, min=d_vote_count+1, max=MAX_COUNT)
        gap = 0
        if bigger_score_count > 0:
            last_idx = bigger_score_count - 1
            last_score_tuple = _cache.zrevrange(cache_key, start=last_idx, end=last_idx, withscores=True)
            if last_score_tuple:
                last_score = last_score_tuple[0][1]
                gap = last_score - d_vote_count
        rank = bigger_score_count + 1
        return rank, gap

    @classmethod
    def get_rank_info(cls, award_id=None, offset=None, limit=None):
        '''
        获取排行信息
        return {'{award_id}': [reg_id1, reg_id2...]}
        '''
        award_ids = ALL_AWARD_IDS
        if award_id:
            award_ids = [award_id]

        result = {}
        for a_id in award_ids:
            result[str(a_id)] = cls.get_rank_info_by_award_id(a_id, offset, limit)

        return result

    @classmethod
    def get_rank_info_by_award_id(cls, award_id, offset=0, limit=-1):
        offset = offset or 0
        limit = limit or -1
        cache_key = get_cache_key(award_id)
        return _cache.zrevrange(cache_key, offset, limit)

    @classmethod
    def update_rank_at_enter(cls, doctor_id, award_ids):
        '''
        医生报名后, 进入排行榜, 票数为0
        '''
        for a_id in award_ids:
            cls.set_cache_vote_count(doctor_id, a_id, 0)

    @classmethod
    def update_rank_at_vote(cls, doctor_id, award_ids):
        '''
        用户投票后, 更新排行榜, 票数自增
        '''
        for a_id in award_ids:
            cache_key = get_cache_key(a_id)
            _cache.zincrby(cache_key, str(doctor_id))
            cls.wrap_water(doctor_id, a_id)

    @classmethod
    def wrap_water(cls, doctor_id, award_id):
        if is_in_watered(doctor_id, award_id):
            return
        else:
            from api.tasks.young_doctor_task import watering_vote_count
            w_unique_key = get_water_cache_key(doctor_id, award_id)
            _cache.sadd(water_key, w_unique_key)
            watering_vote_count.apply_async((doctor_id, award_id), countdown=600)

    @classmethod
    def is_entered_by_doc_id(cls, doc_id):
        '''
        判断医生有没有报名
        '''
        return cls.objects.filter(doctor_id=doc_id).exists()

    @classmethod
    def enter_at_award_ids(cls, doc_id, doctor_type, award_ids):
        '''
        医生报名多个奖项
        '''
        if not award_ids:
            return False, 'empty award_ids'

        enter_ids = []
        try:
            with transaction.atomic():
                for a_id in award_ids:
                    item = cls(award_id=a_id, doctor_id=doc_id, doctor_type=doctor_type)
                    item.save()
                    enter_ids.append(item.id)
        except Exception as e:
            print(traceback.format_exc())
            return False, e.message
        cls.update_rank_at_enter(doc_id, award_ids)
        return True, ''


class YoungDocVote(BaseModel):
    class Meta:
        verbose_name="1905青年医生大赛投票记录表"
        db_table="api_youngdoctor_vote"
        app_label="api"

    registrationer=models.ForeignKey(YoungDoctorRegistrationer,related_name='votes')
    user_id=models.CharField(max_length=32, verbose_name=u"投票用户id", null=False)
    user_type=models.IntegerField(verbose_name=u'投票用户类型', default=0)
    day=models.DateField(auto_now_add=True,verbose_name='投票日期')

    @classmethod
    def do_vote(cls, user_id, user_type, reg_id):
        with transaction.atomic():
            new_obj = cls(registrationer_id=reg_id, user_id=user_id, user_type=user_type)
            new_obj.save()
            reg_obj = YoungDoctorRegistrationer.objects.get(id=reg_id)
            reg_obj.vote_count = F('vote_count') + 1
            reg_obj.final_count = F('final_count') + 1
            reg_obj.save()
        # 更新排行榜
        YoungDoctorRegistrationer.update_rank_at_vote(reg_obj.doctor_id, [reg_obj.award_id])

