#!/usr/bin/env python
# -*- coding: utf-8 -*-

import datetime
import json
import random
import time
from itertools import chain, groupby
from operator import itemgetter

from gm_types.gaia import (
    YOUNG_HIT_TASK_TYPE,
)

from django.conf import settings
from django.db.models import Q

from api.tool.datetime_tool import get_timestamp_epoch
from rpc.cache import variety_cache
from rpc.tool.error_code import gen, CODES
from variety_show.models.young_models import (
    InviteCode,
    TaskRecord,
    VoteRecord,
    TaskConfig,
)
from variety_show.services.service_base import ServiceBase
from variety_show.tasks import (
    idol_add_vote_num,
    fake_vote_num_for_idol,
)

# redis hash
# key:  variety_show_young_{date}_{user_id}

# exists  1  状态位，设置超时时间
# 已投票选手ID  voted_idol_ids  set()
# 任务完成log  complete_tasks dict() {"login":1, ……}

# 任务完成总次数  total_vote_nums int + 1

# 投票记录    vote_records dict() {idol_id1: num1, idol_id2: num2}

# 需要校验的地方
# 1、可投票选手校验 7位选手
# 2、投票状态校验 取 投票记录 + 总投票数 比较数字

# sql作用
# 数据同步 + 数量判断
# 若redis数据被清，用sql数据回填

# sql + redis 双写
# 1. 完成某个任务
# 为选手 投票
# 选手 获得总票数

# tasks
# 虚拟加票
# 票数同步


class YoungCacheService(object):
    """
    少年之名 缓存
    """
    variety_show_young_idol_cache_name = "variety_show_young_idol_voted"
    cache_key_idol_real_voted_num_f = "idol_{}_real_num"
    cache_key_idol_fake_voted_num_f = "idol_{}_fake_num"
    voted_idols = "voted_idols_{user_id}"

    cache_lock_setup_prefix = "setup"
    cache_lock_record_prefix = "task_record"
    cache_lock_vote_prefix = "vote_record"

    @staticmethod
    def add_lock_cache(prefix, row_cache_name, ex=3):
        """
        获取锁的 key 及状态
        :param prefix:
        :param row_cache_name:
        :param ex:
        :return:
        """
        _lock_cache_name = "lock_{}_{}".format(prefix, row_cache_name)
        _lock_status = variety_cache.set(_lock_cache_name, 1, ex=ex, nx=True)

        return _lock_cache_name, _lock_status

    @staticmethod
    def unlock_cache(lock_cache_name):
        """
        释放锁
        :param lock_cache_name:
        :return:
        """
        variety_cache.delete(lock_cache_name)

    @classmethod
    def get_variety_show_young_user_cache_name(cls, user_id):
        now = datetime.datetime.now()
        ex = int(
            (
                (now.replace(hour=0, second=0, minute=0, microsecond=0) + datetime.timedelta(days=1))
                - now
            ).total_seconds()
        )

        _cache_name = 'variety_show_young_{date}_{user_id}'.format(date=now.strftime("%Y-%m-%d"), user_id=user_id)

        if not variety_cache.exists(_cache_name):
            # 缓存不存在的时候再上锁
            _lock_cache_name, _lock_status = cls.add_lock_cache(cls.cache_lock_setup_prefix, _cache_name)  # 创建key时 加锁

            if _lock_status:
                variety_cache.hincrby(_cache_name, "exists")
                variety_cache.expire(_cache_name, ex)

                cls.unlock_cache(_lock_cache_name)  # 移除锁

        return _cache_name

    @classmethod
    def set_user_voted_idol_ids(cls, user_id):
        """
        初始用户支持的学员idlie列表
        :param user_id:
        :return:
        """
        from variety_show.services import YoungIdolsService
        cache_key = cls.voted_idols.format(user_id=user_id)
        vote_records = YoungIdolsService.get_vote_records(user_id)

        idol_id_set = set()
        for record in vote_records:
            idol_id = record['idol_id']
            if idol_id in idol_id_set:
                continue
            idol_id_set.add(idol_id)
            create_time = record['create_time']
            variety_cache.zadd(cache_key, get_timestamp_epoch(create_time), idol_id)
        variety_cache.expire(cache_key, 86400 * 30 * 5)

    @classmethod
    def get_user_voted_idol_ids(cls, user_id):
        """
        获取用户支持的学员id列表
        :param user_id:
        :return:
        """
        cache_key = cls.voted_idols.format(user_id=user_id)
        if not variety_cache.exists(cache_key):
            cls.set_user_voted_idol_ids(user_id)

        idol_ids = variety_cache.zrevrange(cache_key, 0, -1)
        return idol_ids

    @classmethod
    def update_user_voted_idol_ids(cls, user_id, idol_id):
        """
        更新用户支持的学员
        :param user_id:
        :param idol_id: 学员id
        :return:
        """
        cache_key = cls.voted_idols.format(user_id=user_id)
        if not variety_cache.exists(cache_key):
            cls.set_user_voted_idol_ids(user_id)

        variety_cache.zadd(cache_key, int(time.time()), idol_id)


class YoungInviteService(ServiceBase):
    """
    少年之名 邀请
    """

    invite_key = "young_invite_code"

    @classmethod
    def gen_code(cls, user_id):
        # 邀请码 = 时间戳（毫秒级）后6为 + 随机2位

        cnt = 20
        while cnt:
            pre = str(int(round(time.time() * 1000)))[-6:]
            end = '%02d' % int(random.random() * 100)
            code = '%08d' % (int(pre) * 100 + int(end))
            try:
                InviteCode.objects.get(invite_code=code)
                cnt -= 1
            except InviteCode.DoesNotExist:
                variety_cache.hset(cls.invite_key, user_id, code)
                return code

    @classmethod
    def get_code(cls, user_id):

        code = variety_cache.hget(cls.invite_key, user_id)
        if not code:
            try:
                u = InviteCode.objects.get(user_id=user_id)
                code = u.invite_code
            except InviteCode.DoesNotExist:
                code = cls.gen_code(user_id)
                u = InviteCode()
                u.user_id = user_id
                u.invite_code = code
                u.save()
            variety_cache.hset(cls.invite_key, user_id, code)

        return code


class YoungTaskService(ServiceBase):
    """
    少年之名 任务列表
    """
    YOUNG_TASK_TYPE_DAILY_LOGIN = "daily_login"
    YOUNG_TASK_TYPE_BROWSE_SPECIAL = "browse_special"
    YOUNG_TASK_TYPE_INVITE = "invite"
    YOUNG_TASK_TYPE_GROUP_BY = "group_by"

    task_type_key_mapping = {  # 任务类型: (任务名，次数)
        YOUNG_HIT_TASK_TYPE.DAILY_LOGIN: (YOUNG_TASK_TYPE_DAILY_LOGIN, 1),  # 登录
        YOUNG_HIT_TASK_TYPE.NEW_SPECIAL: (YOUNG_TASK_TYPE_BROWSE_SPECIAL, 1),  # 浏览专场
        YOUNG_HIT_TASK_TYPE.INVITE_USER_REGISTER: (YOUNG_TASK_TYPE_INVITE, 99999),  # 邀请
        YOUNG_HIT_TASK_TYPE.GROUP_BY: (YOUNG_TASK_TYPE_GROUP_BY, 1),  # 拼团
    }

    task_not_control_keys = [YOUNG_TASK_TYPE_INVITE, ]  # 不受次数控制的key

    # cache_key_configs
    cache_key_complete_tasks = "complete_tasks"  # 任务完成log  dict() {"login":1, ……}
    cache_key_total_vote_nums = "total_vote_nums"  # 任务完成总次数 int + 1

    @classmethod
    def _get_today_user_completed_tasks_from_sql(cls, user_id):
        """
        从sql中找用户今天完成的任务
        :param user_id:
        :return:
        """
        completed_dic = {
            cls.cache_key_total_vote_nums: 0
        }

        _today = datetime.date.today()
        sql_tasks_list = cls.get_querysets_data(
            model_=TaskRecord,
            query=Q(user_id=user_id, done_date=_today, is_valid=True),
            fields=["id", "task_type"]
        ).using(settings.SLAVE_DB_NAME)

        if not sql_tasks_list:
            return completed_dic

        for task_type, items in groupby(sorted(sql_tasks_list, key=itemgetter("task_type")), key=itemgetter("task_type")):
            _task_name, _control_num = cls.task_type_key_mapping.get(task_type, ("", 0))
            if not _task_name:
                continue
            _valid_nums = sum(1 for _ in items)

            if _task_name in cls.task_not_control_keys:
                _num = _valid_nums

            else:
                _num = _valid_nums if _control_num > _valid_nums else _control_num

            completed_dic[_task_name] = _num
            completed_dic[cls.cache_key_total_vote_nums] += _num

        return completed_dic

    @classmethod
    def _get_today_user_completed_tasks_from_cache(cls, cache_name):
        """
        从缓存中获取用户今天完成的任务
        :param cache_name:
        :return:
        """
        result = {}

        cache_keys = [cls.cache_key_complete_tasks, cls.cache_key_total_vote_nums]
        cache_values = variety_cache.hmget(cache_name, cache_keys)

        for k, v in zip(cache_keys, cache_values):
            if k == cls.cache_key_complete_tasks:
                _v = v and json.loads(v) or {}
                result.update(_v)

            elif k == cls.cache_key_total_vote_nums:
                _v = v and int(v) or 0
                result[k] = _v

            else:
                continue

        return result

    @classmethod
    def get_today_user_has_completed_tasks(cls, user_id, cache_name):
        """
        获取用户今天已完成的任务
        :param user_id:
        :param cache_name:
        :return:
        """
        completed_dic = cls._get_today_user_completed_tasks_from_cache(cache_name)
        if not completed_dic:
            completed_dic = cls._get_today_user_completed_tasks_from_sql(user_id)

        return completed_dic

    @classmethod
    def set_today_user_has_completed_tasks(cls, cache_name, completed_task_dic):
        """
        设置用户已完成任务
        :param cache_name:
        :param completed_task_dic:
        :return:
        """
        status = variety_cache.hmset(
            cache_name,
            completed_task_dic
        )

        return status

    @classmethod
    def get_today_user_tasks(cls, user_id):
        """
        获取用户今天任务完成的状态
        :param user_id:
        :return:
        """
        result = []

        cache_name = YoungCacheService.get_variety_show_young_user_cache_name(user_id)
        has_completed_dic = cls.get_today_user_has_completed_tasks(user_id, cache_name)

        task_config = cls.get_querysets_data(
            model_=TaskConfig,
            query=cls.base_query,
            fields=["new_special_id", "group_special_id"],
            order_by=["-id"]
        ).first() or {}

        for task_type, task_map in cls.task_type_key_mapping.items():
            task_name, control_num = task_map
            _completed_num = has_completed_dic.get(task_name, 0)

            not_control_key = bool(task_name in cls.task_not_control_keys)
            if not_control_key:
                done_status = False
            else:
                done_status = bool(_completed_num >= control_num)

            _data = {
                "task_type": task_type,
                "total_num": _completed_num if not_control_key else control_num,
                "done_num": _completed_num,
                "done_status": done_status,
                "jump_id": 0,
            }

            if task_type == YOUNG_HIT_TASK_TYPE.GROUP_BY:
                _data["jump_id"] = task_config.get("group_special_id", 0)

            elif task_type == YOUNG_HIT_TASK_TYPE.NEW_SPECIAL:
                _data["jump_id"] = task_config.get("new_special_id", 0)

            result.append(_data)

        return result

    @classmethod
    def set_today_user_completed_task(cls, user_id, task_type):
        """
        记录用户今天完成的任务
        :param user_id: 用户ID
        :param task_type: 任务类型
        :return:
        """
        data = {
            "code": CODES.SUCCESS,
            "task_record_id": 0
        }

        _task_name, control_num = cls.task_type_key_mapping.get(task_type, ("", 0))
        if not _task_name:
            gen(CODES.INVALID_PARAMS)

        # SQL 查询 看下当前任务的已完成记录
        _today = datetime.date.today()
        completed_task_count = cls.get_querysets_data(
            model_=TaskRecord,
            query=Q(user_id=user_id, task_type=task_type, done_date=_today, is_valid=True)
        ).count()  # 这块要用从库么？！
        # ).using(settings.SLAVE_DB_NAME).count()  # 这块要用从库么？！

        effect_task_record = False
        if _task_name in cls.task_not_control_keys or \
                (_task_name not in cls.task_not_control_keys and control_num > completed_task_count):
            effect_task_record = True
        else:
            data["code"] = CODES.ACTIVITY_TASK_MAX_ASSIST  # 当前任务超次数了!

        # 记录数据时，加锁
        _lock_cache_name, _lock_status = YoungCacheService.add_lock_cache(
            "{}_{}".format(YoungCacheService.cache_lock_record_prefix, _task_name), user_id)

        if not _lock_status:
            gen(CODES.ACCESS_LIMIT)

        # 双重校验
        if effect_task_record:
            cache_name = YoungCacheService.get_variety_show_young_user_cache_name(user_id)
            has_completed_dic = cls.get_today_user_has_completed_tasks(user_id, cache_name)

            # 如果redis里记录的数据高于控制数
            if _task_name not in cls.task_not_control_keys and has_completed_dic.get(_task_name, 0) >= control_num:
                effect_task_record = False
                data["code"] = CODES.ACTIVITY_TASK_MAX_ASSIST

            if effect_task_record and data["code"] == CODES.SUCCESS:  # 在可更新的情况下，再写入每天记录
                _total_nums = has_completed_dic.pop(cls.cache_key_total_vote_nums, 0)
                if _task_name not in has_completed_dic:
                    has_completed_dic[_task_name] = 0
                has_completed_dic[_task_name] += 1  # 当项任务完成 + 1
                _total_nums += 1  # 总任务完成次数 + 1

                # 完成任务记录
                cls.set_today_user_has_completed_tasks(
                    cache_name,
                    {
                        cls.cache_key_complete_tasks: json.dumps(has_completed_dic),
                        cls.cache_key_total_vote_nums: _total_nums
                    }
                )

        # 这里把完成的每次任务都记录下来
        task_record = TaskRecord.objects.create(
            user_id=user_id,
            task_type=task_type,
            is_valid=effect_task_record  # 是否有效，通过是否写入状态判断
        )
        data["task_record_id"] = task_record.id

        # 移除锁
        YoungCacheService.unlock_cache(_lock_cache_name)

        return data


class YoungVotedService(ServiceBase):
    """
    少年之名 投票
    """
    can_voted_idol_nums = settings.VARIETY_SHOW_YOUNG_CONFIG["can_voted_idol_nums"]  # 可投票人数控制

    # cache_key_configs
    cache_key_voted_idol_ids = "voted_idol_ids"  # 已投票选手ID  set()
    cache_key_total_vote_nums = "total_vote_nums"  # 任务完成总次数 int + 1
    cache_key_vote_records = "vote_records"  # 投票记录     dict() {idol_id1: num1, idol_id2: num2}

    @staticmethod
    def get_variety_show_young_user_cache_name(user_id):
        return YoungCacheService.get_variety_show_young_user_cache_name(user_id)

    @classmethod
    def _get_today_user_has_voted_idols_info_from_sql(cls, user_id):
        """
        从sql中找当前用户当天已投票的用户
        :param user_id:
        :return:
        """
        _data = {
            cls.cache_key_vote_records: {},
            cls.cache_key_voted_idol_ids: [],
        }

        _today = datetime.date.today()
        sql_voted_list = cls.get_querysets_data(
            model_=VoteRecord,
            query=Q(user_id=user_id, vote_date=_today, is_fake=False),
            fields=["idol_id", "votes"]
        ).using(settings.SLAVE_DB_NAME)

        if not sql_voted_list:
            return _data

        for idol_id, items in groupby(sorted(sql_voted_list, key=itemgetter("idol_id")), key=itemgetter("idol_id")):
            _data[cls.cache_key_voted_idol_ids].append(idol_id)
            _data[cls.cache_key_vote_records].update({
                idol_id: sum(item.get("votes") for item in items)
            })

        return _data

    @classmethod
    def _get_today_user_has_voted_idols_info_from_cache(cls, cache_name):
        """
        从 redis 中读当前用户已投票选手记录
        :param cache_name:
        :return:
        """
        cache_keys = [cls.cache_key_voted_idol_ids, cls.cache_key_total_vote_nums, cls.cache_key_vote_records]
        cache_values = variety_cache.hmget(cache_name, cache_keys)

        _data = {}
        for k, v in zip(cache_keys, cache_values):
            if k == cls.cache_key_total_vote_nums:
                _v = int(v or 0)

            elif k == cls.cache_key_voted_idol_ids:
                _v = v and json.loads(v) or []

            elif k == cls.cache_key_vote_records:
                _v = v and json.loads(v) or {}

            else:
                continue

            _data[k] = _v

        return _data

    @classmethod
    def get_today_user_has_voted_idols_info(cls, user_id, cache_name):
        """
        获取用户今天已投票的选手信息
        :param user_id:
        :param cache_name:
        :return:
        """
        result = cls._get_today_user_has_voted_idols_info_from_cache(cache_name)

        if not all([result.get(cls.cache_key_voted_idol_ids), result.get(cls.cache_key_vote_records)]):
            _data = cls._get_today_user_has_voted_idols_info_from_sql(user_id)
            result.update(_data)

        return result

    @classmethod
    def set_today_user_vote_for_contestants(cls, user_id, idol_id):
        """
        为选手投票
        :param user_id:
        :param idol_id:
        :return:
        """
        cache_name = cls.get_variety_show_young_user_cache_name(user_id)

        # 1、判断选手是否在可有效投票的选手列表里
        # 2、判断投票次数与总投票次数是否一致
        # 3、可投票。判断投票数量

        cache_data = cls.get_today_user_has_voted_idols_info(user_id, cache_name)

        has_voted_total_nums = cache_data.get(cls.cache_key_total_vote_nums, 0)  # 票不足
        if not has_voted_total_nums:
            gen(CODES.VOTE_NUM_NOT_ENOUGH)

        has_voted_idol_ids = cache_data.get(cls.cache_key_voted_idol_ids) or []
        user_voted_record = cache_data.get(cls.cache_key_vote_records) or {}
        idol_voted_num = int(user_voted_record.get(str(idol_id)) or 0)  # 当前用户对选手投票数

        # 数量上限判断
        if any([
            all([idol_id not in has_voted_idol_ids, len(has_voted_idol_ids) >= cls.can_voted_idol_nums]),
            idol_voted_num >= has_voted_total_nums
        ]):
            gen(CODES.FD_EXCEED_VOTE_LIMIT)

        # 数量更新
        new_voted_idol_ids = set(has_voted_idol_ids)
        new_voted_idol_ids.add(idol_id)

        user_voted_record[str(idol_id)] = has_voted_total_nums

        the_time_voted_nums = has_voted_total_nums - idol_voted_num

        # Redis记录数据时，加锁
        _lock_cache_name, _lock_status = YoungCacheService.add_lock_cache(
            YoungCacheService.cache_lock_vote_prefix,
            cache_name
        )

        if not _lock_status:
            gen(CODES.ACCESS_LIMIT)

        # 记录
        variety_cache.hmset(
            cache_name,
            {
                cls.cache_key_voted_idol_ids: json.dumps(list(new_voted_idol_ids)),
                cls.cache_key_vote_records: json.dumps(user_voted_record),
            }
        )

        YoungCacheService.update_user_voted_idol_ids(user_id, idol_id)

        VoteRecord.objects.create(
            user_id=user_id,
            idol_id=idol_id,
            votes=the_time_voted_nums
        )

        # 触发异步任务，虚拟加票
        fake_vote_num_for_idol.delay(
            user_id=user_id,
            idol_id=idol_id
        )

        #  选手总票数增加
        idol_add_vote_num(
            idol_id=idol_id,
            vote_num=the_time_voted_nums,
            field_name="real_votes"
        )

        # 异步任务针对选手加票
        # idol_add_vote_num.delay(
        #     idol_id=idol_id,
        #     vote_num=the_time_voted_nums,
        #     field_name="real_votes"
        # )

        # variety_cache.hincrby(
        #     YoungCacheService.variety_show_young_idol_cache_name,
        #     YoungCacheService.cache_key_idol_real_voted_num_f.format(idol_id),
        #     the_time_voted_nums
        # )

        # 释放锁
        YoungCacheService.unlock_cache(_lock_cache_name)

        return gen(CODES.SUCCESS)

    @classmethod
    def get_idol_voted_nums_by_idol_ids(cls, idol_ids):
        """
        通过选手ID获取票数
        :param idol_ids:
        :return:
        """
        result = {}
        redis_pre_get_val_dic = {
            idol_id: [
                (YoungCacheService.cache_key_idol_real_voted_num_f.format(idol_id), "real_votes"),
                (YoungCacheService.cache_key_idol_real_voted_num_f.format(idol_id), "fake_votes"),
            ] for idol_id in idol_ids
        }

        cache_names = list(map(itemgetter(0), chain.from_iterable(redis_pre_get_val_dic.values())))
        cache_values = variety_cache.hmget(
            YoungCacheService.variety_show_young_idol_cache_name,
            cache_names
        )
        redis_vs = {
            k: int(v or 0) for k, v in zip(cache_names, cache_values)
        }

        for idol_id, cn_ls in redis_pre_get_val_dic.items():
            result[idol_id] = {k: redis_vs.get(cache_n, 0) for cache_n, k in cn_ls}

        return result
