# coding=utf-8
import datetime

from django.db import IntegrityError
from django.db import models
from django.db import transaction
from gm_types.gaia import ORDER_STATUS, SERVICE_ITEM_PRICE_TYPE, ACTIVITY_TYPE_ENUM, GROUPBUY_TYPE

import point

from api.manager.refund_manager import decrease_inventory
from api.manager import groupbuy_manager
from api.models import POINTS_TYPE
from api.models import SETTLEMENT_STATUS
from api.models import Service
from api.models import ServiceItem
from api.models import Settlement
from api.models import Shopcart, Order, CouponInfo
from api.models import Special, SpecialItem, SpecialService
from api.models.types import PAYMENT_TYPE
from pay.tool import random_tool
from pay.tool import time_tool, DatetimeTool
from rpc.tool.error_code import CODES
from rpc.tool.error_code import gen
from rpc.tool.log_tool import info_logger


class OrderingInfo(object):
    def __init__(self, number, service_obj=None, temp_service_item_model_obj=None,
                 online_special_id_set=None, service_online_special_id_by_tags_set=None):
        self.service_obj = service_obj
        self.temp_service_item_model_obj = temp_service_item_model_obj
        self.number = number
        self.online_special_id_set = online_special_id_set
        self.service_online_special_id_by_tags_set = service_online_special_id_by_tags_set
        self.service_item_ordering_dict = None


def _try_get_service_item_id_and_number_from_one_item(number, service_item_id=None, ):
    """
    :return: ( success, { service_item_id: number} , None or CODES )
    """
    service_item_id_to_number_and_cart_item_id = {}

    if number <= 0:
        return False, service_item_id_to_number_and_cart_item_id, CODES.PARAMS_INVALID

    if service_item_id:
        if service_item_id <= 0:
            return False, service_item_id_to_number_and_cart_item_id, CODES.PARAMS_INVALID

        items = ServiceItem.objects.filter(id=service_item_id, is_delete=False)

        if len(items) != 1:
            return False, service_item_id_to_number_and_cart_item_id, CODES.SERVICE_NOT_FOUND

        item = items[0]
        service_item_id_to_number_and_cart_item_id[item.id] = (number, "0")

        return True, service_item_id_to_number_and_cart_item_id, None

    else:
        return False, service_item_id_to_number_and_cart_item_id, CODES.SERVICE_NOT_FOUND


def _try_get_service_item_id_and_number_from_cart_item_info(person, cart_item_info=None):
    """

    :return: ( success, { service_item_id: (number, cart_item_id) }, , { cart_item_id : CODES } )
    """
    siid_to_number = {}
    siid_to_cart_item_id = {}
    service_item_id_to_number_and_cart_item_id = {}
    cart_item_id_to_service_item_id = {}
    errors = {}

    if cart_item_info:

        cart_item_ids = [k for k, v in cart_item_info.items()]
        items = Shopcart.objects.filter(person=person, id__in=cart_item_ids)

        for shop_cart_item in items:

            siid = shop_cart_item.service_item_id
            cart_item_id_to_service_item_id[str(shop_cart_item.id)] = siid
            siid_to_cart_item_id[siid] = str(shop_cart_item.id)

        for cid, number in cart_item_info.items():
            if cid in cart_item_id_to_service_item_id:
                if number > 0:
                    siid = cart_item_id_to_service_item_id[cid]
                    service_item_id_to_number_and_cart_item_id[siid] = (number, cid)
                else:
                    errors[cid] = CODES.PARAMS_INVALID
            else:
                errors[cid] = CODES.SERVICE_NOT_FOUND

        if errors:
            return False, service_item_id_to_number_and_cart_item_id, errors
        else:
            return True, service_item_id_to_number_and_cart_item_id, errors
    else:
        return False, service_item_id_to_number_and_cart_item_id, errors


def get_service_id_to_online_special_ids_by_tags(service_ids, now=None):
    if now is None:
        now = datetime.datetime.now()

    all_online_special_id_set = set(Special.objects.filter(
        is_online=True,
        start_time__lte=now,
        end_time__gte=now,
    ).values_list('id', flat=True))

    service_id_to_online_special_ids_by_tags = {}

    special_service_data = list(SpecialService.objects.filter(
        service_id__in=service_ids, special_id__in=all_online_special_id_set
    ).values_list('special_id', 'service_id'))

    for special_id, service_id in special_service_data:
        if service_id not in service_id_to_online_special_ids_by_tags:
            service_id_to_online_special_ids_by_tags[service_id] = []
        service_id_to_online_special_ids_by_tags[service_id].append(special_id)

    return service_id_to_online_special_ids_by_tags


def get_sku_id_to_online_special_id_set(service_item_ids, now=None):
    if now is None:
        now = datetime.datetime.now()

    all_online_special_data = SpecialItem.objects.filter(
        serviceitem_id__in=service_item_ids,
        special__is_online=True,
        special__start_time__lte=now,
        special__end_time__gte=now,
    ).values_list('serviceitem_id', 'special_id')

    sku_id_to_online_special_id_set = {}

    for sku_id, special_id in all_online_special_data:
        if sku_id not in sku_id_to_online_special_id_set:
            sku_id_to_online_special_id_set[sku_id] = set()
        sku_id_to_online_special_id_set[sku_id].add(special_id)

    return sku_id_to_online_special_id_set


def _get_ordering_info_from_service_item_id_and_number(service_item_id_and_number):
    """get services.
    1. 根据service_item_id_and_number获取，结构应该是 { 1222: 1, 998: 2, }

    return:
        { service_item_id: OrderingInfo,},
    """
    ordering_info = {}

    all_service_item_ids = [k for k, v in service_item_id_and_number.items()]

    sis = ServiceItem.objects.filter(id__in=all_service_item_ids)

    assert len(sis) == len(service_item_id_and_number)

    now = datetime.datetime.now()

    siid_to_obj = {si.id: si for si in sis}

    all_service_id = [si.service_id for si in sis]

    ss = Service.objects.filter(id__in=all_service_id)

    sku_id_to_online_special_id_set = get_sku_id_to_online_special_id_set(all_service_item_ids, now)
    service_id_to_online_special_ids_by_tags = get_service_id_to_online_special_ids_by_tags(all_service_id, now)

    sid_to_obj = {s.id: s for s in ss}

    for service_item_id, number in service_item_id_and_number.items():
        service_item_model_obj = siid_to_obj[service_item_id]
        service_obj = sid_to_obj[service_item_model_obj.service_id]
        online_special_id_set = sku_id_to_online_special_id_set.get(service_item_id, None)

        service_online_special_id_by_tags_set = set(service_id_to_online_special_ids_by_tags.get(service_obj.id, []))
        ordering_info[service_item_id] = OrderingInfo(
            number=number, service_obj=service_obj,
            temp_service_item_model_obj=service_item_model_obj,
            online_special_id_set=online_special_id_set,
            service_online_special_id_by_tags_set=service_online_special_id_by_tags_set)

    return ordering_info


def _get_ordering_info_from_paramters(cart_item_info, service_item_id, number, user):
    success = False
    buy_one_thing = True
    error_code = None
    service_item_id_to_number_and_cart_item_id = {}
    cart_item_id_to_error_codes = {}

    if cart_item_info and isinstance(cart_item_info, dict) and cart_item_info > 0:
        buy_one_thing = False

        ci_info = cart_item_info

        for cid, number in ci_info.items():
            if cid <= 0 or number <= 0:
                gen(CODES.PARAMS_INVALID)

        success, service_item_id_to_number_and_cart_item_id, cart_item_id_to_error_codes = \
            _try_get_service_item_id_and_number_from_cart_item_info(
                person=user.person,
                cart_item_info=ci_info
            )
    else:
        success, service_item_id_to_number_and_cart_item_id, error_code = \
            _try_get_service_item_id_and_number_from_one_item(number=number,
                                                              service_item_id=service_item_id)

    return success, buy_one_thing, service_item_id_to_number_and_cart_item_id, error_code, cart_item_id_to_error_codes


_ordering_info_error_text_service_pre_payment_zero = u'美购预付款要大于0哦~'
_ordering_info_error_text_service_pre_payment_must_lte = u'美购出现错误，请联系客服~'
_ordering_info_error_text_service_not_found = u'该美购已失效~'
_ordering_info_error_text_service_max_limit = u'该美购最多可以购买{}件~'
_ordering_info_error_text_service_sold_out = u'该美购已卖完~'
_ordering_info_error_text_service_max_ordering_limit = u'该美购最多可购买{}件~'
_ordering_info_error_text_service_max_single_user_buy_limit = u'该美购最多可购买{}件~'
_ordering_info_error_text_seckill_max_single_user_buy_limit = u'秒杀仅可购买{}件哦~'
_ordering_info_error_text_service_unpaid = u'该美购有尚未付款的订单哦~'
_ordering_info_error_text_order_seckill_limit = u'秒杀仅可购买{}件哦，不可再购买啦~'
_ordering_info_error_text_order_seckill_left_limit = u'秒杀仅可再购买{}件哦~'
_ordering_info_error_text_order_service_limit = u'该美购仅可购买{}件哦，不可再购买啦~'
_ordering_info_error_text_order_service_left_limit = u'该美购仅可再购买{}件哦~'


def _check_valid(user, service_item_id_to_ordering_info):
    def check_prepay_gt_zero(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        for siid, info in siid_to_ordering_info:
            ordering_price_info = info.ordering_price_info
            pre_payment_price = ordering_price_info["pre_payment_price"]
            gengmei_price = ordering_price_info["gengmei_price"]

            if pre_payment_price < 1:
                siid_to_error_text.append((siid, _ordering_info_error_text_service_pre_payment_zero))
            elif gengmei_price < pre_payment_price:
                siid_to_error_text.append((siid, _ordering_info_error_text_service_pre_payment_must_lte))
            else:
                left.append((siid, info))
        return left

    def check_buy_limit(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        orderding_limit = Service.get_orderding_limit()
        for siid, info in siid_to_ordering_info:
            if info.number > orderding_limit:
                siid_to_error_text.append((siid, _ordering_info_error_text_service_max_limit.format(orderding_limit)))
            else:
                left.append((siid, info))
        return left

    def check_sku_stock(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        for siid, info in siid_to_ordering_info:
            sku_stock = info.service_item_ordering_dict["sku_stock"]

            if sku_stock <= 0:
                siid_to_error_text.append((siid, _ordering_info_error_text_service_sold_out))
            else:
                left.append((siid, info))
        return left

    def check_buy_number_lt_stock(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        for siid, info in siid_to_ordering_info:
            sku_stock = info.service_item_ordering_dict["sku_stock"]
            sale_limit = info.ordering_price_info["sale_limit"]

            stock = min(sku_stock if sku_stock > 0 else 0, sale_limit if sale_limit > 0 else 0)

            if info.number > stock:
                siid_to_error_text.append((siid, _ordering_info_error_text_service_max_ordering_limit.format(stock)))
            else:
                left.append((siid, info))
        return left

    def check_service_single_user_buy_limit(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        for siid, info in siid_to_ordering_info:
            single_user_buy_limit = info.service_obj.single_user_buy_limit
            if single_user_buy_limit > 0:
                if info.number > single_user_buy_limit:
                    siid_to_error_text.append(
                        (siid,
                         _ordering_info_error_text_service_max_single_user_buy_limit.format(single_user_buy_limit)))
                else:
                    left.append((siid, info))
            else:
                left.append((siid, info))

        return left

    def check_seckill_single_user_buy_limit(user, siid_to_ordering_info, siid_to_error_text):
        left = []
        for siid, info in siid_to_ordering_info:
            price_info = info.ordering_price_info

            if price_info.get('price_type') == SERVICE_ITEM_PRICE_TYPE.SECKILL:
                single_user_buy_limit = price_info.get('single_user_buy_limit', 0)
                if info.number > single_user_buy_limit > 0:
                    siid_to_error_text.append(
                        (siid,
                         _ordering_info_error_text_seckill_max_single_user_buy_limit.format(single_user_buy_limit)))
                else:
                    left.append((siid, info))
            else:
                left.append((siid, info))
        return left

    def check_order_limit(user, siid_to_ordering_info, siid_to_error_text):
        # 1. 未支付订单中包含有购物车中SKU
        # 2. 购物车中SKU处于秒杀且【价格已经下单数量】大于等于【单用户购买限制】
        # 3. 购物车中SKU处于秒杀且【价格还能购买数量】小于【购买数量】
        # 4. 购物车中SKU如果存在【美购本月购买上限】，【已经下单数量】大于等于【美购本月购买上限】
        # 5. 购物车中SKU如果存在【美购本月购买上限】，【SKU 还能购买数量】小于【购买数量】

        current_info = [i for i in siid_to_ordering_info]
        left_info = []

        all_service_item_id = [k for k, v in siid_to_ordering_info]

        order_exclude = models.Q(status=ORDER_STATUS.CANCEL) | models.Q(status=ORDER_STATUS.REFUNDED)
        user_query = models.Q(user=user)
        phone_query = models.Q(phone=user.userextra.phone)

        # 1. 未支付订单中包含有购物车中SKU（先去掉...）
        # unpaid_user_service_item_ids_list = Order.objects \
        #     .filter(user=user, service_item_id__in=all_service_item_id,
        #             status__in=[ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAYING]) \
        #     .values('service_item_id') \
        #     .distinct()
        # unpaid_user_service_item_ids = set(kv['service_item_id'] for kv in unpaid_user_service_item_ids_list)

        for siid, info in current_info:
            left_info.append((siid, info))

        # 2. 购物车中SKU处于秒杀且【价格已经下单数量】大于等于【单用户购买限制】
        # 3. 购物车中SKU处于秒杀且【价格还能购买数量】小于【购买数量】

        left_info = check_seckill(all_service_item_id, left_info,
                                  order_exclude, phone_query, user_query,
                                  siid_to_error_text, )

        # 4. 购物车中SKU如果存在【美购本月购买上限】，【已经下单数量】大于等于【美购本月购买上限】
        # 5. 购物车中SKU如果存在【美购本月购买上限】，【SKU 还能购买数量】小于【购买数量】

        left_info = check_service_limit(left_info, order_exclude, phone_query, user_query, siid_to_error_text)

        return left_info

    def check_service_limit(current_info, order_exclude, phone_query, user_query, siid_to_error_text):
        left_info = []
        all_limit_service_item_ids = set()

        for siid, info in current_info:
            service_item_id = info.service_item_ordering_dict["id"]
            single_user_buy_limit = info.service_obj.single_user_buy_limit
            if single_user_buy_limit > 0:
                all_limit_service_item_ids.add(service_item_id)

        dt = DatetimeTool()

        service_order_user_list = list(Order.objects.filter(service_item_id__in=all_limit_service_item_ids, )
                                       .exclude(order_exclude)
                                       .filter(created_time__gte=dt.get_first_day_month(),
                                               created_time__lte=dt.get_last_day_month())
                                       .filter(user_query)
                                       .values('service_item_id')
                                       .annotate(total=models.Count('service_item_id'))
                                       )
        service_order_user_count = {o['service_item_id']: o['total'] for o in service_order_user_list}

        service_order_phone_list = list(Order.objects.filter(service_item_id__in=all_limit_service_item_ids, )
                                        .exclude(order_exclude)
                                        .filter(created_time__gte=dt.get_first_day_month(),
                                                created_time__lte=dt.get_last_day_month())
                                        .filter(phone_query)
                                        .values('service_item_id')
                                        .annotate(total=models.Count('service_item_id'))
                                        )
        service_order_phone_count = {o['service_item_id']: o['total'] for o in service_order_phone_list}

        for siid, info in current_info:
            service_item_id = info.service_item_ordering_dict["id"]
            single_user_buy_limit = info.service_obj.single_user_buy_limit

            if service_item_id in all_limit_service_item_ids:

                order_user_count = service_order_user_count.get(service_item_id, 0)
                order_phone_count = service_order_phone_count.get(service_item_id, 0)

                if order_user_count >= single_user_buy_limit or order_phone_count >= single_user_buy_limit:
                    siid_to_error_text.append(
                        (siid, _ordering_info_error_text_order_service_limit.format(single_user_buy_limit)))
                else:
                    order_user_left_count = single_user_buy_limit - order_user_count
                    order_phone_left_count = single_user_buy_limit - order_phone_count

                    if order_user_left_count < info.number or order_phone_left_count < info.number:
                        left_count = min(order_user_left_count if order_user_left_count < info.number else 65535,
                                         order_phone_left_count if order_phone_left_count < info.number else 65535,
                                         )
                        siid_to_error_text.append(
                            (siid, _ordering_info_error_text_order_service_left_limit.format(left_count)))
                    else:
                        left_info.append((siid, info))
            else:
                left_info.append((siid, info))
        return left_info

    def check_seckill(all_service_item_id, current_info, order_exclude, phone_query, user_query, siid_to_error_text):
        left_info = []
        all_seckill_price_ids = set()
        for siid, info in current_info:
            price_info = info.ordering_price_info

            is_seckill_price = price_info.get('price_type') == SERVICE_ITEM_PRICE_TYPE.SECKILL
            single_user_buy_limit = price_info.get('single_user_buy_limit')

            if is_seckill_price and single_user_buy_limit > 0:
                all_seckill_price_ids.add(price_info.get('id'))

        seckill_price_order_user_list = list(Order.objects.filter(service_item_price_id__in=all_seckill_price_ids)
                                             .filter(service_item_id__in=all_service_item_id, ).exclude(order_exclude)
                                             .filter(user_query)
                                             .values('service_item_price_id')
                                             .annotate(total=models.Count('service_item_price_id'))
                                             )
        seckill_price_order_user_count = {o['service_item_price_id']: o['total'] for o in seckill_price_order_user_list}

        seckill_price_order_phone_list = list(Order.objects.filter(service_item_price_id__in=all_seckill_price_ids)
                                              .filter(service_item_id__in=all_service_item_id, ).exclude(order_exclude)
                                              .filter(phone_query)
                                              .values('service_item_price_id')
                                              .annotate(total=models.Count('service_item_price_id'))
                                              )
        seckill_price_order_phone_count = {o['service_item_price_id']: o['total'] for o in
                                           seckill_price_order_phone_list}

        for siid, info in current_info:
            price_info = info.ordering_price_info
            price_id = price_info.get('id')
            single_user_buy_limit = price_info.get('single_user_buy_limit')

            if price_id in all_seckill_price_ids:

                order_user_count = seckill_price_order_user_count.get(price_id, 0)
                order_phone_count = seckill_price_order_phone_count.get(price_id, 0)

                if order_user_count >= single_user_buy_limit or order_phone_count >= single_user_buy_limit:
                    siid_to_error_text.append(
                        (siid, _ordering_info_error_text_order_seckill_limit.format(single_user_buy_limit)))
                else:
                    order_user_left_count = single_user_buy_limit - order_user_count
                    order_phone_left_count = single_user_buy_limit - order_phone_count

                    if order_user_left_count < info.number or order_phone_left_count < info.number:
                        left_count = min(order_user_left_count if order_user_left_count < info.number else 65535,
                                         order_phone_left_count if order_phone_left_count < info.number else 65535,
                                         )
                        siid_to_error_text.append(
                            (siid, _ordering_info_error_text_order_seckill_left_limit.format(left_count)))
                    else:
                        left_info.append((siid, info))
            else:
                left_info.append((siid, info))

        return left_info

    #  定义:
    # 【库存】: min( SKU库存, 价格可销售数量 ）
    # 【系统限制】: 下单对一个SKU的最大购买限制
    # 【购买数量】: 客户想要对这个SKU购买的数量
    # 【单用户购买限制】: 某个价格单个用户购买的最大数量限制
    # 【价格已经下单数量】: 某个价格该用户已经购买数量
    # 【价格还能购买数量】: 【单用户购买限制】 - 【价格已经下单数量】
    # 【美购本月购买数量】: 单个用户对于SKU在这个月的购买数量
    # 【美购本月购买上限】: 单个用户针对SKU单月购买数量上限
    # 【SKU已经下单数量】: SKU该用户已经购买数量
    # 【SKU还能购买数量】: 【美购本月购买上限】 - 【美购本月购买数量】

    filter_rules = [
        # 预付款为0
        check_prepay_gt_zero,
        # 【购买数量】大于【系统限制】
        check_buy_limit,
        # 购物车中SKU库存为0
        check_sku_stock,
        # 购物车中【库存】小于【购买数量】
        check_buy_number_lt_stock,
        # 购物车中SKU所属SPU如果存在【美购本月购买上限】，【购买数量】大于【美购本月购买上限】
        check_service_single_user_buy_limit,
        # 购物车中SKU处于秒杀且【购买数量】大于【单用户购买限制】
        check_seckill_single_user_buy_limit,
        # 一切和用户已经下过单相关的检查
        check_order_limit,
    ]

    siid_to_error_text_list = []
    siid_to_ordering_info_list = service_item_id_to_ordering_info.items()

    for rule in filter_rules:
        if len(siid_to_ordering_info_list) > 0:
            left_list = rule(user, siid_to_ordering_info_list, siid_to_error_text_list)
            siid_to_ordering_info_list = left_list

    service_item_id_to_error_text = {i[0]: i[1] for i in siid_to_error_text_list}
    return len(service_item_id_to_error_text) == 0, service_item_id_to_error_text


_valid_groupbuy_mode = (GROUPBUY_TYPE.OLD_TO_NEW, GROUPBUY_TYPE.OLD_TO_OLD)


def _get_service_item_id_to_ordering_info(cart_item_info, service_item_id, number, user, groupbuy_price_id,
                                          now=None):
    if now is None:
        now = datetime.datetime.now()

    params_invalid = False

    # 是否购买一件, service_item_id: (2, cart_id)
    success, buy_one_thing, service_item_id_to_number_and_cart_item_id, \
        error_code, cart_item_id_to_error_codes = _get_ordering_info_from_paramters(
            cart_item_info,
            service_item_id,
            number,
            user
        )

    service_item_id_to_number = {k: v[0] for k, v in service_item_id_to_number_and_cart_item_id.items()}
    service_item_id_to_cart_item_id = {k: v[1] for k, v in service_item_id_to_number_and_cart_item_id.items()}

    if not success and buy_one_thing:
        if error_code == CODES.PARAMS_INVALID:
            params_invalid = True

    if not success and not buy_one_thing:
        error_codes = cart_item_id_to_error_codes.values()
        params_invalid = any([c == CODES.PARAMS_INVALID for c in error_codes])

    if params_invalid:
        gen(CODES.PARAMS_INVALID)

    if not success:
        # CODES.SERVICE_NOT_FOUND
        if buy_one_thing:
            cart_item_id_to_error_codes = {"0": CODES.SERVICE_NOT_FOUND}

        return False, {}, service_item_id_to_cart_item_id, cart_item_id_to_error_codes

    # OrderingInfo
    service_item_id_to_ordering_info = _get_ordering_info_from_service_item_id_and_number(
        service_item_id_to_number)

    if groupbuy_price_id:
        if len(service_item_id_to_ordering_info) > 1:
            gen(CODES.PARAMS_INVALID)
        if number > 1:
            gen(CODES.PARAMS_INVALID)
        if not buy_one_thing:
            gen(CODES.PARAMS_INVALID)

    # 获取当前价格信息，本次之后所有的ServiceItem应该都是这一次的实例（因为价格信息经过一次读取之后会保存在实例中）
    for sku_id, v in service_item_id_to_ordering_info.items():
        service_obj = v.service_obj

        # service_item_ordering_dict 替代 service_item_obj，不要把 temp_service_item_model_obj 以任何形态泄露出去
        service_item_model = v.temp_service_item_model_obj
        del v.temp_service_item_model_obj

        service_not_found = False

        # 这里的逻辑应该保持和service_info_manager.get_current_price_info_by_sku_ids 一致
        service_can_sell = service_obj.is_can_be_sold(check_any_sku_has_stock=False)
        if service_can_sell is not True:
            service_not_found = True

        if service_item_model.is_delete:
            service_not_found = True

        is_child = True if service_item_model.parent_id else False
        current_price_info = service_item_model.get_current_price_info(is_child=is_child)
        if current_price_info is None:
            service_not_found = True

        if service_obj.payment_type not in (PAYMENT_TYPE.FULL_PAYMENT, PAYMENT_TYPE.PREPAYMENT):
            service_not_found = True

        if service_not_found:
            ciid = service_item_id_to_number_and_cart_item_id[service_item_model.id][1]
            cart_item_id_to_error_codes[ciid] = CODES.SERVICE_NOT_FOUND
        else:
            price_info = current_price_info

            if groupbuy_price_id:

                groupbuy_price_info = service_item_model.get_price_info(groupbuy_price_id) or {}

                price_exist = bool(groupbuy_price_info and groupbuy_price_info.get("is_enable", False))

                if not price_exist:
                    return gen(CODES.SERVICE_GROUPBUY_PRICE_DISABLE)

                price_type = groupbuy_price_info.get("price_type", None)
                is_groupbuy_price = bool(price_type == ACTIVITY_TYPE_ENUM.GROUPBUY)

                sku_and_price_match = bool(groupbuy_price_info.get("service_item_id", None) == sku_id)

                price_valid = price_exist and is_groupbuy_price and sku_and_price_match

                if price_valid:
                    rule = groupbuy_price_info["selling_rule"]

                    groupbuy_type = rule["groupbuy_type"]
                    groupbuy_nums = rule["groupbuy_nums"]
                    groupbuy_countdown = rule["groupbuy_countdown"]

                    groupbuy_type_valid = groupbuy_type in _valid_groupbuy_mode
                    groupbuy_nums_valid = groupbuy_nums and groupbuy_nums >= 2
                    groupbuy_countdown_valid = groupbuy_countdown and groupbuy_countdown > 0

                    groupbuy_param_valid = groupbuy_type_valid and groupbuy_nums_valid and groupbuy_countdown_valid

                    if not groupbuy_param_valid:
                        return gen(CODES.PARAMS_INVALID)

                    start_time = groupbuy_price_info["selling_rule"]["start_time"]
                    end_time = groupbuy_price_info["selling_rule"]["end_time"]

                    start_time_valid = start_time and start_time <= now
                    if not start_time_valid:
                        # 拼团未开始
                        return gen(CODES.GROUPBUY_BEFORE_START_TIME)

                    rule_is_enable = rule.get('is_enable', False)
                    end_time_valid = end_time and end_time >= now

                    if not end_time_valid or not rule_is_enable:
                        # 拼团过期，或者是拼团被下线了
                        return gen(CODES.GROUPBUY_AFTER_END_TIME)

                    sold_out = groupbuy_price_info['sale_limit'] <= 0 or service_item_model.sku_stock <= 0
                    if sold_out:
                        # 拼团价的东西卖完了
                        return gen(CODES.GROUPBUY_SOLD_OUT)

                    price_info = groupbuy_price_info
                else:
                    return gen(CODES.PARAMS_INVALID)

            v.ordering_price_info = price_info

            service_item_ordering_dict = {
                "id": service_item_model.id,
                "service_id": service_item_model.service_id,
                "sku_stock": service_item_model.sku_stock,
                "items_name": service_item_model.items_name,
                "more_buy_count": service_item_model.get_more_buy_count(),
                "cost_price": service_item_model.cost_price,
            }

            v.service_item_ordering_dict = service_item_ordering_dict

    if cart_item_id_to_error_codes:
        return False, {}, service_item_id_to_cart_item_id, cart_item_id_to_error_codes
    else:
        return True, service_item_id_to_ordering_info, service_item_id_to_cart_item_id, cart_item_id_to_error_codes


def try_get_groupbuy_team_id_and_groupbuy_price_id(user_id, groupbuy_code, create_groupbuy_price_id):
    if groupbuy_code:
        # 传入参团验证码的时候，要判断团能不能参加
        groupbuy_team, error_code = groupbuy_manager.try_join_groupbuy_team(user_id, groupbuy_code)

        if groupbuy_team:
            groupbuy_team_id = groupbuy_team.id
            groupbuy_price_id = groupbuy_team.price_id

            if groupbuy_price_id:
                return groupbuy_team_id, groupbuy_price_id
            else:
                return gen(CODES.PARAMS_INVALID)
        else:
            return gen(error_code)
    elif create_groupbuy_price_id:
        try:
            groupbuy_price_id = int(create_groupbuy_price_id)
            return None, groupbuy_price_id
        except:
            return gen(CODES.PARAMS_INVALID)
    else:
        return None, None


def try_get_service_item_id_to_ordering_info(cart_item_info, service_item_id, number, user, groupbuy_price_id):
    success, service_item_id_to_ordering_info, service_item_id_to_cart_item_id, \
        cart_item_id_to_error_codes = _get_service_item_id_to_ordering_info(
            cart_item_info, service_item_id, number, user, groupbuy_price_id
    )

    if not success:
        cart_item_id_to_error_text = {
            ciid: _ordering_info_error_text_service_not_found if error_code == CODES.SERVICE_NOT_FOUND else error_code
                for ciid, error_code in cart_item_id_to_error_codes.items()
            }
        return False, {}, service_item_id_to_cart_item_id, {}, cart_item_id_to_error_text

    is_valid, service_item_id_to_error_text = _check_valid(user, service_item_id_to_ordering_info)

    if not is_valid:
        return False, {}, service_item_id_to_cart_item_id, service_item_id_to_error_text, {}

    return True, service_item_id_to_ordering_info, service_item_id_to_cart_item_id, {}, {}


def get_service_item_id_to_ordering_info(cart_item_info, service_item_id, number, user, groupbuy_price_id):
    success, service_item_id_to_ordering_info, service_item_id_to_cart_item_id, \
        cart_item_id_to_error_codes = _get_service_item_id_to_ordering_info(
            cart_item_info, service_item_id, number, user, groupbuy_price_id
    )

    if not success:
        params_invalid = any(
            [error_code == CODES.PARAMS_INVALID for cid, error_code in cart_item_id_to_error_codes.items()])
        service_not_exist = any(
            [error_code == CODES.SERVICE_NOT_FOUND for cid, error_code in cart_item_id_to_error_codes.items()])

        if params_invalid:
            gen(CODES.PARAMS_INVALID)
        elif service_not_exist:
            gen(CODES.SERVICE_NOT_FOUND)
        else:
            gen(CODES.PARAMS_INVALID)
    else:
        return service_item_id_to_ordering_info


def compute_points_deduction_per_service_item(service_item_id_to_ordering_info, user_total_points_yuan,
                                              service_item_coupon_shared_list_dict):
    """

    :param service_item_id_to_ordering_info:
    :param user_total_points_yuan:
    :param service_item_coupon_shared_list_dict:
    :return: { service_item_id_1: [ 2, 1, 0] }
    """
    service_item_deduction_dict = {}

    user_left_points_yuan = user_total_points_yuan

    for service_item_id, v in service_item_id_to_ordering_info.items():
        # if deduction is less than 1, break this loop, points should be used
        # by base 100
        if user_left_points_yuan <= 0:
            break

        service_obj = v.service_obj
        price_info = v.ordering_price_info
        pre_payment_price = price_info["pre_payment_price"]
        service_item_number = v.number

        # 默认可以使用美分
        can_use_points = True

        if 'selling_rule' in price_info and 'can_use_points' in price_info['selling_rule']:
            can_use_points = price_info['selling_rule']['can_use_points']

        spu_points_deduction_percent = service_obj.points_deduction_percent
        sku_discount = price_info['discount']
        points_deduction_yuan = int(sku_discount * spu_points_deduction_percent * 0.01)

        points_for_current_service_item = []

        if can_use_points:
            if service_item_id in service_item_coupon_shared_list_dict:
                coupon_deduction_list = service_item_coupon_shared_list_dict[service_item_id]
            else:
                coupon_deduction_list = [0 for i in range(service_item_number)]

            assert len(coupon_deduction_list) == service_item_number

            for i in range(service_item_number):
                coupon_deduction = coupon_deduction_list[i]

                pre_payment_price_max_point_deduction = (pre_payment_price - coupon_deduction
                                                         if coupon_deduction > 0 else
                                                         pre_payment_price) - 1

                _deduction = min(points_deduction_yuan, user_left_points_yuan, pre_payment_price_max_point_deduction)

                if _deduction < 0:
                    _deduction = 0

                user_left_points_yuan -= _deduction
                points_for_current_service_item.append(_deduction)
        else:
            for i in range(service_item_number):
                points_for_current_service_item.append(0)

        service_item_deduction_dict[service_item_id] = points_for_current_service_item

    return service_item_deduction_dict


def get_services_from_cart_or_service_id_itemkey(person, cart_item_ids,
                                                 service_id=None, item_key=None):
    """get services.
    1. 从购物车获取
    return:
        {(service_id, item_key): (service_obj, service_item_obj),},
    """
    raise Exception("废弃了，不要调用")

    services = {}
    if service_id:
        services[(service_id, item_key)] = item_key

    elif cart_item_ids:
        items = Shopcart.objects.filter(person=person, id__in=cart_item_ids)
        for item in items:
            services[(item.service_id, item.service_item_key)] = item.service_item_key

    ids = [k[0] for k in services.keys()]
    objs = Service.objects.filter(id__in=ids)
    _objs_dict = {obj.id: obj for obj in objs}

    result = {}
    for k, v in services.iteritems():
        item_key = k[1]

        items = ServiceItem.objects.filter(service_id=k[0], is_delete=False)
        if item_key:
            items = items.filter(key=item_key)

        if len(items) != 1:
            return gen(CODES.SERVICE_NOT_FOUND)

        service_item_obj = items[0]
        if service_item_obj.service_id != k[0]:
            return gen(CODES.SERVICE_NOT_FOUND)

        service = _objs_dict[k[0]]
        result[(service.id, item_key)] = (service, service_item_obj)

    if len(result) < 1:
        return gen(CODES.SERVICE_NOT_FOUND)

    return result


def create_settlement(person):
    while True:
        new_id = random_tool.generate_id(id_length=12)
        try:
            with transaction.atomic():
                settlement = Settlement.objects.create(
                        id=new_id,
                        person=person,
                        expired_at=time_tool.get_settlement_expired_datetime()
                )
                return settlement
        except IntegrityError:
            info_logger.warning("Settlement Create Fail, new_id: " + new_id)
            pass


def check_gift(services, use_point):
    if len(services) > 1:
        return

    for k, v in services.items():
        obj = v.service_obj

        if obj.payment_type == PAYMENT_TYPE.EXCHANGE_GIFT and use_point:
            return gen(CODES.FREE_OR_GIFT_CAN_NOT_USE_POINTS_DEDUCTION)


def can_cancled_settlement_2_paid(settlement):
    if settlement.status != SETTLEMENT_STATUS.CANCEL:
        # 不是已取消的订单重置
        return

    with transaction.atomic():
        # 冻结美券
        # 扣美分
        # 减库存
        coupon_info_ids = settlement.get_coupon_info_ids()
        if coupon_info_ids:
            coupon_infos = CouponInfo.objects.filter(id__in=coupon_info_ids)
            for ci in coupon_infos:
                ci.freeze_coupon()

        for item in settlement.items.all():
            order = item.order
            if order.status != ORDER_STATUS.CANCEL:
                raise gen(CODES.ORDER_PAY_STATUS_ERROR)
            order = item.order

            point.remove(
                user_id=order.user.id,
                reason=POINTS_TYPE.BUY_SERVICE,
                point=order.points_deduction,
                order_id=order.id
            )
            decrease_inventory(order)


class SettlementExtraManager(models.Manager):
    def create(self, settlement, current_city_id, channel, device_id=None, idfa=None, idfv=None):
        if device_id:
            idfa = device_id
            idfv = device_id
        super(SettlementExtraManager, self).create(
                settlement=settlement,
                current_city_id=current_city_id,
                channel=channel,
                idfa=idfa,
                idfv=idfv
        )
