# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function

import random
import json
import datetime
from datetime import timedelta

from gm_types.gaia import USER_RIGHTS_LEVEL, POINTS_TYPE
from gm_types.point.error import ERROR
from gm_types.point import ACTIVITY_TYPE, GOODS_TYPE, LOTTERY_RESULT

from django.core import serializers
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.db import transaction
from django.utils import timezone

import point

from api.models import ChannelGift, Person, User
from point.models import PointActivity, PointActivityGoods, PointUserGoods, PointGoods, PointSnapshot

from rpc.tool.error_code import gen
from api.tool.coupon_tool import check_can_get_channel_gift_ids, _try_get_gift

from .point_activity_manager import get_point_activity_goods_id_to_count, get_channel_gift_id_to_coupon_infos

NOTHING_NUMBER = settings.POINT_MALL_LOTTERY_NOTHING_NUMBER
TRY_AGAIN_NUMBER = settings.POINT_MALL_LOTTERY_TRY_AGAIN_NUMBER


def _try_get_point_activity(point_activity_id, now=None):
    pa = PointActivity.objects.filter(id=point_activity_id, activity_type=ACTIVITY_TYPE.LOTTERY,
                                      is_online=True).first()
    #  pa.describe
    if not pa:
        gen(ERROR.POINT_ACTIVITY_NOT_EXIST)

    if now > pa.end_time:
        gen(ERROR.POINT_ACTIVITY_EXPIRE)

    if now < pa.start_time:
        gen(ERROR.POINT_ACTIVITY_NOT_START)

    return pa


def _get_point_activity_goods_in_pool(point_activity):
    #  可以在不同的情况下对外部传入的point_activity进行锁定
    #  先全部捞出来，因为券还有其他库存限制问题

    pags = list(PointActivityGoods.objects.filter(
        activity_id=point_activity.id, is_online=True, current_stock__gt=0,
    ).select_related('good').order_by('order', '-id'))

    all_channel_gift_id = {pa.good.channel_gift_id for pa in pags
                           if pa.good and pa.good.channel_gift_id and pa.good.goods_type == GOODS_TYPE.COUPON}

    can_get_channel_gift_ids_set, already_get_channel_gift_ids_set = check_can_get_channel_gift_ids(
        all_channel_gift_id, user_id=None)

    can_use_point_activity_goods = []

    for pag in pags:
        #  对于某个用户达到了礼品抽中上限，但是礼品还有的情况下，用户还是能在pool内看到这个东西
        #  但是实际如果抽中了，那么会fallback掉
        if pag.good.goods_type == GOODS_TYPE.COUPON:
            #  美券需要检查库存
            if pag.good.channel_gift_id in can_get_channel_gift_ids_set:
                can_use_point_activity_goods.append(pag)
        else:
            #  其他直接加入候选列表
            can_use_point_activity_goods.append(pag)

    # 取前placeholder个
    result = can_use_point_activity_goods[:point_activity.placeholder]

    return result


def _get_fake_fake_user_id_and_activity_goods_ids(point_activity_id,
                                                  max_record_count,
                                                  all_fake_user_ids,
                                                  all_goods_id):
    # 这个函数里面的所有行为都要使用同一个seed处理
    # 1. 只返回最多20条记录
    # 2. 只返回抽中记录的最新20条
    # 3. 在抽中记录不够的时候，使用马甲信息填充
    # 4. 马甲信息需要保证对于一次抽奖是伪随机但是固定的，对于实物类奖品只能生成一条信息。

    result = []

    try:
        from random import Random
        rn = Random(point_activity_id)

        max_record_count = max_record_count if max_record_count < len(all_goods_id) else len(all_goods_id)

        all_fuids = list(all_fake_user_ids)
        all_gids = list(all_goods_id)
        rn.shuffle(all_fuids)
        rn.shuffle(all_gids)

        r1 = all_fuids[: max_record_count]
        r2 = all_gids[: max_record_count]
        result = zip(r1, r2)
    except:
        pass

    return result


def _get_user_id_and_goods_id_list_for_lottery(point_activity_id, point_activity_goods_in_pool):
    max_record_count = 20

    id_user_id_and_activity_goods_ids = list(PointUserGoods.objects.filter(
        activity_id=point_activity_id, activity_goods_id__isnull=False,
        activity_goods__good__goods_type__in=[GOODS_TYPE.COUPON, GOODS_TYPE.POINT, GOODS_TYPE.REAL]
    ).values_list('id', 'user_id', 'activity_goods__good_id').order_by('-create_time')[:max_record_count])

    fake_user_id_and_activity_goods_ids = []
    if len(id_user_id_and_activity_goods_ids) < max_record_count:
        all_goods_id = []
        for pag in point_activity_goods_in_pool:
            n = 1 if pag.good.goods_type == GOODS_TYPE.REAL else max_record_count
            if pag.good.goods_type in [GOODS_TYPE.THSJOIN, GOODS_TYPE.ONEAGAIN]:
                n = 0
            all_goods_id.extend([pag.good_id] * n)

        all_fake_user_ids = list(Person.objects.filter(
            is_puppet=True).values_list('user_id', flat=True).order_by('id')[:max_record_count * 3])

        fake_user_id_and_activity_goods_ids = _get_fake_fake_user_id_and_activity_goods_ids(
            point_activity_id, max_record_count, all_fake_user_ids, all_goods_id
        )

    user_id_and_activity_goods_ids = [(uid, gid) for _, uid, gid in id_user_id_and_activity_goods_ids]

    user_id_and_goods_id_list = user_id_and_activity_goods_ids + fake_user_id_and_activity_goods_ids

    return user_id_and_goods_id_list


def get_lottery_item_list(point_activity_id, now=None):
    if not now:
        now = datetime.datetime.now()

    point_activity = _try_get_point_activity(point_activity_id, now)

    point_activity_goods_in_pool = _get_point_activity_goods_in_pool(point_activity)

    item_info_list = [{
                          "thumb": p.good.thumb,
                          "name": p.good.name,
                      } for p in point_activity_goods_in_pool]

    user_id_and_goods_id = _get_user_id_and_goods_id_list_for_lottery(point_activity_id, point_activity_goods_in_pool)

    all_user_id = {uid for uid, gid in user_id_and_goods_id}
    all_good_id = {gid for uid, gid in user_id_and_goods_id}

    user_id_to_name = {uid: uname for uid, uname in
                       User.objects.filter(id__in=all_user_id).values_list('id', 'last_name')}

    #中奖记录只留有效的物品
    valid_goods_type = [GOODS_TYPE.COUPON, GOODS_TYPE.POINT, GOODS_TYPE.REAL]
    good_id_to_name = {gid: gname for gid, gname in
                       PointGoods.objects.filter(id__in=all_good_id, goods_type__in=valid_goods_type).values_list('id', 'name')}

    lottery_record = [{"user_name": user_id_to_name[uid], "goods_name": good_id_to_name[gid]}
                      for uid, gid in user_id_and_goods_id if uid in user_id_to_name and gid in good_id_to_name]

    return point_activity.id, point_activity.cost, item_info_list, lottery_record


def draw_lottery(point_activity_id, user, now=None, special_id=None):
    if not now:
        now = datetime.datetime.now()

    temp_point_activity = _try_get_point_activity(point_activity_id, now)

    result = {
        "lottery_result": None,
        "selected_point_activity_goods_info": {}
    }

    with transaction.atomic():
        point_activity = PointActivity.objects.select_for_update().get(id=temp_point_activity.id)

        user_point = point.get_point_for(user)
        if user_point < point_activity.cost:
            result['lottery_result'] = LOTTERY_RESULT.POINT_NOT_ENOUGH
            return result

        today = datetime.datetime(now.year, now.month, now.day)
        next_day = today + datetime.timedelta(days=1)

        today_user_count = PointUserGoods.objects.filter(
            user_id=user.id, activity_id=point_activity_id,
            create_time__gte=today, create_time__lt=next_day
        ).count()

        if today_user_count >= point_activity.frequent_limit:
            result['lottery_result'] = LOTTERY_RESULT.FREQUENT_LIMIT
            return result

        user_level_number = int(user.userextra and user.userextra.user_rights_level or USER_RIGHTS_LEVEL.V1)

        lottery_result = None
        selected_point_activity_goods_info = {}
        selected_point_activity_goods_id = None

        get_point = 0

        # 谢谢参与跟再来一次不再固定概率
        # first_random_int = random.randint(1, 100)

        # if first_random_int <= NOTHING_NUMBER:
        #     lottery_result = LOTTERY_RESULT.NOTHING
        # elif NOTHING_NUMBER < first_random_int <= TRY_AGAIN_NUMBER:
        #      再试一次可以快速结束
            # result['lottery_result'] = LOTTERY_RESULT.TRY_AGAIN
            # return result
        # else:
            #  顺利通过前两者
        selected_point_activity_goods_info, just_try_again = _try_lock_good(point_activity, user)
        if just_try_again:
            #  再试一次可以快速结束
            result['lottery_result'] = LOTTERY_RESULT.TRY_AGAIN
            return result

        if not selected_point_activity_goods_info:
            # 失败了，可能是上面两种失败，也可能是试图去锁定库存之类失败了，当NOTHING
            lottery_result = LOTTERY_RESULT.NOTHING
        else:
            result["selected_point_activity_goods_info"] = selected_point_activity_goods_info
            good_type = selected_point_activity_goods_info["goods_type"]
            selected_point_activity_goods_id = selected_point_activity_goods_info["activity_goods_id"]

            if good_type == GOODS_TYPE.COUPON:
                lottery_result = LOTTERY_RESULT.COUPON
            elif good_type == GOODS_TYPE.POINT:
                lottery_result = LOTTERY_RESULT.POINT
                get_point = selected_point_activity_goods_info["point"]
            elif good_type == GOODS_TYPE.REAL:
                lottery_result = LOTTERY_RESULT.REAL
            elif good_type == GOODS_TYPE.ONEAGAIN:
                lottery_result = LOTTERY_RESULT.TRY_AGAIN
            else:
                lottery_result = LOTTERY_RESULT.NOTHING

        result['lottery_result'] = lottery_result

        #  如果是通过_try_lock_good到达的，那么券和库存之类本身已经处理好了，下面只要扣分和写入领取记录

        # 写入获取记录

        exchange_use_point = point_activity.cost

        pug = PointUserGoods()
        pug.user_id = user.id
        pug.point = exchange_use_point
        pug.user_level = user_level_number
        pug.activity_goods_id = selected_point_activity_goods_id
        pug.activity_id = point_activity_id
        pug.special_id = special_id
        pug.save()

        pag = list(PointActivityGoods.objects.select_related('good').filter(id=selected_point_activity_goods_id))
        goods = [pag[0].good] if pag else []

        point_snapshot = PointSnapshot()
        point_snapshot.user_goods = pug
        point_snapshot.user_exchange = serializers.serialize('json', [pug])
        point_snapshot.goods = serializers.serialize('json', goods)
        point_snapshot.activity = serializers.serialize('json', [point_activity])
        point_snapshot.activity_goods = serializers.serialize('json', pag)

        user_extra_json = json.dumps({
            "phone": user.person.phone,
            "address": user.userextra.address,
        })

        point_snapshot.extra = user_extra_json
        point_snapshot.save()

        #  最后扣分，失败可以回滚

        point.remove(
            user_id=user.id,
            reason=POINTS_TYPE.POINTS_LOTTERY,
            point=exchange_use_point,
        )

        if get_point > 0:
            point.add(user_id=user.id, reason=POINTS_TYPE.GET_POINTS_FROM_LOTTERY, point=get_point)

        return result


def _try_lock_good(point_activity, user):
    point_activity_goods_in_pool = _get_point_activity_goods_in_pool(point_activity)

    all_weight_and_pag_id = [(p.weight, p.id) for p in point_activity_goods_in_pool if p.weight >= 0]

    total_weight = sum([w for w, _ in all_weight_and_pag_id])

    if total_weight <= 0:
        return None, False
    else:
        selected_point_activity_goods_id = None

        array = []
        i = 1
        for w, pag_id in all_weight_and_pag_id:
            array.append((i, i + w - 1, pag_id))
            i += w
        selected = random.randint(1, total_weight)

        for s, e, pag_id in array:
            if s <= selected <= e:
                selected_point_activity_goods_id = pag_id

        if not selected_point_activity_goods_id:
            return None, False

        with transaction.atomic():
            # 锁定pag
            pag = PointActivityGoods.objects.select_for_update().select_related('good').get(
                id=selected_point_activity_goods_id
            )

            # 谢谢惠顾 再来一次 不需要后续处理
            if pag.good.goods_type == GOODS_TYPE.ONEAGAIN:
                return None, True

            # 判断本次抽奖命中的礼品是不是达到了用户抽中的上限，如果是的话，让用户TRY_AGAIN
            user_point_activity_goods_id_to_count = get_point_activity_goods_id_to_count([pag.id], user.id)
            point_activity_goods_count = user_point_activity_goods_id_to_count.get(pag.id, 0)

            if point_activity_goods_count >= pag.limit:
                return None, True

            # 库存检查，美券还要先能拿到才能扣库存，由于这里获取pool的时候是锁定的，所以应该不会出现库存不够...
            if pag.current_stock < 1:
                return None, False

            if pag.good.goods_type == GOODS_TYPE.COUPON:
                #  由于券的库存不受这边限制，所以可能出现检查pool的时候有券，实际分配的时候已经没了，如果是的话，让用户TRY_AGAIN
                channel_gift_id = pag.good.channel_gift_id
                cg = ChannelGift.objects.select_for_update().get(id=channel_gift_id)
                try:
                    success, codes = _try_get_gift(cg, user)
                except:
                    success = None
                if not success:
                    return None, True

            # 能活着到这里，才开始减库存什么
            # 谢谢惠顾不减库存
            if pag.good.goods_type != GOODS_TYPE.THSJOIN:
                pag.current_stock -= 1
            pag.save()

            point = None
            coupon_values = None
            coupon_count = None

            if pag.good.goods_type == GOODS_TYPE.POINT:
                point = pag.good.point
            elif pag.good.goods_type == GOODS_TYPE.COUPON:
                channel_gift_id_to_coupon_infos = get_channel_gift_id_to_coupon_infos([pag.good.channel_gift_id])
                if pag.good.channel_gift_id in channel_gift_id_to_coupon_infos:
                    coupon_values = channel_gift_id_to_coupon_infos[pag.good.channel_gift_id]["coupon_values"]
                    coupon_count = channel_gift_id_to_coupon_infos[pag.good.channel_gift_id]["coupon_count"]

            selected_point_activity_goods_info = {
                "activity_goods_id": selected_point_activity_goods_id,
                "goods_type": pag.good.goods_type,
                "name": pag.good.name,
                "thumb": pag.good.thumb,
                "image": pag.good.image,
                "describe": pag.good.describe,
                "comment": pag.good.comment,

                "point": point,
                "coupon_values": coupon_values,
                "coupon_count": coupon_count,
            }
            return selected_point_activity_goods_info, False
