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

import json
from random import choice
from datetime import datetime, timedelta

from django.conf import settings
from django.db import transaction
from django.db.models import Q
from gm_types.gaia import COUPON_TYPES, COUPON_STATUS, DOCTOR_TYPE, COUPON_GIFT_TYPES
from gm_types.gaia import BENEFIT_TYPE, COUPON_USE_TYPE, COUPON_INVALID
from gm_types.trade import COUPON_TIME_TYPE

import point
from api.manager import coupon_manager
from api.manager.user_manager import user_portraits
from api.models import ChannelGift
from api.models import ChannelGiftUserRelation
from api.models import CouponDistribute, CouponChannel
from api.models import CouponGift, CouponDoctorRestrict
from api.models import CouponInfo, Coupon
from api.models import LOGIN_AUTH_TYPE
from api.models import Order, Doctor
from api.models import POINTS_TYPE
from api.models import Person
from api.models import QQCouponRecord
from api.models import User
from api.models import UserExtra
from api.tool.datetime_tool import get_timestamp_or_none
from api.tool.log_tool import logging_exception
from api.tool.notification_tool import send_notification
from api.tool.service_tool import get_serviceitem_by_option_id
from api.tool.user_tool import get_user_extra_by_user_id
from api.tool.user_tool import get_user_from_context, user_register_log_data
from api.tool.user_tool import is_merchant_user
from api.tool.coupon_tool import create_coupon_info, get_coupon_gift_info_for_service_detail, \
    get_coupon_gift_id_for_doctor_detail, user_get_gift_by_displayab_page, user_get_gift_by_push_for_shopcart
from api.tool.coupon_tool import _try_get_gift, filter_available_gifts
from api.manager.coupon_manager import get_coupon_gift_info_for_service_detail_with_type
from api.manager.user_manager import user_portraits
from api.models.coupon import HAS_BOUGHT
from pay.manager import settlement_manager

from rpc.cache import coupon_badge_cache
from rpc.decorators import bind
from rpc.decorators import bind_context
from rpc.decorators import list_interface
from rpc.decorators import cache_page
from rpc.tool.error_code import gen, CODES
from rpc.tool.random_tool import random_str
from rpc.tool.time_tool import get_current_time
from utils.qq_connect_verify import QQConnect

from gm_tracer import func_span, traced_function

REASON_MAP = {
        COUPON_INVALID.NEW_USER: u'该券仅限未下过单新用户可用',
        COUPON_INVALID.DOCTOR: u'该美购不在美券可用医生美购范围',
        COUPON_INVALID.DOCTOR_NEW: u'该美券限制医生新人可用',
        COUPON_INVALID.SECKILL: u'该美券不可用于秒杀美购',
        COUPON_INVALID.SPECIAL: u'该美购不在美券可用专题美购范围',
        COUPON_INVALID.THRESHOLD: u'下单预付款金额小于美券抵扣金额',
        COUPON_INVALID.REPAY: u'预付款金额小于美券预付款门槛金额',
        COUPON_INVALID.DISCOUNT:  u'该商品特殊性质导致不可使用',
        COUPON_INVALID.SKU: u'该美券仅限制部分美购使用',
        COUPON_INVALID.TIME: u'该美券已过期',
        COUPON_INVALID.STATUS: u'该美券未被领取',
        COUPON_INVALID.HOSPITAL_PAYMENT: u'尾款金额小于美券尾款门槛金额',
        COUPON_INVALID.GROUPBUY: u'该美券不可用于拼团美购',
        COUPON_INVALID.PRICE_RANGE: u'预付款金额不在美券可用金额范围',
        COUPON_INVALID.PRECE_NOT_IN_SPECIAL: u'该美购仅限制价格专场使用',
        COUPON_INVALID.PROMOTION: u'该美购不在美券可用活动美购范围',
    }


def _get_doctor_names_by_coupon_info_ids(coupon_info_ids):
    ciid2cid = {ci[0]: ci[1] for ci in CouponInfo.objects.filter(id__in=coupon_info_ids).values_list('id', 'coupon_id')}
    coupon_ids = set(ciid2cid.values())

    cid2dname = CouponDoctorRestrict.objects.filter(coupon_id__in=coupon_ids).values_list('id', 'coupon_id',
                                                                                          'doctor__name')

    coupon_id2doctor_name = {}

    for id, cid, dname in cid2dname:
        if cid not in coupon_id2doctor_name:
            coupon_id2doctor_name[cid] = dname

    coupon_info_ids_to_docotor_name = {
        ciid: coupon_id2doctor_name[ciid2cid[ciid]] if ciid in ciid2cid and ciid2cid[
                                                                                ciid] in coupon_id2doctor_name else ''
        for ciid in
        coupon_info_ids}

    return coupon_info_ids_to_docotor_name


@bind_context('coupon/info', login_required=True)
@bind_context('api/coupon/info', login_required=True)
@list_interface(
    offset_name='start', limit_name='count',
    element_model=CouponInfo, element_func_list=[CouponInfo.coupon_info_data],
)
def coupon_coupon_info(ctx, count=10, start=0, only_show_platform_coupon=True):
    results = []
    user = get_user_from_context(ctx)

    if user is None:
        return results

    coupon_infos = CouponInfo.objects.filter(user=user)

    if only_show_platform_coupon:
        # 对于旧版本请求，只返回平台券
        coupon_infos = coupon_infos.filter(coupon__coupon_type=COUPON_TYPES.PLATFORM)
    # 旧版本会返回所有状态的券（包含已经使用），新版本不显示已使用美券
    coupon_status = [COUPON_STATUS.CLAIMED, COUPON_STATUS.FROZEN]
    coupon_infos = coupon_infos.filter(status__in=coupon_status)

    # 去掉已过期的
    now = datetime.now()
    coupon_infos = coupon_infos.exclude(end_time__lte=now)

    coupon_infos = coupon_infos.order_by('-claimed_time')
    for coupon_info in coupon_infos[start:start + count]:
        results.append(coupon_info.coupon_info_data())

    return results


@bind_context('coupon/available', login_required=True)
@bind_context('api/coupon/available', login_required=True)
@list_interface(offset_name='start', limit_name='count', element_model=CouponInfo)
def coupon_available(ctx, service_id=None, itemkey=None, count=10, start=0, cart_item_ids=None,
                     cart_item_info=None, service_item_id=None, number=1,
                     only_show_platform_coupon=True, coupon_type=0,
                     groupbuy_code=None, create_groupbuy_price_id=None,
                     device_id=None, coupon_use_type=COUPON_USE_TYPE.VALID_COUPON,
                     promotion_item_relation=None,
                     ):
    result = {
        "is_can_discount": True,  # 是否可用抵扣
        "discount_desc": "",  # 抵扣描述
    }
    user = get_user_from_context(ctx)
    #兼容pc m站
    if is_merchant_user(user) and (int(coupon_type) == COUPON_TYPES.PLATFORM or int(coupon_type) == 0):
        result.update({
            "is_can_discount": False,  # 是否可用抵扣
            "discount_desc": "机构账号不可以使用美券哦～",  # 抵扣描述
        })

    groupbuy_team_id, groupbuy_price_id = settlement_manager.try_get_groupbuy_team_id_and_groupbuy_price_id(
        user.id, groupbuy_code, create_groupbuy_price_id
    )
    service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(
        cart_item_info,
        service_item_id,
        number, user, groupbuy_price_id, promotion_item_relation=promotion_item_relation
    )

    promotion_ids = []
    if promotion_item_relation:
        promotion_ids = [obj['promotion_id'] for item in promotion_item_relation.values() for obj in item]

    reason_to_coupons = {}
    if coupon_use_type == COUPON_USE_TYPE.VALID_COUPON:
        coupons = CouponInfo.valid_coupons(
            user, service_item_id_to_ordering_info, only_show_platform_coupon,
            int(coupon_type), device_id=device_id, coupon_use_type=coupon_use_type,
            promotion_ids=promotion_ids,
        )
    else:
        coupons = []
        unvalid_coupons = CouponInfo.valid_coupons(
            user, service_item_id_to_ordering_info, only_show_platform_coupon,
            int(coupon_type), device_id=device_id, coupon_use_type=coupon_use_type,
            promotion_ids=promotion_ids,
        )
        for cp, reason in unvalid_coupons:
            if not reason:
                continue
            coupons.append(cp)
            reason_to_coupons[cp.coupon_id] = reason

    result['count'] = len(coupons)
    ci_dtos = to_coupon_info_dto(coupons)
    pre_payment_price = CouponInfo._sum_price(service_item_id_to_ordering_info.values(), 'pre_payment_price')
    for ci_dto in ci_dtos:
        if ci_dto['benefit_type'] == BENEFIT_TYPE.DISCOUNT:
            ci_dto['value'] = pre_payment_price - int(pre_payment_price * ci_dto['discount_rate'] // 100)
    sorted_coupon_info_dtos = CouponInfo.sort_coupons(ci_dtos)

    coupons_info_dtos = [x for x in sorted_coupon_info_dtos[start:start + count]]

    for coupons in coupons_info_dtos:
        reason_code = reason_to_coupons.get(coupons['coupon_id'], '')
        if reason_code in REASON_MAP:
            if int(coupon_type) == COUPON_TYPES.PLATFORM:
                coupons['reason'] = REASON_MAP[reason_code]
            else:
                coupons['reason'] = REASON_MAP[reason_code].replace('预付款', '尾款')
        else:
            coupons['reason'] = ''

    result['coupons'] = coupons_info_dtos
    return result


@bind_context('api/coupon/use_coupon_price')
def coupon_available(ctx, service_item_id=None, number=1):
    no_data_result = {
        'has_use_coupon_price': False,
        'use_coupon_price': None,
    }

    user = get_user_from_context(ctx)

    if user is None or service_item_id is None:
        return no_data_result

    groupbuy_price_id = None  # 券后价不被使用了，这里传空参数保证不炸就好了...
    service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(
        {}, service_item_id, number, user, groupbuy_price_id
    )

    if len(service_item_id_to_ordering_info.items()) != 1:
        return no_data_result

    sum_coupon_value = 0

    for ct in (COUPON_TYPES.PLATFORM, COUPON_TYPES.DOCTOR):
        coupons = CouponInfo.valid_coupons(
            user, service_item_id_to_ordering_info, False, int(ct)
        )
        if coupons:
            ci_dtos = to_coupon_info_dto(coupons)
            coupon_value = CouponInfo.sort_coupons(ci_dtos)[0]['value']
            if coupon_value > 0:
                sum_coupon_value += coupon_value

    if sum_coupon_value > 0:
        gengmei_price = None
        for k, v in service_item_id_to_ordering_info.items():
            price_info = v.ordering_price_info
            gengmei_price = price_info["gengmei_price"]

        use_coupon_price = gengmei_price - sum_coupon_value
        return {
            'has_use_coupon_price': True,
            'use_coupon_price': use_coupon_price,
        }
    else:
        return no_data_result


@bind_context('api/coupon/claim_phone')
def coupon_claim_phone(ctx, phone, gift_id, business_channel_id, allow_get_gift_fail=False):
    user = None

    # try get user from person by person.phone
    try:
        person = Person.objects.get(phone=phone)
        user = person.user
    except Person.DoesNotExist:
        pass

    # if not found, try to get user from user.username by phone
    if not user:
        try:
            user = User.objects.get(username=phone)
        except User.DoesNotExist:
            pass

    # if all failed, create person/user
    if not user:
        try:
            with transaction.atomic():
                pwd = random_str(10)
                user = UserExtra.create_user(phone, "", pwd, LOGIN_AUTH_TYPE.COUPON)
                user_extra = get_user_extra_by_user_id(user.id, phone)
                user_extra.portrait = choice(user_portraits)
                user.last_name = u'更美用户' + user_extra.referral_code
                user.save()

                person = Person.get_or_create(user)
                person.save()

                ctx.logger.app(**user_register_log_data(user_id=user.id))

                # 用户注册加美分
                point.add(user_id=user.id, reason=POINTS_TYPE.REGISTER)
                # 发送所长欢迎私信
                send_notification(
                        uid=user.id,
                        title=u'所长欢迎你',
                        content=settings.WELCOME_CONTENT,
                        url='gengmei://message_chat?user_key=22_{}'.format(user.id)
                )
        except:
            logging_exception()
            return gen(CODES.COUPON_CLAIM_FAIL)

    # here, we say user is good to go!
    channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=business_channel_id)
    success, codes = _try_get_gift(channel_gift, user)

    if allow_get_gift_fail:
        get_gift_code = 0 if success else codes
        gift_info = channel_gift.gift.info_data
        return {
            'get_gift_code': get_gift_code,
            'gift_info': gift_info,
        }
    else:
        if not success:
            raise gen(codes)

        return channel_gift.gift.info_data


@bind_context('api/coupon/claim_code', login_required=True)
def coupon_redeem(ctx, code):
    user = get_user_from_context(ctx)
    try:
        relation = ChannelGiftUserRelation.objects.get(code=code)
    except ChannelGiftUserRelation.DoesNotExist:
        raise gen(CODES.COUPON_NOT_FOUND)
    channel_gift = relation.channel_gift
    if channel_gift.limit and channel_gift.limit <= ChannelGiftUserRelation.objects.filter(user=user, channel_gift=channel_gift).count():
        raise gen(CODES.COUPON_EXCEED_LIMIT)
    if ChannelGiftUserRelation.objects.filter(channel_gift=channel_gift, user_id__isnull=False).count() >= channel_gift.total:
        raise gen(CODES.COUPON_IS_EMPTY)

    with transaction.atomic():
        relation.channel_gift.gift.can_be_claimed()
        relation.claim(user=user)
        create_coupon_info(channel_gift=relation.channel_gift, user=user, relation=relation)

    return relation.channel_gift.gift.info_data


@bind('coupon/launch_info/get')
@bind('api/coupon/launch_get')
def coupon_launch_info_get(coupon_launch_channel):
    coupon_gift = CouponGift.objects.get(channel=coupon_launch_channel)
    return coupon_gift.info_data


@bind('api/coupon/channel_coupons')
@list_interface(offset_name='start', limit_name='count')
def channel_coupon_list(channel, count=10, start=0):
    coupons = Coupon.objects.filter(couponlaunch__channel=channel).order_by('-end_time')
    data = [coupon.data() for coupon in coupons[start: start + count]]
    return {'count': count, 'coupons': data}


def _create_invite(user, business_channel_id):
    channel_gifts = ChannelGift.objects.filter(business_channel_id=business_channel_id).filter(activated_time__isnull=False)
    channel_gift = channel_gifts.last()
    if business_channel_id == 13:
        limit = settings.INVITED_COUPON_LIMIT
    elif business_channel_id == 12:
        limit = settings.INVITE_COUPON_LIMIT
    else:
        raise gen(CODES.LIMITED)

    if ChannelGiftUserRelation.objects.filter(user=user).filter(
            channel_gift__in=channel_gifts).count() > limit:
        if business_channel_id == 12:
            return
        else:
            return gen(CODES.COUPON_EXCEED_LIMIT)

    with transaction.atomic():
        relation = ChannelGiftUserRelation.objects.create_without_code(user, channel_gift=channel_gift)
        create_coupon_info(channel_gift=channel_gift, user=user, relation=relation)
    return relation.channel_gift.gift.info_data


@bind("api/coupon/invite")
def coupon_invite(invite_user_id, invited_user_id):
    """
    当前登录用户可能不是要塞券用户, 通过uid塞
    """
    INVITE_CHANNEL_ID = 12
    INVITED_CHANNEL_ID = 13

    try:
        invite_user = User.objects.get(pk=invite_user_id)
        invited_user = User.objects.get(pk=invited_user_id)
    except User.DoesNotExist:
        return gen(CODES.USER_NOT_FOUND)

    invite_info = _create_invite(invite_user, INVITE_CHANNEL_ID)
    invited_info = _create_invite(invited_user, INVITED_CHANNEL_ID)

    return invite_info


@bind_context('api/coupon/get_fengxianggou_coupongift', login_required=True)
def coupongift_for_fengxianggou(ctx):
    user = get_user_from_context(ctx)

    new_user_gift_id = settings.FENGXIANGGOU_COUPONGIFT_NEW_USER_GIFT_ID
    new_user_channel_id = settings.FENGXIANGGOU_COUPONGIFT_NEW_USER_CHANNEL_ID

    old_user_gift_id = settings.FENGXIANGGOU_COUPONGIFT_OLD_USER_GIFT_ID
    old_user_channel_id = settings.FENGXIANGGOU_COUPONGIFT_OLD_USER_CHANNEL_ID

    is_new_user = Order.objects.filter(user=user, status__in=HAS_BOUGHT).exists() is False

    gift_id = new_user_gift_id if is_new_user else old_user_gift_id
    channel_id = new_user_channel_id if is_new_user else old_user_channel_id

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

    channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=channel_id)
    success, codes = _try_get_gift(channel_gift, user)

    gift_info = to_show_gift_dict(gift)

    return {
        'success': success,
        'gift_info': gift_info,
        'codes': codes,
    }


@bind_context('api/coupon/user_coupongift_is_claimed', login_required=True)
def user_coupongift_is_claimed(ctx, channel_id_and_gift_id_list):
    user = get_user_from_context(ctx)

    result = {}
    for channel_id, gift_id in channel_id_and_gift_id_list:
        key = str(channel_id) + u"_" + str(gift_id)
        if key not in result:
            try:
                channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=channel_id)
                channel_gift_count = ChannelGiftUserRelation.objects.filter(
                    user=user, channel_gift=channel_gift).count()
                result[key] = channel_gift_count > 0
            except ChannelGift.DoesNotExist:
                gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)

    return result


@bind('api/coupongift/info_for_invite_channel')
def coupongift_for_invite_channel():
    INVITE_CHANNEL_ID = 12
    q = Q(business_channel_id=INVITE_CHANNEL_ID) & Q(activated_time__isnull=False)
    channel_gifts = ChannelGift.objects.filter(q)
    channel_gift = channel_gifts.last()
    return channel_gift.gift.info_data


@bind_context("api/coupon/claimed_channel_gift_count")
def claimed_channel_gift_count(ctx, business_channel_id, gift_id=None):
    query = Q(business_channel_id=business_channel_id)
    if gift_id:
        query &= Q(gift_id=gift_id)

    channel_gifts = ChannelGift.objects.filter(query)
    user = get_user_from_context(ctx)
    count = ChannelGiftUserRelation.objects.filter(user=user).filter(
            channel_gift__in=channel_gifts
    ).count()

    return {
        'count': count
    }


# @bind_context('api/coupon/coupon_info')
# def get_coupon_info(ctx, coupon_id=None):
#     """
#     NOTE:
#         Desc: 根据coupon_id 获取红包信息，同时获取该用户时候领取了改红包的信息
#         :param ctx:
#         :param coupon_id:
#         :return:
#         change log: v5.6.0
#     """
#     user = get_user_from_context(ctx)
#     try:
#         coupou = Coupon.objects.get(pk=coupon_id)
#         coupou = coupou.data()
#     except Coupon.DoesNotExist:
#         return {}
#
#     try:
#         query = Q(user=user) & Q(coupon_id=coupon_id)
#         coupon_info = CouponInfo.objects.get(query)
#         coupou['is_receive'] = True
#     except:
#         coupou['is_receive'] = False
#
#     return coupou
#
#
# @bind('api/coupon/gift_coupons')
# def get_coupon_by_gift(channel):
#     try:
#         gift = CouponGift.objects.get(channel=channel)
#     except CouponGift.DoesNotExist:
#         return gen(CODES.COUPON_GIFT_NOT_FOUND)
#
#     gift_to_coupons = GiftToCoupon.objects.filter(coupon_gift_id=gift.id)
#     coupons = []
#     for o in gift_to_coupons:
#         number = 0
#         while number < int(o.number):
#             coupon = Coupon.objects.get(id=o.coupon_id)
#             coupons.append(coupon.data())
#             number += 1
#
#     return coupons


def is_qualified_for(rule, **kwargs):
    """
    红包规则
    :param rule:
    :param kwargs:
    :return:
    """
    def register_coupongift_rule(user=None):
        """
        美购首页悬浮红包资格
        :param user:
        :return:
        """
        return not Order.objects.filter(user=user, status__in=HAS_BOUGHT).exists()

    _map = {
        'REGISTER_COUPONGIFT': register_coupongift_rule
    }
    return _map.get(rule) and _map[rule](**kwargs)


@bind_context("api/coupongift/create", login_required=True)
def create_couponinfo(ctx, gift_id, business_channel_id, rule=None):
    # claim coupongift
    user = get_user_from_context(ctx)

    # 检查用户是否有资格领取该红包
    # 具体实现请查看:is_qualified.
    if rule and not is_qualified_for(rule, user=user):
            raise gen(CODES.NOT_QUALIFIED_FOR_COUPONGIFT)

    channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=business_channel_id)
    success, codes = _try_get_gift(channel_gift, user)
    if not success:
        raise gen(codes)

    data = channel_gift.gift.info_data
    # 是否可以继续领取该红包
    data.update({
        'claimable': ChannelGiftUserRelation.objects.filter(user=user,
                                                            channel_gift=channel_gift).count() < channel_gift.limit
    })
    return data


@bind('api/coupongift/get')
def get_coupongift_info(gift_id, channel_id):
    q = Q(gift_id=gift_id) & Q(business_channel_id=channel_id)

    try:
        cg = ChannelGift.objects.get(q)
    except ChannelGift.DoesNotExist:
        gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)

    return cg.gift.info_data


@bind_context('api/coupongift/check', login_required=True)
def check_coupongift(ctx, gift_id, channel_id):
    user = get_user_from_context(ctx)
    q = Q(gift_id=gift_id) & Q(business_channel_id=channel_id)

    channel_gift = ChannelGift.objects.get(q)

    data = channel_gift.gift.info_data
    # 是否可以继续领取该红包
    data.update({
        'claimable': ChannelGiftUserRelation.objects.filter(user=user,
                                                            channel_gift=channel_gift).count() < channel_gift.limit
    })
    return data


@bind_context('api/coupongift/list_info')
def get_coupongift_list_info(ctx, ids, channel_id):
    # ids is a list of int
    # check if user has logged in
    user = get_user_from_context(ctx)

    q = Q(gift_id__in=ids) & Q(business_channel_id=channel_id)
    channel_gifts = ChannelGift.objects.filter(q)
    if not channel_gifts:
        return {'gifts': []}

    _gifts = []
    for i in ids:
        cgs = filter(lambda x: x.gift_id == int(i), channel_gifts)
        if not cgs:
            continue
        cg = cgs[0]
        data = cg.gift.info_data
        data.update({'limit': cg.limit})
        if not user:
            # claimable:  是否可继续领取
            # claimed：是否已领取
            data['claimable'] = True
            data['claimed'] = False
        else:
            data['claimable'] = ChannelGiftUserRelation.objects.filter(user=user, channel_gift=cg).count() < cg.limit
            data['claimed'] = ChannelGiftUserRelation.objects.filter(user=user, channel_gift=cg).exists()

        _gifts.append(data)

    return {
        'gifts': _gifts
    }


@bind("api/coupongift/land_page")
def get_coupongift_land_page(distribute_id=None):
    try:
        land_coupon = CouponDistribute.objects.get(pk=distribute_id)
    except CouponDistribute.DoesNotExist:
        raise gen(CODES.COUPON_DOES_NOT_EXIST)

    share_data = {'title': land_coupon.title, 'share_image': land_coupon.share_image, 'wechat': land_coupon.wechat,
                   'qq': land_coupon.qq, 'weibo': land_coupon.weibo}
    return {
        'title': land_coupon.name,
        'background': land_coupon.background,
        'gift_id': land_coupon.gift.gift.id,
        'gift_img': land_coupon.gift_img,
        'enable': land_coupon.enable,
        'channel_id': land_coupon.gift.business_channel.id,
        'share_data': share_data
    }


@bind_context("api/coupongift/qqcoupon")
def get_qqcoupon_gift(ctx, phone, authorization_code,
                      code, card_id, signature,
                      attach, redirect_uri, gift_info):
    """
    phone                  手机号
    authorization_code     鉴权code
    code                   卡券code
    card_id                卡券类型id
    signature              url中附带的签名
    attach                 url中附加参数字段
    redirect_uri           现为m站QQ卡券地址
    gift_info:             礼包信息 以'gift_id', 'channel'组成的json
    """

    redirect_uri_list = redirect_uri.split('?')
    if (
            not redirect_uri_list or
            not redirect_uri_list[0].startswith('http')
    ):
        return gen(CODES.QQ_CARD_VERIFY_FAILED)

    redirect_uri = redirect_uri_list[0]

    record = QQCouponRecord.objects.filter(card_code=code)
    if record:
        return gen(CODES.QQ_CARD_GAINED)

    init_kwargs = {
        'code': authorization_code,
        'card_id': card_id,
        'signature': signature,
        'attach': attach,
        'redirect_uri': redirect_uri,
    }

    connect = QQConnect(**init_kwargs)
    openid, access_token = connect.get_openid_access_token()

    if not openid or not access_token:
        return gen(CODES.QQ_CARD_VERIFY_FAILED)

    try:
        get_gift = ctx.gaia_local['api/coupon/claim_phone'](
            phone=phone, gift_id=gift_info['gift_id'],
            business_channel_id=gift_info['channel']
        ).unwrap()
    except:
        logging_exception()
        return gen(CODES.COUPON_CLAIM_FAIL)

    # QQ卡券修改已领取状态

    # 记录领取状态
    try:
        _ = QQCouponRecord.objects.get_or_create(
            card_id=card_id, card_code=code, openid=openid,
            authorization_code=authorization_code,
            access_token=access_token, attach=attach, phone=phone,
            gift_channel=gift_info['channel'],
            gift=gift_info['gift_id']
        )
    except:
        # 已有多条记录的异常
        logging_exception()
        return gen(CODES.QQ_CARD_GAINED)

    return get_gift


def to_show_gift_dict(gift):
    coupon_and_number = gift.get_coupon_and_number_list()
    number = sum([gt[1] for gt in coupon_and_number])
    # 这里的total_value，针对原有的预付款和尾款抵扣金额都直接相加
    # 如果需要增加不能理解为金额的类型的话...大量显示券和礼包的地方都要大改
    total_value = sum([gt[0].value * gt[1] for gt in coupon_and_number]) if number > 0 else 0

    show_coupon_list = [{'info': to_show_coupon_dict(coupon[0]), 'count': coupon[1]} for coupon in coupon_and_number]

    return {
        # 礼包id
        'id': gift.id,
        # 礼包名称
        'name': gift.name,
        # 礼包中所有美券（一种美券可能有多张）的总抵扣价值
        'value': total_value,
        # 礼包中美券总数（一种美券可能有多张）
        'coupon_number': number,
        # 礼包开始领取时间
        'start_time': get_timestamp_or_none(gift.start_time),
        # 礼包结束领取时间
        'end_time': get_timestamp_or_none(gift.end_time),
        # 礼包中美券用于显示的领取信息
        'coupons': show_coupon_list,
        # 礼包类型
        'gift_type': gift.gift_type
    }


def to_show_coupon_dict(coupon_obj):
    from gm_types.trade import COUPON_TIME_TYPE

    if coupon_obj.coupon_type == COUPON_TIME_TYPE.START_END:
        start_time = coupon_obj.start_time.strftime('%Y-%m-%d')
        end_time = coupon_obj.end_time.strftime('%Y-%m-%d')
        time_text = '有效期 {}-{}'.format(start_time, end_time)
    else:
        start_time = None
        end_time = None
        time_text = '领取后{}天内使用'.format(coupon_obj.countdown)

    threshold_text = ""
    benefit_type = coupon_obj.benefit_type
    if benefit_type == BENEFIT_TYPE.ZHIJIAN:
        threshold_text = u'无门槛美券'
    elif benefit_type == BENEFIT_TYPE.MANJIAN:
        threshold_text = u'满{}使用'.format(coupon_obj.prepay_threshold)
    elif benefit_type == BENEFIT_TYPE.DISCOUNT:
        threshold_text = u'满{}-{}使用'.format(coupon_obj.prepay_threshold, coupon_obj.upper_limit)

    data = {
        # 注意区分，这里 不!是!CouponInfo!Id
        'coupon_id': coupon_obj.id,
        'name': coupon_obj.name,

        # 美券类型, int, 目前有平台(1)/医生(2)
        'coupon_type': coupon_obj.coupon_type,
        'coupon_type_text': u'平台券' if coupon_obj.coupon_type == COUPON_TYPES.PLATFORM else u'医生券',

        # 直减、满减标记
        'has_threshold': coupon_obj.has_threshold,
        # 满X减少里面的 X，这里其实可能是 预付款/尾款
        'threshold': coupon_obj.prepay_threshold if coupon_obj.prepay_threshold else None,
        # 满X减Y 里面哪个减（对于不同类型减的是不同的），在某些情况下可能没有这个值，比如没做的折扣券，之后可能会在这里增加判断的type
        'value': coupon_obj.value if coupon_obj.value else None,
        # 7780 折扣券注释
        # 'threshold_text': u'满{}使用'.format(
        #     coupon_obj.prepay_threshold) if coupon_obj.prepay_threshold > 0 else u'无门槛美券',
        'threshold_text': threshold_text,

        # 券的可用时间类型
        'time_type': coupon_obj.time_type,  # 券可用时间类型，分为 开始/结束，领取后X天
        'start_time': start_time,  # 可选， 可能为None，
        'end_time': end_time,
        'countdown': coupon_obj.countdown if coupon_obj.countdown else None,  # 倒计时类型的时候，"领取后X天内使用"
        'time_text': time_text,

        'use_term': coupon_obj.use_term,
    }
    return data


@bind_context('api/coupongift/listinfo_by_doctor')
@list_interface(offset_name='start_num', limit_name='count')
def get_coupongift_by_doctor(ctx, doctor_id=None, hospital_id=None, count=10, start_num=0,
    filter_displayable=True
):
    """
    医生主页可领美券
    """
    user = get_user_from_context(ctx)
    assert not (doctor_id is None and hospital_id is None)
    if hospital_id is not None:
        doctor = Doctor.objects.filter(
            hospital_id=hospital_id, is_online=True, doctor_type=DOCTOR_TYPE.OFFICER
        ).only('id').first()
        if doctor:
            doctor_id = doctor.id
    # 暂时不管用户已经领取过的
    can_get_gift_ids, already_get_gift_ids, gift_id_to_left_count = get_coupon_gift_id_for_doctor_detail(
        user, doctor_id, start_num, count, filter_displayable=filter_displayable
    )

    res = coupon_manager.gift_ids_to_gift_dtos(can_get_gift_ids, already_get_gift_ids)

    # 全店通用券>医生通用>医生部分可用券
    # 如果类型一致, 则按照金额排序(金额越大越靠前)
    # 如果金额一致, 则按照ID排序(Id越大, 越靠前)

    res.sort(key=lambda x: (-x['doctor_coupon_type'], -x['expense'], -x['gift_id']))

    return res


@bind_context('api/coupongift/get_top_coupon_gift_infos_by_service')
def get_top_coupon_gift_infos_by_service(ctx, service_id):
    result = coupon_manager.get_top_coupon_gift_infos(service_id)
    return result


@bind('api/coupongift/get_show_coupon_for_service_detail')
def get_show_coupon_for_service_detail(service_id, show_count=None):
    if show_count is None:
        show_count = 2  # 历史上的默认值

    return coupon_manager.get_show_coupon_dict_for_service_detail(service_id, show_count=show_count)


@bind_context('api/coupongift/listinfo_by_service')
@list_interface(offset_name='start_num', limit_name='count')
def get_coupongift_by_service(ctx, service_id, check_user_has_gift_left=False, count=10, start_num=0,
                              use_new_result_format=True):
    """
    美购详情页可领美券
    """
    if service_id is None or start_num < 0 or count <= 0:
        return []
    user = get_user_from_context(ctx) if check_user_has_gift_left is True else None
    result = coupon_manager.get_coupon_gift_info_list_for_service_detail(service_id, user, count, start_num)
    return result


@bind_context('api/coupongift/listinfo_by_service_with_type')
@list_interface(offset_name='start_num', limit_name='count')
def get_coupongift_by_service_with_type(ctx, service_id, check_user_has_gift_left=False, count=10, start_num=0, use_new_result_format=True, gift_type=None):
    """
    美购详情页 美券列表
    """
    if service_id is None or start_num < 0 or count <= 0:
        return []
    user = get_user_from_context(ctx) if check_user_has_gift_left is True else None
    result = coupon_manager.get_coupon_gift_info_for_service_detail_with_type(service_id, user, count, start_num, gift_type)

    # 针对尾款券进行排序: 金额大, id大
    if gift_type and int(gift_type) == 2 and result:
            result.sort(key=lambda x: (-x['expense'], -x['gift_id']))

    return result


@bind_context('api/coupongift/listinfo_by_gift_ids')
def get_coupongift_by_gift_ids(ctx, gift_ids, check_user_has_gift_left=False, filter_displayable=True):
    """ 直播间 美券礼包列表 """

    if not gift_ids:
        return []

    user = get_user_from_context(ctx) if check_user_has_gift_left is True else None

    result = coupon_manager.get_coupongift_by_gift_ids(
        gift_ids, user,
        filter_displayable=filter_displayable
    )

    result.sort(key=lambda x: gift_ids.index(x["gift_id"]))

    return result


@bind_context('api/coupongift/create_by_gift', login_required=True)
def coupongift_create_by_gift(ctx, gift_id, filter_displayable=True):
    """
    领取礼包的最早生效的channel
    :param ctx:
    :param gift_id:
    :return:
        {
            has_gift_left: 是否可以剩余
            success: 是否领取成功
        }
    """
    user = get_user_from_context(ctx)
    gift_id = int(gift_id)
    get_gift_success, user_has_gift_left = user_get_gift_by_displayab_page(
        gift_id, user,
        filter_displayable=filter_displayable
    )
    return {
        'has_gift_left': user_has_gift_left,
        'success': get_gift_success,
    }


@bind_context('api/coupon/get_one_purchase_coupongift', login_required=True)
def coupongift_for_one_purchase(ctx):
    """
    一元购的美券
    :param ctx:
    :return:
    """
    user = get_user_from_context(ctx)

    gift_id = settings.ONEPURCHASE_COUPONGIFT_GIFT_ID
    channel_id = settings.ONEPURCHASE_COUPONGIFT_CHANNEL_ID

    is_old_user = Order.objects.filter(user=user, status__in=HAS_BOUGHT).exists()

    if is_old_user is False:
        gift = CouponGift.objects.get(id=gift_id)

        channel_gift = ChannelGift.objects.get(gift_id=gift_id, business_channel_id=channel_id)
        success, codes = _try_get_gift(channel_gift, user)

        gift_info = to_show_gift_dict(gift)

        return {
            'success': success,
            'gift_info': gift_info,
            'codes': codes,
        }
    else:
        gen(CODES.NOT_QUALIFIED_FOR_COUPONGIFT)


@bind_context('api/coupon/get_gift_by_push_for_shopcart')
def coupongift_push_for_shopcart(ctx, user_ids=[]):
    """
    """
    uids = set(user_ids)

    users = list(User.objects.filter(id__in=uids))

    result = user_get_gift_by_push_for_shopcart(users)

    return result


@bind_context('api/coupon_list/v1', login_required=True)
def coupon_list_v1(ctx, status=0, offset=0, count=10, remove_badge=True):
    """
    用户个人中心美券列表页
    :param remove_badge:
    :param count:
    :param offset:
    :param ctx:
    :param status: 0.未消费，1.已消费，2.已过期
    :return:
    """

    user = get_user_from_context(ctx)
    is_new_user = CouponInfo._is_new_user_to_coupon(user)
    queryset = CouponInfo.objects.filter(user=user)
    now = get_current_time()
    unbadged_coupon_ids = set()
    will_overtime_coupon_ids = set()
    cache_will_overtime_ids = set()
    # 未消费美券
    if status == 0:
        queryset = queryset.filter(status=COUPON_STATUS.CLAIMED, end_time__gt=now).order_by('-claimed_time')
        if not is_new_user:
            queryset = queryset.filter(coupon__is_new_user=False)
        coupon_ids = set(ciid for ciid in queryset.values_list('id', flat=True))
        will_overtime_coupon_ids = set(ciid for ciid in queryset.filter(end_time__lte=now + timedelta(hours=72)).values_list('id', flat=True))
        if coupon_badge_cache.get(str(user.id)):
            cache_ids = json.loads(coupon_badge_cache.get(str(user.id)))
            badged_coupon_ids = set(cache_ids.get('ids'))
        else:
            badged_coupon_ids = set([])
        unbadged_coupon_ids = coupon_ids - badged_coupon_ids

        if coupon_badge_cache.get("will_overtime_{}".format(str(user.id))):
            cache_will_overtime_ids = json.loads(coupon_badge_cache.get("will_overtime_{}".format(str(user.id))))
            cache_will_overtime_ids = set(cache_will_overtime_ids.get('ids'))

        if remove_badge:
            coupon_badge_cache.set(str(user.id), json.dumps({'ids': list(coupon_ids)}))
            coupon_badge_cache.set("will_overtime_{}".format(str(user.id)), json.dumps({'ids': list(will_overtime_coupon_ids)}))
        will_overtime_coupon_ids -= cache_will_overtime_ids
    # 已消费美券
    elif status == 1:
        queryset = queryset.filter(
            Q(status=COUPON_STATUS.CONSUMED) | Q(status=COUPON_STATUS.FROZEN)
        )
    # 已过期美券
    else:
        common_filter = Q(status=COUPON_STATUS.CLAIMED)
        query_filter = time_filter = Q(end_time__lte=now)
        if not is_new_user:
            query_filter = time_filter | Q(coupon__is_new_user=True)
        queryset = queryset.filter(common_filter & query_filter).order_by('-end_time')

    coupon_list = list(queryset[offset: offset + count].select_related('coupon'))

    coupon_info_list = to_coupon_info_dto(coupon_list, unbadged_coupon_ids, will_overtime_coupon_ids)

    return coupon_info_list


def to_coupon_info_dto(coupon_infos, unbadged_coupon_ids=None, will_overtime_coupon_ids=None):
    dto_list = []

    unbadged_coupon_ids = unbadged_coupon_ids if unbadged_coupon_ids else []
    will_overtime_coupon_ids = will_overtime_coupon_ids if will_overtime_coupon_ids else []

    all_doctor_ids = set([c.coupon.doctor_id for c in coupon_infos])

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

    for ci in coupon_infos:
        ciid = ci.id
        cinfo_data = ci.coupon_info_data()

        doctor_id = ci.coupon.doctor_id
        _, doctor_name, doctor_type = doctor_id_to_data.get(doctor_id, (None, None, None))

        c = {
            'id': cinfo_data['id'],
            'value': cinfo_data['value'],
            'name': cinfo_data['name'],
            'coupon_type': cinfo_data['coupon_type'],
            'announcement': cinfo_data['announcement'],
            'start_time': cinfo_data['start_time'],
            'end_time': cinfo_data['end_time'],
            'frozen_time': cinfo_data['frozen_time'],
            'status': cinfo_data['status'],
            'use_term': cinfo_data['use_term'],
            'prepay_threshold': cinfo_data['prepay_threshold'],
            'has_threshold': cinfo_data['has_threshold'],
            'gengmei_percent': cinfo_data['gengmei_percent'],

            'doctor_id': doctor_id,
            'doctor_name': doctor_name,
            'doctor_type': doctor_type,

            'has_badge': ciid in unbadged_coupon_ids,
            'coupon_id': cinfo_data['coupon_id'],
            'groupbuy_avail': cinfo_data['groupbuy_avail'],

            # # 7780 折扣券添加
            'discount_rate': cinfo_data['discount_rate'],
            'upper_limit': cinfo_data['upper_limit'],
            'benefit_type': cinfo_data['benefit_type'],
            'will_overtime': ciid in will_overtime_coupon_ids,
            'use_new_term': cinfo_data['use_new_term'],
        }

        dto_list.append(c)

    return dto_list


@bind('api/channel_coupon/get')
def get_coupon_share_info(data_id):
    try:
        cc = CouponChannel.objects.get(id=data_id)
        re_data = {
            'first_background': cc.phone_image,
            'last_background': cc.coupon_image,
            'download_url': cc.download,
            'package_id': cc.package_id,
            'number': cc.number,
        }
        return re_data
    except CouponChannel.DoesNotExist:
        gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)


@bind('api/batch/coupon_info/get')
def batch_get_coupon_info(service_ids):
    return coupon_manager.get_best_coupon_gift_info_for_service_card(service_ids)


def get_unclaimed_coupons_dict_for_coupon_gift(coupon_gifts_info):
    """
    获取未领取的礼包中的美券信息
    :param coupon_gifts_info:
    :return:
    """
    coupons_dict = {}
    coupon_count = 0
    # 从gifts里拆出coupon_info
    for coupon_gift_info in coupon_gifts_info:
        if coupon_gift_info['has_gift_left']:
            for coupon, count in coupon_gift_info.get('coupons_info', []):
                coupon_count += count
                if coupon['id'] not in coupons_dict.keys():
                    data = {
                        'groupbuy_avail': coupon['groupbuy_avail'],
                        'prepay_threshold': coupon['prepay_threshold'],
                        'value': coupon['value'],
                        'service_id': coupon_gift_info.get('service_id')
                    }
                    coupons_dict[coupon['id']] = data
    return coupons_dict, coupon_count


def get_forge_couponinfo(coupons_dict, user, claimed_time=datetime.now()):
    """
    伪造couponinfo表中的数据，模拟成下单可以使用
    :param coupons_dict:
    :param user:
    :param claimed_time:
    :return:
    """
    forge_cps = []
    coupon_ids = coupons_dict.keys()
    coupons = Coupon.objects.filter(id__in=coupon_ids)
    for coupon in coupons:
        if coupon.time_type == COUPON_TIME_TYPE.START_END:
            start_time = coupon.start_time
            end_time = coupon.end_time
        else:
            start_time = claimed_time
            end_time = claimed_time + timedelta(days=coupon.countdown)
        cpi = CouponInfo(
            coupon=coupon,
            user=user,
            claimed_time=claimed_time,
            status=COUPON_STATUS.CLAIMED,
            start_time=start_time,
            end_time=end_time,
            # relation=relation
        )
        forge_cps.append(cpi)
    return forge_cps


@bind_context('api/coupon/get_recommend_coupon')
def get_recommend_coupon(ctx, service_id=None, service_item_id=None, groupbuy_price_id=None):
    """
    1、根据service_id，拿到可领或已领的美券信息，如果没有service_item_id或者没有user信息，return可领的美券张数
    2、把所有可领+已领的美券信息塞给这个用户，
    3、模拟下单，有拼团价的模拟拼团下单。没有拼团的，模拟普通下单。拿到一个最合适的券recommend_coupon
    4、拿recommend_coupon去美券信息里找这个券是否已领，根据状态提示对应的文案
    """
    COUPON_COUNT = 500
    GIFT_TYPE = COUPON_GIFT_TYPES.PLATFORM # 目前这个接口只针对预付款券（平台券）
    NUMBER = 1
    user = get_user_from_context(ctx)
    now = datetime.now()
    result = {}

    # 根据 user service_id 获得美券礼包的信息
    coupon_gifts_info = get_coupon_gift_info_for_service_detail_with_type(service_id, user=user, count=COUPON_COUNT, gift_type=GIFT_TYPE)

    # 从美券大礼包里拿出 去重后的未领取的美券的信息 以及张数和
    coupons_dict, coupon_count = get_unclaimed_coupons_dict_for_coupon_gift(coupon_gifts_info)

    result.update({
                'coupon_count': coupon_count,
            })

    # 未登录或者没选中sku，到此为止，只返回可领取券的数量
    if not user or not service_item_id:
        return result

    # 把未领取的美券塞进CouponInfo表，伪造CouponInfo表的信息
    forge_cps = get_forge_couponinfo(coupons_dict, user, now)

    if not groupbuy_price_id:
        groupbuy_price_id = None

    # 由sku_id以及groupbuy_price_id拿可用券
    service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(
        {}, service_item_id, NUMBER, user, groupbuy_price_id
    )
    valid_coupons = CouponInfo.valid_coupons(user, service_item_id_to_ordering_info) + CouponInfo.get_vaild_coupons(service_item_id_to_ordering_info, forge_cps)

    # 用groupbuy_price_id查不到可用券，就去查非拼团
    if not valid_coupons and groupbuy_price_id:
        service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(
            {}, service_item_id, NUMBER, user, None
        )
        valid_coupons = CouponInfo.valid_coupons(user, service_item_id_to_ordering_info) + CouponInfo.get_vaild_coupons(service_item_id_to_ordering_info, forge_cps)

    # 输出内容组装
    if valid_coupons:
        ci_dtos = to_coupon_info_dto(valid_coupons)
        valid_coupon_infos = CouponInfo.sort_coupons(ci_dtos)
        # 找到coupons_info里对应的id，找不到的话 就是已领的，能找到的话，根据has_gift_left判断是已领还是未领
        best_coupon = valid_coupon_infos[0]
        best_coupon_id = best_coupon['coupon_id']
        groupbuy_desc = '拼团' if best_coupon['groupbuy_avail'] and groupbuy_price_id else '下单'
        if CouponInfo.objects.filter(
                user=user,
                status=COUPON_STATUS.CLAIMED,
                end_time__gt=now,
                coupon_id=best_coupon_id,
        ).exists():
            desc = "您有{}元券,{}立减{}元".format(best_coupon['value'], groupbuy_desc, best_coupon['value'])
            has_coupon = False
        else:
            desc = "领券{}立减{}元".format(groupbuy_desc, best_coupon['value'])
            has_coupon = True
        result.update({
            'desc': desc,
            'has_coupon': has_coupon,
        })
    return result


@bind_context('api/service/get_sku_discount_price')
def get_discount_price(ctx, service_id=None, service_item_id=None, groupbuy_price_id=None, gift_type=None, price=None):
    user = get_user_from_context(ctx)
    discount_price, _, _ = calculate_preferential_price(user, service_id, service_item_id, groupbuy_price_id, gift_type, price)
    result = {
        service_item_id: discount_price,
    }
    return result


def calculate_preferential_price(user=None, service_id=None, service_item_id=None, groupbuy_price_id=None, gift_type=None, price=None, is_login=True):
    """
    价格竞争力三期，券后价展示

    1、美购详情页，同一个美购里的价格计算可以批量
    2、美购列表，都是未登录状态下的计算，也可以批量，并且可以缓存
    """
    # 更美价为0，直接返回
    deal_price = {
        COUPON_TYPES.PLATFORM: price.get('pre_price'),
        COUPON_TYPES.DOCTOR: price.get('final_price'),
    }
    # 处理一下groupbuy
    if not groupbuy_price_id:
        groupbuy_price_id = None

    # 礼包类型限制一下
    if gift_type:
        gift_types = [gift_type]
    else:
        gift_types = [COUPON_TYPES.PLATFORM, COUPON_TYPES.DOCTOR]

    if settings.DISCOUNT_PRICE_DEMOTION:
        is_login = False  # 设置开关关掉已经登录用户精确计算券后价

    # 要开始算优惠价了
    preferential_price = price['total_price']
    pre_discount = 0
    last_discount = 0
    for gift_type in gift_types:
        if is_login:
            # 用户登录时，券后价要精确计算
            discount_value, pre_discount_value, last_discount_value = get_login_discount(user, service_id, service_item_id, groupbuy_price_id, gift_type, deal_price[gift_type])
        else:
            # 用户未登录时，券后价用现有美券算, 拿到所有礼包信息
            discount_value, pre_discount_value, last_discount_value = get_not_login_discount(service_id, deal_price[gift_type], groupbuy_price_id, gift_type, user)
        preferential_price -= discount_value
        if gift_type == COUPON_TYPES.PLATFORM:
            pre_discount = pre_discount_value
        if gift_type == COUPON_TYPES.DOCTOR:
            last_discount = last_discount_value
    return max(preferential_price, 0), pre_discount, last_discount


@traced_function(name="get_not_login_discount")
def get_not_login_discount(service_id=None, price=None, groupbuy_price_id=None, gift_type=None, user=None):
    """
    未登录时的用券减免, 未走下单流程，数据不够可靠
    """
    if price is None:
        return 0
    COUPON_COUNT = 500
    coupon_gifts_info = get_coupon_gift_info_for_service_detail_with_type(service_id, user=user, count=COUPON_COUNT, gift_type=gift_type)
    discount_price = 0
    pre_discount_price = 0
    last_discount_price = 0
    can_use_coupons_info = []
    for coupon_gift_info in coupon_gifts_info:
        if coupon_gift_info['gift_type'] == gift_type:
            for coupon, count in coupon_gift_info.get('coupons_info', []):
                coupon['gift_type'] = gift_type
                if groupbuy_price_id:
                    can_use_coupons_info.append(coupon) if coupon['groupbuy_avail'] else None
                else:
                    can_use_coupons_info.append(coupon)
    for can_use_coupon_info in can_use_coupons_info:
        if can_use_coupon_info['benefit_type'] == BENEFIT_TYPE.DISCOUNT:
            if price >= can_use_coupon_info['prepay_threshold'] and price <= can_use_coupon_info['upper_limit']:
                can_use_coupon_info['value'] = price - int(price * can_use_coupon_info['discount_rate'] // 100)
        if price >= can_use_coupon_info['prepay_threshold'] and price >= can_use_coupon_info['value']:
            discount_price = max(discount_price, can_use_coupon_info['value'])
            if can_use_coupon_info['gift_type'] == COUPON_TYPES.PLATFORM:
                pre_discount_price = max(pre_discount_price, can_use_coupon_info['value'])
            if can_use_coupon_info['gift_type'] == COUPON_TYPES.DOCTOR:
                last_discount_price = max(last_discount_price, can_use_coupon_info['value'])

    return discount_price, pre_discount_price, last_discount_price


@traced_function(name="get_login_discount")
def get_login_discount(user=None, service_id=None, service_item_id=None, groupbuy_price_id=None, gift_type=None, price=None):
    """
    登录后的用券减免
    登录后会模拟用户领券下单，数据真实
    """
    COUPON_COUNT = 500
    NUMBER = 1
    now = datetime.now()

    # 根据 user service_id 获取美券礼包信息
    coupon_gifts_info = get_coupon_gift_info_for_service_detail_with_type(service_id, user=user, count=COUPON_COUNT, gift_type=gift_type)

    # 从美券大礼包里拿出 去重后的未领取的美券的信息 以及张数和
    coupons_dict, coupon_count = get_unclaimed_coupons_dict_for_coupon_gift(coupon_gifts_info)
    # 把未领取的美券塞进CouponInfo表，伪造CouponInfo表的信息
    forge_cps = get_forge_couponinfo(coupons_dict, user, now)
    # 拼团的只查拼团，非拼团的只查非拼团
    service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(
        {}, service_item_id, NUMBER, user, groupbuy_price_id
    )
    # 拿有效的券
    valid_coupons = CouponInfo.valid_coupons(user, service_item_id_to_ordering_info, False, gift_type) + \
                    CouponInfo.get_vaild_coupons(service_item_id_to_ordering_info, forge_cps)
    if valid_coupons:
        ci_dtos = to_coupon_info_dto(valid_coupons)
        for ci_dto in ci_dtos:
            if ci_dto['benefit_type'] == BENEFIT_TYPE.DISCOUNT:
                ci_dto['value'] =  price - int(price * ci_dto['discount_rate'] // 100)
        valid_coupon_infos = CouponInfo.sort_coupons(ci_dtos)
        # 拿到最优券
        best_coupon = valid_coupon_infos[0]
        pre_valid_coupon_infos = [coupon for coupon in valid_coupon_infos if coupon['coupon_type'] == COUPON_TYPES.PLATFORM]
        pre_best_coupon = pre_valid_coupon_infos[0] if pre_valid_coupon_infos else {}
        last_valid_coupon_infos = [coupon for coupon in valid_coupon_infos if coupon['coupon_type'] == COUPON_TYPES.DOCTOR]
        last_best_coupon = last_valid_coupon_infos[0] if last_valid_coupon_infos else {}
        return best_coupon['value'], pre_best_coupon.get('value', 0), last_best_coupon.get('value', 0)
    return 0, 0, 0


@bind_context('api/coupongift/batch_list_coupongifts')
def batch_list_coupongifts(ctx, gift_ids=[], gift_type=COUPON_GIFT_TYPES.PLATFORM):
    """ OM 后台 直播-平台美券信息列表
    """

    gifts = CouponGift.objects.filter(
        id__in=gift_ids,
        gift_type=gift_type
    )

    return {
        item.id: {
            "name": item.name,
            "value": item.value,
            "created_time": get_timestamp_or_none(item.created_time),
            "updated_time": get_timestamp_or_none(item.updated_time),
            "start_time": get_timestamp_or_none(item.start_time),
            "end_time": get_timestamp_or_none(item.end_time),
            "launch_time": get_timestamp_or_none(item.launch_time),
            "gift_id": item.id,
            "gift_type": item.gift_type,
        }
        for item in gifts
    }

@bind_context('api/coupongift/filter_available_gifts')
def batch_list_coupongifts(ctx, gift_ids=[], offset=0, size=10):
    """ 直播 过滤可领取的美券礼包 """

    filtered_gift_ids = filter_available_gifts(gift_ids, offset=offset, size=size)

    return {
        "gift_ids": filtered_gift_ids
    }
