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

import datetime
import json
import random

from django.conf import settings
from django.db.models import Q, Count, F
from django.conf import settings

from api.models import CouponInfo, ChannelGift, CouponGift, ChannelGiftUserRelation, ChannelGiftStatistics
from api.models import GiftToCoupon, Service, Coupon, ServiceItem, SpecialService, Special, SpecialItem
from api.models import Doctor, CouponSpecialRestrict, CouponDoctorRestrict, CouponSKURestrict

from api.models.coupon import get_coupon_limited_doctor_id_set, get_limited_max_gengmei_percent

from django.db import transaction

from gm_types.gaia import COUPON_STATUS, COUPON_DISTRIBUTION_STATUS, COUPON_GIFT_TYPES, \
    COUPON_TYPES, BENEFIT_TYPE, DOCTOR_USE_COUPON_TYPE
from gm_types.trade import COUPON_TIME_TYPE

from api.tool.datetime_tool import get_timestamp_epoch
from rpc.cache import coupon_cache
from rpc.tool.error_code import gen, CODES
from rpc.tool.log_tool import logging_exception


def create_coupon_info(channel_gift, user, relation):
    channel_gift.can_be_claimed()

    if channel_gift.gift_enable is not True:
        raise gen(CODES.COUPON_UNAVAILABLE)

    gift_info_data = channel_gift.gift.info_data
    coupon_ids = [cd.get('id') for cd in gift_info_data.get('coupons', [])]

    if len(coupon_ids) > 0:
        gift_coupons = GiftToCoupon.objects.filter(coupon_gift=channel_gift.gift, coupon_id__in=coupon_ids)
        # generate couponInfo and bind it 2 user
        for gift_coupon in gift_coupons:
            for x in range(gift_coupon.number):
                CouponInfo.objects.create_coupon(coupon=gift_coupon.coupon, user=user, relation=relation)


def _get_gift_id_and_types(service_id, special_show_coupon_gift_ids, now=None):
    """
    缓存美购礼包信息
    获取美购可用的礼包数据 coupon_gift
    :param cache_time: 缓存时间 秒
    :return: list [
    # 礼包id,  列表类型,    包含的券种类,           ,   包含券总张数
     ( id,  gift_type, len(distinct(coupon_id)), sum(coupon_id*number) ) ...]
    """

    def _get_from_db(service_id, special_show_coupon_gift_ids, now, cache_time):
        try:
            service = Service.objects.get(id=service_id)
        except Service.DoesNotExist:
            return []

        coupon_ids = _get_coupon_ids_for_service_detail(now, service)

        if len(coupon_ids) + len(special_show_coupon_gift_ids) == 0:
            return []

        from api.models import GiftToCoupon

        start_time = now
        end_time = now + datetime.timedelta(seconds=cache_time)

        gift_infos = list(GiftToCoupon.objects.filter(coupon_enable=True)
                          .filter(Q(coupon_id__in=coupon_ids) | Q(coupon_gift_id__in=special_show_coupon_gift_ids))
                          .filter(Q(coupon_gift__start_time__lte=end_time, coupon_gift__end_time__gte=start_time))
                          .values_list('coupon_gift_id', 'coupon_gift__start_time', 'coupon_gift__end_time',
                                       'coupon_gift__gift_type')
                          .order_by('coupon_gift__gift_type', 'coupon_gift__end_time', 'coupon_gift_id')
                          .distinct())

        all_coupon_gift_id = [gi[0] for gi in gift_infos]

        limited_doctor_id_set = get_coupon_limited_doctor_id_set()
        if service.doctor_id in limited_doctor_id_set:
            limited_max_gengmei_percent = get_limited_max_gengmei_percent()
            invalid_gift_ids = {gift_id for gift_id in GiftToCoupon.objects.filter(
                coupon_gift_id__in=all_coupon_gift_id, coupon__coupon_type=COUPON_TYPES.PLATFORM,
                coupon__gengmei_percent__gt=limited_max_gengmei_percent).values_list('coupon_gift_id', flat=True)}
            if invalid_gift_ids:
                new_gift_infos = [info for info in gift_infos if info[0] not in invalid_gift_ids]
                gift_infos = new_gift_infos
                all_coupon_gift_id = [gi[0] for gi in gift_infos]

        cgid_2_cid_number = list(GiftToCoupon.objects.filter(
            coupon_gift_id__in=all_coupon_gift_id
        ).values_list('coupon_gift_id', 'coupon_id', 'number'))

        cgid_2_cid_set = {}
        cgid_2_number = {}

        for cgid, cid, n in cgid_2_cid_number:
            if cgid not in cgid_2_cid_set:
                cgid_2_cid_set[cgid] = set()
            cgid_2_cid_set[cgid].add(cid)

            if cgid not in cgid_2_number:
                cgid_2_number[cgid] = 0
            cgid_2_number[cgid] += n

        gis = [[gi[0], gi[1], gi[2], gi[3], len(cgid_2_cid_set.get(gi[0], ())), cgid_2_number.get(gi[0], 0)] for gi in
               gift_infos]

        # return [list(item) for item in gift_infos]
        return gis

    def _dump_to_json(raw_data):
        from copy import deepcopy
        raw_data = deepcopy(raw_data)
        for item in raw_data:
            item[1] = get_timestamp_epoch(item[1])
            item[2] = get_timestamp_epoch(item[2])
        return json.dumps(raw_data)

    def _load_from_redis(raw_data):
        raw_data = json.loads(raw_data)
        for item in raw_data:
            item[1] = datetime.datetime.fromtimestamp(item[1])
            item[2] = datetime.datetime.fromtimestamp(item[2])
        return raw_data

    if now is None:
        now = datetime.datetime.now()

    # 处理cache版本的一个好办法是变更key，但是前提是只有单一（？）程序读写的时候才可以这么做
    cache_key = "getgiftinfos3ba4_v2:{}".format(service_id)
    raw_data = coupon_cache.get(cache_key)
    if raw_data:
        result = _load_from_redis(raw_data)
    else:
        cache_time = random.randint(40, 60)
        result = _get_from_db(service_id, special_show_coupon_gift_ids, now, cache_time)
        coupon_cache.setex(cache_key, cache_time, _dump_to_json(result))

    # 按照真正的now捞出来所需要的数据
    r = map(lambda x: (x[0], x[3], x[4], x[5]), filter(lambda x: x[1] <= now <= x[2], result))
    return r


def get_coupon_gift_info_for_service_detail(user, service_id, start_num=0, count=10, now=None):
    """
    由于每一次详情页调用和美购列表每一项都会涉及这里，因此性能非常重要，user可以为None
    :return
        [ ( gift_id, gift_type, distinct_coupon_id_count, sum_coupon_number) ...], #can get
         [ ( gift_id, gift_type, distinct_coupon_id_count, sum_coupon_number) ...] #alread get
    """
    special_show_coupon_gift_ids = []
    try:
        # config_data = settings.SPECIAL_SHOW_COUPON_GIFT_IDS
        global_gift_ids_json = coupon_cache.get("global_gift_ids")
        global_gift_ids = json.loads(global_gift_ids_json) if global_gift_ids_json else []
        if global_gift_ids and isinstance(global_gift_ids, list):
            special_show_coupon_gift_ids_set = set([d for d in global_gift_ids if isinstance(d, int) and d > 0])
            special_show_coupon_gift_ids = list(special_show_coupon_gift_ids_set)
    except BaseException:
        logging_exception()

    gift_id_and_types_data = _get_gift_id_and_types(service_id, special_show_coupon_gift_ids, now)

    if len(gift_id_and_types_data) == 0:
        return [], []

    gift_id_to_info = {
        gid: (gid, gtype, distinct_coupon_id_count, sum_coupon_number)
        for gid, gtype, distinct_coupon_id_count, sum_coupon_number in gift_id_and_types_data
    }
    gift_ids = [gi[0] for gi in gift_id_and_types_data]

    can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count = _get_user_gift_ids(gift_ids, user)
    temp_result = (can_get_gift_ids + already_get_gift_ids)[start_num: start_num + count]
    result = [gift_id_to_info[gid] for gid in temp_result]
    return [x for x in result if x[0] in can_get_gift_ids], [x for x in result if x[0] in already_get_gift_ids]


def _get_coupon_ids_for_service_detail(now, service):
    '''
    获取spu可用的有效券id列表, 包含平台券|尾款券
    :param now:
    :param service: service_obj
    :return:
    '''
    # SKUs -> SpecialItem -> Special -> [Platform]Coupon
    # SPU -> Special -> [Platform]Coupon
    # Doctor -> [Platform]Coupon
    # SKUs ->  [Doctor]Coupon

    item_ids = service.get_can_sell_item_ids()
    doctor_id = service.doctor_id

    base_coupon_filter_query = Q(distribution_status=COUPON_DISTRIBUTION_STATUS.OPEN) & \
        (
          (Q(end_time__gte=now) & Q(time_type=COUPON_TIME_TYPE.START_END))
          |
          Q(time_type=COUPON_TIME_TYPE.COUNTDOWN)
        )

    # 平台券
    platform_coupon_filter_query = base_coupon_filter_query & Q(coupon_type=COUPON_TYPES.PLATFORM)
    platform_coupon_ids = set(Coupon.objects.filter(platform_coupon_filter_query).values_list('id', flat=True))

    spu_special_ids = list(SpecialService.objects.filter(service_id=service.id).values_list('special_id', flat=True))
    sku_special_ids = list(SpecialItem.objects.filter(serviceitem_id__in=item_ids).values_list('special_id', flat=True))
    total_special_ids = set(spu_special_ids + sku_special_ids)
    special_ids = list(Special.objects.filter(
        is_online=True, start_time__lt=now, end_time__gt=now,
        id__in=total_special_ids
    ).values_list('id', flat=True))

    platform_special_coupon_ids = set(CouponSpecialRestrict.objects.filter(
        special_id__in=special_ids, coupon_id__in=platform_coupon_ids
    ).values_list('coupon_id', flat=True).distinct())

    platform_doctor_coupon_ids = set(CouponDoctorRestrict.objects.filter(
        doctor_id=doctor_id, coupon_id__in=platform_coupon_ids
    ).values_list('coupon_id', flat=True).distinct())

    # 尾款券
    doctor_coupon_filter_query = base_coupon_filter_query & Q(coupon_type=COUPON_TYPES.DOCTOR)
    sku_filter_query = Q(restric_skus__sku_id__in=item_ids)
    doctor_sku_coupon_ids = set(Coupon.objects.filter(
        doctor_coupon_filter_query & sku_filter_query
    ).values_list('id', flat=True))

    # 两个platform查询如果合并的话，会生成一个看上去执行计划很糟糕的SQL，目前暂时放弃这种做法
    coupon_ids = platform_special_coupon_ids | platform_doctor_coupon_ids | doctor_sku_coupon_ids
    return coupon_ids


def get_coupon_gift_id_for_doctor_detail(user, doctor_id, start_num=0, count=10, now=None):
    """
    :return [can get ids ...], [alread get ids ...],  { gift_id: 666 }
    """
    if doctor_id is None or start_num < 0 or count <= 0:
        return [], [], {}

    if now is None:
        now = datetime.datetime.now()

    doctor = None
    try:
        doctor = Doctor.objects.get(id=doctor_id)
    except Doctor.DoesNotExist:
        pass

    if doctor is None:
        return [], [], {}

    sku_ids = doctor.get_can_sell_sku_ids()
    can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count = _get_doctor_gift_id_and_left_count_by_skuids(
        sku_ids, start_num, count, user, now)
    return can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count


def _get_doctor_gift_id_and_left_count_by_skuids(sku_ids, start_num, count, user, now):
    if len(sku_ids) == 0:
        return [], [], {}

    coupon_filter_query = Q(distribution_status=COUPON_DISTRIBUTION_STATUS.OPEN) & (
        (Q(end_time__gte=now) & Q(time_type=COUPON_TIME_TYPE.START_END)) | Q(time_type=COUPON_TIME_TYPE.COUNTDOWN)) & Q(
        coupon_type=COUPON_TYPES.DOCTOR) & Q(
        restric_skus__sku_id__in=sku_ids)

    coupon_ids = set(Coupon.objects.filter(coupon_filter_query).values_list('id', flat=True))

    if len(coupon_ids) == 0:
        return [], [], {}

    from api.models import GiftToCoupon

    gift_infos = set(GiftToCoupon.objects.filter(coupon_id__in=coupon_ids, coupon_enable=True,
                                                 coupon_gift__start_time__lte=now,
                                                 coupon_gift__end_time__gte=now,
                                                 coupon_gift__gift_type=COUPON_GIFT_TYPES.DOCTOR)
                     .values_list('coupon_gift_id', 'coupon_gift__created_time'))

    if len(gift_infos) == 0:
        return [], [], {}

    gift_infos = sorted(gift_infos, key=lambda x: x[1], reverse=True)  # 按照创建时间从大到小排序

    gift_ids = [x[0] for x in gift_infos]

    # 医生版暂时不关注用户已经领取的
    can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count = _get_user_gift_ids(gift_ids, user)
    result = (can_get_gift_ids + already_get_gift_ids)[start_num: start_num + count]
    return [x for x in result if x in can_get_gift_ids], \
           [x for x in result if x in already_get_gift_ids], \
        gift_id_to_left_count


def get_sku_id_to_doctor_coupon_info_list(sku_ids, now=None):
    if now is None:
        now = datetime.datetime.now()

    valid_sku_ids = {si for si in ServiceItem.objects.filter(
        id__in=sku_ids, is_delete=False).values_list('id', flat=True)}

    can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count = _get_doctor_gift_id_and_left_count_by_skuids(
        valid_sku_ids, 0, 2147483647, None, now
    )

    coupon_id_to_gift_id = {}

    for coupon_id, coupon_gift_id in list(GiftToCoupon.objects.filter(
            coupon_gift_id__in=can_get_gift_ids, coupon_enable=True
    ).values_list('coupon_id', 'coupon_gift_id')):
        coupon_id_to_gift_id[coupon_id] = coupon_gift_id

    coupon_ids = coupon_id_to_gift_id.keys()
    coupon_id_to_coupon_data = {data['id']: data for data in Coupon.objects.filter(
        id__in=coupon_ids
    ).values('id', 'doctor_id', 'has_threshold', 'prepay_threshold', 'value')}

    doctor_ids = {v["doctor_id"] for k, v in coupon_id_to_coupon_data.items()}

    all_doctor = list(Doctor.objects.filter(id__in=doctor_ids).select_related('business_partener'))

    doctor_id_to_info = {}

    for doctor in all_doctor:
        business_group_id = None
        business_group_name = None

        if doctor.business_group:
            from themis.models import Team
            business_group_id = doctor.business_group
            team = Team.objects.filter(id=doctor.business_group).values_list('id', 'name').first()
            if team:
                business_group_name = team[1]

        dinfo = {
            "business_partener_id": doctor.business_partener.id if doctor.business_partener else None,
            "business_partener_user_name": doctor.business_partener.username if doctor.business_partener else None,
            "business_group_id": business_group_id,
            "business_group_name": business_group_name,
        }
        doctor_id_to_info[doctor.id] = dinfo

    coupon_sku_restricts = list(CouponSKURestrict.objects.filter(
        coupon_id__in=coupon_ids, sku_id__in=valid_sku_ids)
    )

    sku_id_to_coupon_infos = {}
    for ckr in coupon_sku_restricts:
        sku_id = ckr.sku_id
        if sku_id not in sku_id_to_coupon_infos:
            sku_id_to_coupon_infos[sku_id] = []

        coupon_id = ckr.coupon_id
        info = coupon_id_to_coupon_data.get(coupon_id, {})

        if info:
            doctor_id = info['doctor_id']
            prepay_threshold = info['prepay_threshold']
            has_threshold = info['has_threshold']

            coupon_info = {
                'doctor_id': doctor_id,
                'coupon_id': coupon_id,
                'threshold': prepay_threshold if has_threshold else 0,
                'value': info['value'],
                'left_number': gift_id_to_left_count.get(coupon_id_to_gift_id.get(coupon_id, 0), 0),
            }
            coupon_info.update(doctor_id_to_info.get(doctor_id, {}))

            sku_id_to_coupon_infos[sku_id].append(coupon_info)

    return sku_id_to_coupon_infos


def get_coupon_info_for_doctor_send_coupon(doctor_ids):
    all_gift_ids = []
    for doctor_id in doctor_ids:
        gift_ids, _, gift_id_to_left_count = get_coupon_gift_id_for_doctor_detail(None, doctor_id, count=65535)
        all_gift_ids.extend(gift_ids)

    result = _gift_id_to_doctor_send_coupon_info(all_gift_ids)
    return result


def _gift_id_to_doctor_send_coupon_info(gift_ids):
    gift_infos = list(GiftToCoupon.objects.filter(
        coupon_gift_id__in=gift_ids, coupon_enable=True,
        coupon_gift__gift_type=COUPON_GIFT_TYPES.DOCTOR
    ).values_list('coupon_gift_id', 'coupon_id'))

    gift_id_to_coupon_id = {}
    for gift_id, coupon_id in gift_infos:
        gift_id_to_coupon_id[gift_id] = coupon_id
    all_coupon_ids = [coupon_id for gift_id, coupon_id in gift_id_to_coupon_id.items()]

    coupon_id_to_obj = {coupon.id: coupon for coupon in Coupon.objects.filter(id__in=all_coupon_ids)}

    all_doctor_ids = {coupon.doctor_id for _, coupon in coupon_id_to_obj.items()}

    doctor_id_to_doctor_name = {d[0]: d[1] for d in
                                Doctor.objects.filter(id__in=all_doctor_ids).values_list('id', 'name')
                                }

    gift_id_to_gift_end_time = {gift.id: gift.end_time for gift in CouponGift.objects.filter(id__in=gift_ids)}

    result = []
    for gift_id in gift_ids:
        coupon_id = gift_id_to_coupon_id.get(gift_id, None)

        if coupon_id and coupon_id in coupon_id_to_obj:
            coupon = coupon_id_to_obj[coupon_id]

            end_time = gift_id_to_gift_end_time.get(gift_id, None)
            end_time_str = end_time.strftime('%Y-%m-%d') if end_time else ''

            doctor_name = doctor_id_to_doctor_name.get(coupon.doctor_id, '')

            # 7780添加
            coupon_threshold_desc, has_threshold_desc = "", ""
            if coupon.benefit_type == BENEFIT_TYPE.ZHIJIAN:
                coupon_threshold_desc = '无使用门槛'
                has_threshold_desc = '直减券'
            elif coupon.benefit_type == BENEFIT_TYPE.MANJIAN:
                coupon_threshold_desc = '满{}可用'.format(coupon.prepay_threshold)
                has_threshold_desc = '满减券'
            elif coupon.benefit_type == BENEFIT_TYPE.DISCOUNT:
                coupon_threshold_desc = '满{}-{}可用'.format(coupon.prepay_threshold, coupon.upper_limit)
                has_threshold_desc = '折扣券'

            # 7780以下
            # coupon_threshold_desc = '满{}可用'.format(coupon.prepay_threshold) if coupon.has_threshold else '无使用门槛'
            # has_threshold_desc = '满减券' if coupon.has_threshold else '直减券'

            doctor_coupon_use_desc = {
                DOCTOR_USE_COUPON_TYPE.PART_GENERAL: "部分美购可用",
                DOCTOR_USE_COUPON_TYPE.DOCTOR_GENERAL: "该医生全部美购适用",
                DOCTOR_USE_COUPON_TYPE.FULL_PLATFORM_GENERAL: "该机构全部美购适用",
            }[coupon.doctor_coupon_use_type]

            r = {
                'coupon_id': coupon.id,
                'coupon_name': coupon.name,
                'coupon_value': coupon.value,
                'coupon_threshold_desc': coupon_threshold_desc,
                'doctor_name': doctor_name,
                'has_threshold_desc': has_threshold_desc,
                'end_time': end_time_str,
                'get_coupon_data': {
                    'gift_id': gift_id,
                    'channel_id': settings.DOCTOR_BUSINESS_CHANNAL_ID,
                },
                'doctor_coupon_use_desc': doctor_coupon_use_desc,
                'doctor_coupon_use_type': coupon.doctor_coupon_use_type,
            }
            result.append(r)

    # 按价值倒排, 价值相同, id倒排
    result.sort(key=lambda x: (-x['coupon_value'], -x['coupon_id']))

    return result


def _get_user_gift_ids(gift_ids, user, now=None):
    """
    因为gift_ids是有序的, 保证输出的两个数组是按照之前顺序, 第三个返回参数是在所有channel中gift_id剩余可领取数的总和，只有大于0的才有
    如果gift不满足 start_time <= now <= end_time，那么这个gift_id将不会被返回
    :param user: 可以为None
    :return: 如果有user: [用户可领取ids..], [用户已经领过的ids...] , { gift_id: 666 }
             如果user为None: [可领取ids...], [], { gift_id: 666 }
    """
    if not now:
        now = datetime.datetime.now()

    channel_gift_infos = list(
        ChannelGift.objects.filter(
            activated_time__isnull=False, gift_enable=True, displayable=True,
            gift_id__in=gift_ids, gift__start_time__lte=now, gift__end_time__gte=now
        ).values_list('id', 'gift_id', 'total', 'limit')
    )

    channel_gift_ids = [x[0] for x in channel_gift_infos]
    channel_gift_to_count = {i.channel_gift_id: i.claimed_count for i in
                             ChannelGiftStatistics.objects.filter(channel_gift_id__in=channel_gift_ids)}

    gift_id_to_left_count = {}
    has_gift_left_channel_gift_infos = []
    for cg in channel_gift_infos:
        cg_id = cg[0]
        cg_gift_id = cg[1]
        cg_total = cg[2]
        cg_count = channel_gift_to_count.get(cg_id, 0)
        if cg_total > cg_count:
            left_count = cg_total - cg_count
            if cg_gift_id not in gift_id_to_left_count:
                gift_id_to_left_count[cg_gift_id] = 0
            gift_id_to_left_count[cg_gift_id] += left_count
            has_gift_left_channel_gift_infos.append(cg)

    can_get_gift_ids_set = set()
    already_get_gift_ids_set = set()
    if user:
        user_channel_gift_to_count = {i.get('channel_gift_id'): i.get('count') for i in
                                      ChannelGiftUserRelation.objects.filter(
                                          channel_gift__in=channel_gift_ids, user=user).values(
                                          'channel_gift_id').annotate(count=Count('id'))}
        for cg in channel_gift_infos:
            cg_id = cg[0]
            cg_gift_id = cg[1]
            cg_total = cg[2]
            cg_limit = cg[3]
            cg_count = user_channel_gift_to_count.get(cg_id, 0)
            if cg_limit > cg_count and cg_total > channel_gift_to_count.get(cg_id, 0):
                can_get_gift_ids_set.add(cg_gift_id)
            elif cg_count > 0:
                already_get_gift_ids_set.add(cg_gift_id)
    else:
        # 没有登录的话，显示所有的礼包，但是领取页面需要登录，客户端会在列表页要求用户登录
        for cg in has_gift_left_channel_gift_infos:
            cg_gift_id = cg[1]
            cg_limit = cg[3]
            cg_count = 0
            if cg_limit > cg_count:
                can_get_gift_ids_set.add(cg_gift_id)

    # 因为输入的gift_ids是有序的，因此只需要按照次序扫描然后分类id即可
    can_get_gift_ids = []
    already_get_gift_ids = []

    for gid in gift_ids:
        if gid in can_get_gift_ids_set:
            can_get_gift_ids.append(gid)
        if gid in already_get_gift_ids_set:
            already_get_gift_ids.append(gid)

    return can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count


def user_get_gift_by_displayab_page(gift_id, user):
    get_gift_success = False  # 成功领到礼包，没有成功的原因可能是因为被领完了。
    user_has_gift_left = False  # 用户还有没有礼包可以继续领

    if not gift_id:
        return get_gift_success, user_has_gift_left

    gift = CouponGift.objects.get(id=gift_id)
    gift.can_be_claimed()

    gift_info_data = gift.info_data
    coupon_number = gift_info_data.get('coupon_number', 0)

    if coupon_number == 0:
        return get_gift_success, user_has_gift_left

    channel_gift_ids = list(
        ChannelGift.objects.filter(activated_time__isnull=False, gift_enable=True, displayable=True,
                                   gift_id=gift_id).values_list('id', flat=True).order_by('activated_time')
    )

    for cg_id in channel_gift_ids:
        with transaction.atomic():
            cg = ChannelGift.objects.select_for_update().get(id=cg_id)
            if cg and cg.activated_time is not None and cg.gift_enable and cg.displayable and cg.gift_id == gift_id:
                cgur_count = ChannelGiftUserRelation.objects.filter(user=user, channel_gift=cg).count() or 0
                if cgur_count < cg.limit:
                    cgur_total = ChannelGiftUserRelation.objects.filter(channel_gift=cg, user_id__isnull=False).count()
                    if cgur_total < cg.total:
                        get_gift_success = True
                        user_has_gift_left = cgur_count + 1 < cg.limit

                        relation = ChannelGiftUserRelation.objects.create_without_code(user=user, channel_gift=cg)
                        create_coupon_info(cg, user, relation)

                        return get_gift_success, user_has_gift_left

    # 能到这里，就是一个都没有成功啦...
    return get_gift_success, user_has_gift_left


def user_get_gift_by_push_for_shopcart(users):
    # 1. 通过配置获取所有关联的ChannelGift Id。如果这个用户今天领过这些里面任意一个，直接认为领取成功？
    # 2. 关联的ChannelGift select_for_update他们的id
    # 检查还有哪些ChannelGift可用，可用的里面有哪些这个用户还可以领，还有

    # 用户领取结果 1. 成功领到gift_id，需要返回文案 2. 全局领完或者用户领完 3. 用户今天领过
    channel_id = settings.PUSH_FOR_SHOPCART_CHANNEL_ID
    gift_id_and_percent = settings.PUSH_FOR_SHOPCART_GIFT_ID_AND_PERCENT

    result = []

    if len(users) > 0:
        user_ids = [u.id for u in users]

        assert len(user_ids) == len(set(user_ids))

        user_id_to_obj = {u.id: u for u in users}

        gift_id_to_percent = {gp[0]: gp[1] for gp in gift_id_and_percent}
        all_gift_ids = [gp[0] for gp in gift_id_and_percent]

        gift_id_to_gm_url = {}

        gtc = list(GiftToCoupon.objects.select_related('coupon').filter(coupon_gift_id__in=all_gift_ids))
        for gc in gtc:
            if gc.coupon_gift_id not in gift_id_to_gm_url:
                coupon_text = ""
                coupon = gc.coupon
                cv = coupon.value
                benefit_type = coupon.benefit_type

                # 7780添加
                if benefit_type == BENEFIT_TYPE.ZHIJIAN:
                    coupon_text = u'全场通用券'
                elif benefit_type == BENEFIT_TYPE.MANJIAN:
                    coupon_text = u'满{}元使用'.format(coupon.prepay_threshold)
                elif benefit_type == BENEFIT_TYPE.DISCOUNT:
                    coupon_text = u'满{}-{}可用'.format(coupon.prepay_threshold, coupon.upper_limit)

                # 7780以下
                # coupon_text = u'满{}元使用'.format(coupon.prepay_threshold) if coupon.has_threshold else u'全场通用券'
                url = 'gengmei://shop_cart_list?coupon_value={}&coupon_desc={}'.format(cv, coupon_text)
                gift_id_to_gm_url[gc.coupon_gift_id] = url

        all_cg_ids = list(sorted(
            ChannelGift.objects.filter(
                business_channel_id=channel_id,
                gift_id__in=all_gift_ids,
                gift_enable=True).values_list('id', flat=True)))

        with transaction.atomic():
            all_cg = list(ChannelGift.objects.select_for_update().filter(id__in=all_cg_ids))
            cg_id_to_cg_obj = {cg.id: cg for cg in all_cg}

            start = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
            end = datetime.datetime.combine(datetime.date.today(), datetime.time.max)

            get_any_gift_user_id_set = set(ChannelGiftUserRelation.objects.filter(
                user_id__in=user_ids, channel_gift_id__in=all_cg_ids,
                created_time__gte=start, created_time__lte=end
            ).values_list('user_id', flat=True).distinct())

            cg_id_to_count = {i.get('channel_gift_id'): i.get('count') for i in
                              ChannelGiftUserRelation.objects.filter(
                                  channel_gift__in=all_cg_ids, user_id__isnull=False).values(
                                  'channel_gift_id').annotate(count=Count('id'))}

            cg_id_to_left_count = {}
            for cg in all_cg:
                if cg.total > 0:
                    left = cg.total - cg_id_to_count.get(cg.id, 0)
                    cg_id_to_left_count[cg.id] = left if left > 0 else 0

            for user_id in user_ids:
                user_already_get_gift = False
                gm_url = 'gengmei://shop_cart_list'
                get_gift_success = False

                if user_id not in get_any_gift_user_id_set:
                    target_cg_ids = [cg_id for cg_id, left_count in cg_id_to_left_count.items() if left_count > 0]
                    user_channel_gift_to_count = {i.get('channel_gift_id'): i.get('count') for i in
                                                  ChannelGiftUserRelation.objects.filter(
                                                      channel_gift__in=target_cg_ids, user_id=user_id).values(
                                                      'channel_gift_id').annotate(count=Count('id'))}

                    user_can_get_cg_ids = set()

                    for cg_id in target_cg_ids:
                        cg = cg_id_to_cg_obj[cg_id]
                        user_get_count = user_channel_gift_to_count.get(cg_id, 0)
                        if cg.limit > user_get_count:
                            user_can_get_cg_ids.add(cg_id)

                    cg_id_and_percent = []

                    for cg_id in user_can_get_cg_ids:
                        cg = cg_id_to_cg_obj[cg_id]
                        percent = gift_id_to_percent[cg.gift_id]
                        cg_id_and_percent.append((cg_id, percent))

                    user_get_cg_id = _get_random_gift(cg_id_and_percent)

                    if user_get_cg_id > 0:
                        cg = cg_id_to_cg_obj[user_get_cg_id]
                        user = user_id_to_obj[user_id]

                        relation = ChannelGiftUserRelation.objects.create_without_code(user=user, channel_gift=cg)
                        create_coupon_info(cg, user, relation)

                        cg_id_to_left_count[user_get_cg_id] -= 1

                        gm_url = gift_id_to_gm_url.get(cg.gift_id, 'gengmei://shop_cart_list')
                        get_gift_success = True

                else:
                    # 用户今天领过券了
                    user_already_get_gift = True

                user_result = {
                    "user_id": user_id,
                    "gm_url": gm_url,
                    "success": get_gift_success,
                    "already_get_gift": user_already_get_gift,
                }

                result.append(user_result)

    return result


def _get_random_gift(cg_id_and_percent):
    user_get_cg_id = None

    p_start_end_cgid = []
    index = 0
    total = 0
    for cg_id, percent in cg_id_and_percent:
        p_start_end_cgid.append((index, index + percent, cg_id))
        index += percent
        total += percent

    if total > 0:
        random_int = random.randint(0, total - 1)
        for start, end, cgid in p_start_end_cgid:
            if start <= random_int < end:
                return cgid

    return user_get_cg_id


def can_qq_coupon_verificate(user_id, business_channel_id, gift_id):
    channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=business_channel_id)
    relation = ChannelGiftUserRelation.objects.get(user_id=user_id, channel_gift=channel_gift)
    exist_unconsumed = CouponInfo.objects.filter(relation=relation).exclude(status=COUPON_STATUS.CONSUMED).exists()
    if exist_unconsumed:
        return False
    else:
        return True


def _gen_start_time(start_time):
    """
        开始时间
    """
    up_time = datetime.datetime.fromtimestamp(start_time)
    s_datetime = datetime.datetime(up_time.year, up_time.month, up_time.day)
    return s_datetime


def _gen_end_time(end_time):
    """
        结束时间，取当天最后的1s
        若传值时间是 2017-03-18，则保存的时间为 2017-03-18 23:59:59
    """
    up_time = datetime.datetime.fromtimestamp(end_time)
    e_datetime = datetime.datetime(up_time.year, up_time.month, up_time.day) + datetime.timedelta(days=1, seconds=-1)
    return e_datetime


def gen_doctor_coupon_info(coupon_info):
    coupon_dict = {
        'name': coupon_info.get('name'),
        'value': coupon_info.get('value'),
        'is_doctor_new': coupon_info.get('is_doctor_new', False),
        'has_threshold': coupon_info.get('has_threshold', False),
        'prepay_threshold': coupon_info.get('prepay_threshold', 0),
        'time_type': coupon_info.get('time_type'),
        'countdown': coupon_info.get('countdown', 0) if coupon_info.get(
            'time_type') == COUPON_TIME_TYPE.COUNTDOWN else 0,
        'start_time': _gen_start_time(coupon_info.get('use_start_time')) if coupon_info.get(
            'time_type') == COUPON_TIME_TYPE.START_END else None,
        'end_time': _gen_end_time(coupon_info.get('use_end_time')) if coupon_info.get(
            'time_type') == COUPON_TIME_TYPE.START_END else None,
        'distribution_status': coupon_info.get('distribution_status'),
        'announcement': coupon_info.get('announcement'),
    }
    return coupon_dict


def gen_doctor_gift_info(coupon_info):
    gift_info = {
        'name': coupon_info.get('name'),
        'start_time': _gen_start_time(coupon_info.get('get_start_time')),
        'end_time': _gen_end_time(coupon_info.get('get_end_time')),
    }
    return gift_info


def gen_doctor_channelgift(coupon_info):
    channel_info = {
        'total': coupon_info.get('total'),
        'limit': coupon_info.get('limit'),
        'gift_enable': False,
        'displayable': False,
    }
    return channel_info


def skus_for_coupon(sku_ids):
    skus_info = Service.get_default_price_info_by_service_item_ids(sku_ids)
    service_items = ServiceItem.objects.filter(id__in=sku_ids).annotate(service_name=F('service__name'))
    for service_item in service_items:
        sid = service_item.id
        if not skus_info[sid]:
            continue
        skus_info[sid]['service_name'] = service_item.service_name
        skus_info[sid]['service_id'] = service_item.service_id
        skus_info[sid]['name'] = ''.join(service_item.items_name)
        skus_info[sid]['service_type'] = service_item.service.service_type
        if service_item.service.service_type == 3:
            skus_info[sid]['city_name'] = service_item.city.name
    skus = []
    for sku_id in sorted(sku_ids):
        if sku_id in skus_info:
            sku_price_info = skus_info.get(sku_id)
            if not sku_price_info:
                continue
            data = {
                'service_id': sku_price_info.get('service_id'),
                'id': sku_id,
                'service_name': sku_price_info.get('service_name'),
                'name': sku_price_info.get('name'),
                'gengmei_price': sku_price_info.get('gengmei_price'),
                'pre_payment_price': sku_price_info.get('pre_payment_price'),
                'hospital_payment': sku_price_info.get('gengmei_price') - sku_price_info.get('pre_payment_price'),
                'service_type': sku_price_info.get('service_type'),
            }
            if sku_price_info.get('service_type') == 3:
                data.update(city_name=sku_price_info.get('city_name'))
            skus.append(data)
    return skus


def check_can_get_channel_gift_ids(channel_gift_ids, user_id):
    """
    支持user为None的情况, 返回 can_get_channel_gift_ids_set, already_get_channel_gift_ids_set
    """
    channel_gift_infos = list(
        ChannelGift.objects.filter(activated_time__isnull=False, gift_enable=True,
                                   id__in=channel_gift_ids).values_list('id', 'gift_id', 'total', 'limit')
    )
    channel_gift_ids = [x[0] for x in channel_gift_infos]
    channel_gift_to_count = {i.get('channel_gift_id'): i.get('count') for i in
                             ChannelGiftUserRelation.objects.filter(channel_gift__in=channel_gift_ids).values(
                                 'channel_gift_id').annotate(count=Count('id'))}

    channel_gift_id_to_left_count = {}
    has_gift_left_channel_gift_infos = []
    for cg in channel_gift_infos:
        cg_id = cg[0]
        cg_gift_id = cg[1]
        cg_total = cg[2]
        cg_count = channel_gift_to_count.get(cg_id, 0)
        if cg_total > cg_count:
            left_count = cg_total - cg_count
            if cg_id not in channel_gift_id_to_left_count:
                channel_gift_id_to_left_count[cg_id] = 0
            channel_gift_id_to_left_count[cg_id] += left_count
            has_gift_left_channel_gift_infos.append(cg)

    has_gift_left_channel_gift_ids = set(x[0] for x in has_gift_left_channel_gift_infos)

    can_get_channel_gift_ids_set = set()
    already_get_channel_gift_ids_set = set()

    if user_id:
        user_channel_gift_to_count = {i.get('channel_gift_id'): i.get('count') for i in
                                      ChannelGiftUserRelation.objects.filter(
                                          channel_gift__in=has_gift_left_channel_gift_ids, user_id=user_id).values(
                                          'channel_gift_id').annotate(count=Count('id'))}
        for cg_id, cg_gift_id, cg_total, cg_limit in has_gift_left_channel_gift_infos:
            cg_count = user_channel_gift_to_count.get(cg_id, 0)
            if cg_limit > cg_count:
                can_get_channel_gift_ids_set.add(cg_id)
            else:
                already_get_channel_gift_ids_set.add(cg_id)
    else:
        for cg_id, cg_gift_id, cg_total, cg_limit in has_gift_left_channel_gift_infos:
            cg_count = 0
            if cg_limit > cg_count:
                can_get_channel_gift_ids_set.add(cg_id)

    return can_get_channel_gift_ids_set, already_get_channel_gift_ids_set


def _try_get_gift(channel_gift, user):
    """
        根据channel_gift领取礼包
        如果领取失败就返回(False, CODES) 其中CODES为from rpc.tool.error_code import CODES
        成功返回(True, None)
    """
    if channel_gift.limit and channel_gift.limit <= ChannelGiftUserRelation.objects.filter(user=user,
                                                                                           channel_gift=channel_gift).count():
        return False, CODES.COUPON_EXCEED_LIMIT
    if ChannelGiftUserRelation.objects.filter(channel_gift=channel_gift,
                                              user_id__isnull=False).count() >= channel_gift.total:
        return False, CODES.COUPON_IS_EMPTY
    with transaction.atomic():
        relation = ChannelGiftUserRelation.objects.create_without_code(user=user, channel_gift=channel_gift)
        create_coupon_info(channel_gift, user, relation)

    return True, None
