# coding=utf-8

import copy
import datetime
import json
import math
import random

from django.conf import settings
from django.db import IntegrityError
from django.db import transaction
from django.db.models import Q, F
from gm_types.gaia import ACTIVITY_TYPE_ENUM, GROUPBUY_STATUS, COUPON_TYPES
from gm_types.poseidon import AllPromotionType

import point
from api.manager.game_manager import clean_discount
from api.manager.refund_manager import increase_inventory
from api.models import (
    ORDER_STATUS,
    Order,
    OrderInfo,
    OrderCouponInfo,
    POINTS_TYPE,
    ServiceItem,
    ORDER_SOURCE,
    Service,
    SERVICE_MODE,
    OrderExtra
)
from api.models import Person
from api.models import SpecialSeckillService
from api.models.coupon import CouponInfo
from api.models.types import PAYMENT_TYPE, ORDER_OPERATION_TYPE, ORDER_OPERATION_ROLE
from api.tasks import order_task, settlement_task
from api.tool.log_tool import logging_exception
from api.tool.notification_tool import send_notification
from api.tool.user_tool import get_user_extra_by_user_id

from pay.models import ServiceSnapshot
from pay.tasks.alter_task import stock_alert
from pay.tool import DatetimeTool

from rpc.tool.error_code import CODES, gen
from rpc.tool.protocol import PushUrlProtocol

from services.custom_phone_service import PhoneService
from services.doctor_notify_service import DoctorNotifyService


def _json_dump_current_price_info(current_price_info):
    info = copy.deepcopy(current_price_info)
    if 'selling_rule' in info:
        start_time = info['selling_rule']['start_time']
        end_time = info['selling_rule']['end_time']
        start_time = start_time.strftime('%Y-%m-%d %H:%M:%S') if start_time else start_time
        end_time = end_time.strftime('%Y-%m-%d %H:%M:%S') if end_time else end_time
        info['selling_rule']['start_time'] = start_time
        info['selling_rule']['end_time'] = end_time

    json_str = json.dumps(info)
    return json_str


class CouponFrozenException(Exception):
    pass


class MultiattributeServiceException(Exception):
    pass


ORDER_ID_LEN = 10


def _generate_order_id(id_length):
    order_id = random.randint(10 ** (id_length - 1), 10 ** id_length - 1)
    return str(order_id)


def _generate_orderids(order_id_number):
    """

    :param order_id_number:
    :return: can_use_order_ids: set() 其中len(can_use_order_ids) 大于等于 order_id_number (多分配的也不要浪费)
    """
    assert order_id_number > 0

    can_use_order_ids = set()

    while len(can_use_order_ids) < order_id_number:
        need_order_numbers = order_id_number - len(can_use_order_ids)
        numbers = need_order_numbers * 2

        new_order_ids = _get_can_use_order_ids(numbers)
        #  因为倾向于多分配id(目前是需要的两倍)，减少因为id冲突导致需要重新分配，所以可能会出现成功分配的id比需要使用的更多
        can_use_order_ids |= new_order_ids

    return can_use_order_ids


def _get_can_use_order_ids(numbers):
    order_ids = set()
    while len(order_ids) < numbers:
        while len(order_ids) < numbers:
            new_order_id = _generate_order_id(ORDER_ID_LEN)
            order_ids.add(new_order_id)

        order_ids_in_db = set(Order.objects.filter(id__in=order_ids).values_list('id', flat=True))
        order_ids -= order_ids_in_db

    return order_ids


def _create_order(service, itemkey, points, postscript, user,
                  source=ORDER_SOURCE.UNKNOW, service_mode=None, coupon_deduction=0, points_deduction=0,
                  seckill_service=None, is_doctor_see=False):
    # create a order object, but DO NOT SAVE IT
    order_id = _generate_order_id(ORDER_ID_LEN)
    order = Order(id=order_id, user=user, service=service, source=source)
    order.is_stage = order.service.is_stage
    order.payment = service.total_price - points_deduction
    order.real_payment = service.total_price - points_deduction
    order.points_deduction = points_deduction
    order.coupon_deduction = coupon_deduction
    order.is_vouchsafe = service.is_vouchsafe
    order.is_doctor_see = is_doctor_see
    service_snapshot = service.snapshot

    servicesnapshot = ServiceSnapshot.objects.create(service=service, order=order)
    order_extra = OrderExtra.objects.create(order=order)

    # 每个美购至少有一个serviceitem, cash_back_rate以item中的值为准
    if itemkey is None or not service.is_multiattribute:
        item = ServiceItem.objects.filter(service_id=service.id, is_delete=False)
        # is_multiattribute 为 0 的美购 只有一个对应的serviceitem
        if len(item) > 1:
            return gen(CODES.SERVICE_NOT_FOUND)

        cash_back_rate = item.first().cash_back_rate
    else:
        cash_back_rate = ServiceItem.objects.get(key=itemkey, service_id=service.id, is_delete=False).cash_back_rate

    # 经家俊确认 秒杀没有多属性
    if service.is_multiattribute:
        if itemkey is None:
            raise MultiattributeServiceException
        item = ServiceItem.objects.get(key=itemkey, service_id=service.id, is_delete=False)
        order.payment = item.pre_payment_price - points_deduction
        order.real_payment = item.pre_payment_price - points_deduction
        price = item.gengmei_price
        order.service_price = price
        # ........................Delete........................................
        service_snapshot['items'] = item.items_name
        service_snapshot['item_key'] = itemkey
        service_snapshot['price'] = price
        service_snapshot['discount'] = item.discount
        service_snapshot['pre_payment_price'] = item.pre_payment_price
        service_snapshot['total_price'] = item.pre_payment_price
        service_snapshot['original_price'] = item.original_price
        service_snapshot['gengmei_price'] = price
        service_snapshot['cash_back_fee'] = item.cash_back_fee
        service_snapshot['self_support_discount'] = item.self_support_discount
        # .......................Delete.........................................

        servicesnapshot.items = repr(item.items_name)
        servicesnapshot.item_key = itemkey
        servicesnapshot.price = price
        servicesnapshot.discount = item.discount
        servicesnapshot.pre_payment_price = item.pre_payment_price
        servicesnapshot.total_price = item.pre_payment_price
        servicesnapshot.original_price = item.original_price
        servicesnapshot.gengmei_price = price
        servicesnapshot.cash_back_fee = item.cash_back_fee
        servicesnapshot.self_support_discount = item.self_support_discount

        servicesnapshot.max_gengmei_price = service.get_max_gengmei_price()
        # ......................Replace.......................................

        order.service_item_key = item.key
        doctor_seckill_apply = item.get_doctor_seckill_apply()
        if service_mode == SERVICE_MODE.SECKILL and seckill_service:
            # 使用秒杀专题下的秒杀价格
            order.payment = seckill_service.pre_payment_price - points_deduction
            order.real_payment = seckill_service.pre_payment_price - points_deduction
            order.service_price = seckill_service.seckill_price
            # ..........................Delete.........................................
            service_snapshot['discount'] = seckill_service.commission
            service_snapshot['pre_payment_price'] = seckill_service.pre_payment_price
            service_snapshot['is_seckill'] = True
            service_snapshot['specialseckillservice_id'] = seckill_service.id
            # ...........................Delete.......................
            servicesnapshot.discount = seckill_service.commission
            servicesnapshot.pre_payment_price = seckill_service.pre_payment_price
            servicesnapshot.is_seckill = True
            servicesnapshot.specialseckillservice_id = seckill_service.id
            # ...........................Replace..........................

            cash_back_rate = 0  # 秒杀不可返现
            service_snapshot['share_get_cashback'] = False
            servicesnapshot.share_get_cashback = False
            servicesnapshot.cash_back_fee = 0
        elif doctor_seckill_apply:
            order.payment = doctor_seckill_apply.pre_payment_price - points_deduction
            order.real_payment = doctor_seckill_apply.pre_payment_price - points_deduction
            order.service_price = doctor_seckill_apply.seckill_price
            # ..........................Delete.........................................
            service_snapshot['discount'] = doctor_seckill_apply.commission
            service_snapshot['pre_payment_price'] = doctor_seckill_apply.pre_payment_price
            service_snapshot['doctor_seckill_apply_id'] = doctor_seckill_apply.id
            # ...........................Delete.......................
            servicesnapshot.discount = doctor_seckill_apply.commission
            servicesnapshot.pre_payment_price = doctor_seckill_apply.pre_payment_price
            servicesnapshot.doctor_seckill_apply = doctor_seckill_apply
            # ...........................Replace..........................
            if not doctor_seckill_apply.decrease_available_num():
                gen(CODES.SERVICE_SOLD_OUT)
    else:
        # service.can_sell_items已经失效 这里保留只是为了参考
        item = service.can_sell_items()[0]
        order.payment = item.pre_payment_price - points_deduction
        order.real_payment = item.pre_payment_price - points_deduction
        price = item.gengmei_price
        order.service_price = price
        # ........................Delete........................................
        service_snapshot['items'] = item.items_name
        service_snapshot['item_key'] = itemkey
        service_snapshot['price'] = price
        service_snapshot['discount'] = item.discount
        service_snapshot['pre_payment_price'] = item.pre_payment_price
        service_snapshot['total_price'] = item.pre_payment_price
        service_snapshot['original_price'] = item.original_price
        service_snapshot['gengmei_price'] = price
        service_snapshot['cash_back_fee'] = item.cash_back_fee
        service_snapshot['self_support_discount'] = item.self_support_discount
        # .......................Delete.........................................

        servicesnapshot.items = repr(item.items_name)
        servicesnapshot.item_key = itemkey
        servicesnapshot.price = price
        servicesnapshot.discount = item.discount
        servicesnapshot.pre_payment_price = item.pre_payment_price
        servicesnapshot.total_price = item.pre_payment_price
        servicesnapshot.original_price = item.original_price
        servicesnapshot.gengmei_price = price
        servicesnapshot.cash_back_fee = item.cash_back_fee
        servicesnapshot.self_support_discount = item.self_support_discount

        servicesnapshot.max_gengmei_price = service.get_max_gengmei_price()
        # ......................Replace.......................................

        order.service_item_key = item.key
        doctor_seckill_apply = item.get_doctor_seckill_apply()
        if service_mode == SERVICE_MODE.SECKILL and seckill_service:
            # 使用秒杀专题下的秒杀价格
            order.payment = seckill_service.pre_payment_price - points_deduction
            order.real_payment = seckill_service.pre_payment_price - points_deduction
            order.service_price = seckill_service.seckill_price
            # ..........................Delete.........................................
            service_snapshot['discount'] = seckill_service.commission
            service_snapshot['pre_payment_price'] = seckill_service.pre_payment_price
            service_snapshot['is_seckill'] = True
            service_snapshot['specialseckillservice_id'] = seckill_service.id
            # ...........................Delete.......................
            servicesnapshot.discount = seckill_service.commission
            servicesnapshot.pre_payment_price = seckill_service.pre_payment_price
            servicesnapshot.is_seckill = True
            servicesnapshot.specialseckillservice_id = seckill_service.id
            # ...........................Replace..........................

            cash_back_rate = 0  # 秒杀不可返现
            service_snapshot['share_get_cashback'] = False
            servicesnapshot.share_get_cashback = False
            servicesnapshot.cash_back_fee = 0
        elif doctor_seckill_apply:
            order.payment = doctor_seckill_apply.pre_payment_price - points_deduction
            order.real_payment = doctor_seckill_apply.pre_payment_price - points_deduction
            order.service_price = doctor_seckill_apply.seckill_price
            # ..........................Delete.........................................
            service_snapshot['discount'] = doctor_seckill_apply.commission
            service_snapshot['pre_payment_price'] = doctor_seckill_apply.pre_payment_price
            service_snapshot['doctor_seckill_apply_id'] = doctor_seckill_apply.id
            # ...........................Delete.......................
            servicesnapshot.discount = doctor_seckill_apply.commission
            servicesnapshot.pre_payment_price = doctor_seckill_apply.pre_payment_price
            servicesnapshot.doctor_seckill_apply = doctor_seckill_apply
            # ...........................Replace..........................
            if not doctor_seckill_apply.decrease_available_num():
                gen(CODES.SERVICE_SOLD_OUT)

    # 如果支持分享返现,则在描述里面加一条文字
    if service.share_get_cashback is True:
        origin_desc = servicesnapshot.detail_description
        cash_back_desc = u'免责声明：因平台返现比例调整，返现规则以购买须知为准。'
        new_desc = u'{}\n{}'.format(origin_desc, cash_back_desc)
        servicesnapshot.detail_description = new_desc

    # add by lipeng hera_588
    order.discount = servicesnapshot.self_support_discount if servicesnapshot.is_self_support else \
        servicesnapshot.discount
    order.is_self_support = servicesnapshot.is_self_support
    # end

    # 更美价 减 美分 美券
    real_pay = order.service_price - order.coupon_deduction - order.points_deduction

    # 双十一需求， 双十一专场 抽成为实际支付金额
    is_special, special = service.is_unusual_special()
    if is_special and service_mode != SERVICE_MODE.SECKILL:
        order.discount = order.payment - order.coupon_deduction
        servicesnapshot.special = special
        servicesnapshot.special_active = True
        order_extra.can_refund = False

        # 用户返美分金额：返美分的金额=用户实际付款的10%
        order_extra.get_points = int(real_pay * settings.POINTS_PER_YUAN / 10)
    servicesnapshot.save()

    # order.cash_back_fee = math.ceil(order.service_price * cash_back_rate / 100.0)
    order.cash_back_fee = math.floor(real_pay * cash_back_rate / 100.0)
    # only free payment and gift exchange will has this field set
    if service.payment_type in (PAYMENT_TYPE.FREE_PAYMENT, PAYMENT_TYPE.EXCHANGE_GIFT):
        if service.exchange_points_ceiling > points:
            # 可用积分少于最多可抵用积分，则所有积分全部抵用
            points_used_amount = points
        else:
            # 可用积分多于最多可抵用积分，则抵用数为最多最多可抵用积分
            points_used_amount = service.exchange_points_ceiling

        point.remove(
            user_id=user.id,
            reason=POINTS_TYPE.BUY_SERVICE,
            point=points_used_amount,
            order_id=order.id
        )
        order.points = points_used_amount

    order.postscript = postscript
    order.service_mode = service_mode

    user_extra = get_user_extra_by_user_id(user.id)
    if user_extra:
        order.name = user_extra.name
        order.phone = user_extra.phone
        order.address = user_extra.address
    if service_snapshot.get('richtext'):
        cash_back_desc = u'<div style="color:red">免责声明：因平台返现比例调整，返现规则以购买须知为准。</div>'
        origin_rich_text = service_snapshot.get('richtext')
        service_snapshot['richtext'] = u'{}{}'.format(origin_rich_text, cash_back_desc)
    else:
        cash_back_desc = u'免责声明：因平台返现比例调整，返现规则以购买须知为准。'
        origin_rich_text = service_snapshot.get('detail_description')
        service_snapshot['detail_description'] = u'{}{}'.format(origin_rich_text, cash_back_desc)
    order.service_snapshot = json.dumps(service_snapshot)
    order_extra.save()
    order.save()
    return order


def _create_order_v2(service, service_item_obj, points, user, source=ORDER_SOURCE.UNKNOW,
                     coupon_info_id=None, coupon_deduction=0, points_deduction=0, is_doctor_see=False, order_id=None):
    new_order_id = _generate_order_id(ORDER_ID_LEN)

    while True:
        is_order_id_exists = Order.objects.filter(id=new_order_id).exists()
        if is_order_id_exists:
            new_order_id = _generate_order_id(ORDER_ID_LEN)
        else:
            break

    order = Order(id=new_order_id, user=user, service=service, source=source)
    order.is_stage = order.service.is_stage

    order.points_deduction = points_deduction
    order.coupon_deduction = 0          # 预付款券抵扣金额
    order.is_vouchsafe = service.is_vouchsafe
    order.is_doctor_see = is_doctor_see
    service_snapshot = service.snapshot

    servicesnapshot = ServiceSnapshot.objects.create(service=service, order=order)

    order.payment = 0
    order.real_payment = 0

    service_snapshot['service_type'] = service.service_type
    servicesnapshot.service_type = service.service_type

    servicesnapshot.cost_price = service_item_obj.cost_price

    current_price_info = service_item_obj.get_current_price_info()

    servicesnapshot.total_num = service_item_obj.total_num
    servicesnapshot.sell_num_limit = service_item_obj.sku_stock

    order.payment = service_item_obj.pre_payment_price - points_deduction
    order.real_payment = service_item_obj.pre_payment_price - points_deduction

    if coupon_deduction and coupon_info_id:
        coupon_info = CouponInfo.objects.select_related('coupon').get(pk=coupon_info_id)
        coupon_info_dict = coupon_info.coupon_info_data()
        # if coupon used, add payment_without_coupon, coupon_value, coupon_name
        # into snapshot
        service_snapshot['payment_without_coupon'] = order.payment
        servicesnapshot.payment_without_coupon = order.payment

        # 医生券抵扣
        doctor_coupon_deduction = 0
        if coupon_info.coupon.coupon_type == COUPON_TYPES.PLATFORM:
            order.payment = order.payment - coupon_deduction
            order.real_payment = order.real_payment - coupon_deduction
            order.coupon_deduction = coupon_deduction

            if order.payment < 0:
                order.payment = 0
            if order.real_payment < 0:
                order.real_payment = 0
        else:
            doctor_coupon_deduction = coupon_deduction

        service_snapshot['coupon_value'] = order.coupon_deduction
        service_snapshot['coupon_name'] = coupon_info_dict['name']
        service_snapshot['coupon_id'] = coupon_info_dict['id']

        servicesnapshot.coupon_value = order.coupon_deduction
        servicesnapshot.coupon_name = coupon_info_dict['name']
        servicesnapshot.coupon_info_id = coupon_info_dict['id']
        servicesnapshot.coupon_type = coupon_info_dict['coupon_type']

        # 美券更美承担比例的数据由于会通过新的API读取，因此不需要同步写入到order的快照json中去
        coupon = coupon_info.coupon
        coupon_id = coupon.id
        servicesnapshot.coupon_id = coupon_id

        coupon_gengmei_value = 0
        coupon_doctor_value = 0
        gengmei_percent = coupon.gengmei_percent
        assert 0 <= gengmei_percent <= 100
        if coupon_info.coupon.coupon_type == COUPON_TYPES.PLATFORM:
            coupon_gengmei_value = int(math.floor(coupon_deduction * gengmei_percent / 100.00))
            coupon_doctor_value = coupon_deduction - coupon_gengmei_value

        assert coupon_gengmei_value >= 0
        assert coupon_doctor_value >= 0

        servicesnapshot.coupon_gengmei_percent = gengmei_percent
        servicesnapshot.coupon_gengmei_value = coupon_gengmei_value
        servicesnapshot.coupon_doctor_value = coupon_doctor_value

        servicesnapshot.coupon_info = json.dumps({
            'coupon_id': coupon_info.coupon.id,
            'coupon_value': order.coupon_deduction,
            'coupon_name': coupon_info_dict['name'],
            'coupon_info_id': coupon_info_dict['id'],
            'coupon_type': coupon_info_dict['coupon_type'],
            'coupon_gengmei_percent': gengmei_percent,
            'coupon_gengmei_value': coupon_gengmei_value,
            'coupon_doctor_value': coupon_doctor_value,
            'doctor_coupon_deduction': doctor_coupon_deduction,
            'payment_without_coupon': servicesnapshot.payment_without_coupon,
        })

    price = service_item_obj.gengmei_price
    order.service_price = price

    # ........................Delete........................................
    service_snapshot['items'] = service_item_obj.items_name
    service_snapshot['item_key'] = service_item_obj.key
    service_snapshot['price'] = price
    service_snapshot['discount'] = service_item_obj.discount
    service_snapshot['pre_payment_price'] = service_item_obj.pre_payment_price
    service_snapshot['total_price'] = service_item_obj.pre_payment_price
    service_snapshot['original_price'] = service_item_obj.original_price
    service_snapshot['gengmei_price'] = price
    service_snapshot['self_support_discount'] = service_item_obj.self_support_discount
    # .......................Delete.........................................

    servicesnapshot.items = repr(service_item_obj.items_name)
    servicesnapshot.item_key = service_item_obj.key
    servicesnapshot.price = price
    servicesnapshot.discount = service_item_obj.discount
    servicesnapshot.pre_payment_price = service_item_obj.pre_payment_price
    servicesnapshot.total_price = service_item_obj.pre_payment_price
    servicesnapshot.original_price = service_item_obj.original_price
    servicesnapshot.gengmei_price = price
    servicesnapshot.self_support_discount = service_item_obj.self_support_discount

    servicesnapshot.max_gengmei_price = 0
    # ......................Replace.......................................

    order.service_item_key = service_item_obj.key
    order.service_item_id = service_item_obj.id
    order.service_item_price_id = current_price_info.get('id')

    cash_back_rate = service_item_obj.cash_back_rate
    cash_back_fee = service_item_obj.cash_back_fee

    if 'selling_rule' in current_price_info and 'share_get_cashback' in current_price_info['selling_rule']:
        share_get_cashback = current_price_info['selling_rule']['share_get_cashback']

        servicesnapshot.share_get_cashback = share_get_cashback
        service_snapshot['share_get_cashback'] = share_get_cashback

        if not share_get_cashback:
            cash_back_rate = 0
            cash_back_fee = 0

    # 现在虽然记录了cash_back_fee，但是实际order的cash_back_fee是根据cash_back_rate计算的
    service_snapshot['cash_back_fee'] = cash_back_fee
    servicesnapshot.cash_back_fee = cash_back_fee
    # 更美价 减 美分 美券
    real_pay = order.service_price - order.coupon_deduction - order.points_deduction
    # order.cash_back_fee = math.ceil(order.service_price * cash_back_rate / 100.0)
    order.cash_back_fee = math.floor(real_pay * cash_back_rate / 100.0)

    # add by lipeng hera_588
    order.discount = servicesnapshot.self_support_discount if servicesnapshot.is_self_support else \
        servicesnapshot.discount
    order.is_self_support = servicesnapshot.is_self_support
    # end

    servicesnapshot.price_info = _json_dump_current_price_info(current_price_info)

    # only free payment and gift exchange will has this field set
    if service.payment_type in (PAYMENT_TYPE.FREE_PAYMENT, PAYMENT_TYPE.EXCHANGE_GIFT):
        if service.exchange_points_ceiling > points:
            # 可用积分少于最多可抵用积分，则所有积分全部抵用
            points_used_amount = points
        else:
            # 可用积分多于最多可抵用积分，则抵用数为最多最多可抵用积分
            points_used_amount = service.exchange_points_ceiling

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

        order.points = points_used_amount

    order.postscript = ''

    is_default_price = current_price_info.get('is_default_price')

    if 'selling_rule' in current_price_info:
        rule = current_price_info['selling_rule']
        order.activity_id = rule['activity_id']
        order.activity_type = rule['activity_type']

    order.service_mode = SERVICE_MODE.NORMAL if is_default_price else SERVICE_MODE.ACTIVITY

    user_extra = get_user_extra_by_user_id(user.id)
    if user_extra:
        order.name = user_extra.name
        order.phone = user_extra.phone
        order.address = user_extra.address
    cash_back_desc = u'<div style="color:red">免责声明：因平台返现比例调整，返现规则以购买须知为准。</div>'
    origin_rich_text = service_snapshot.get('richtext')
    rich_text = u'{}{}'.format(origin_rich_text, cash_back_desc)

    service_snapshot['richtext'] = rich_text
    servicesnapshot.rich_text = rich_text

    order.service_snapshot = json.dumps(service_snapshot)

    assert order.payment > 0
    assert order.real_payment > 0

    # 先save order,成功了再save其他的
    order.save()
    servicesnapshot.save()

    return order


def bulk_create_order_and_service_snapshot(to_create_order_info_chunk):
    batch_size = len(to_create_order_info_chunk)

    can_use_order_ids_set = set()

    max_retry_count = 9

    while max_retry_count > 0:
        while len(can_use_order_ids_set) < batch_size:
            new_can_use_order_ids_set = set(
                _generate_orderids(batch_size - len(can_use_order_ids_set)))
            can_use_order_ids_set |= new_can_use_order_ids_set

        order_ids = list(sorted((can_use_order_ids_set.pop() for i in range(batch_size)), key=lambda k: int(k)))

        to_insert_orders = []
        to_insert_orderextras = []
        to_insert_orderinfos = []
        to_insert_servicesnapshots = []
        to_insert_ordercouponinfos = []

        for i in range(batch_size):
            info = to_create_order_info_chunk[i]
            oid = order_ids[i]
            o = info['api_order_dict']
            promotion_id = o.pop('promotion_id', 0)
            if promotion_id:
                extra_info = {
                    'promotion_id': promotion_id,
                    'promotion_type': o.pop('promotion_type')
                }
                to_insert_orderextras.append(_create_order_extra_model(oid, extra_info))

            to_insert_orders.append(_create_order_model(oid, o))

            oi = info['api_orderinfo_dict']
            to_insert_orderinfos.append(_create_orderinfo_model(oid,oi))

            ss = info['pay_servicesnapshot_dict']
            to_insert_servicesnapshots.append(_create_service_snapshot_model(oid, ss))

            oci_list = info['api_ordercouponinfo_dict_list']
            for oci in oci_list:
                to_insert_ordercouponinfos.append(_create_ordercouponinfo_model(oid, oci))

        try:
            with transaction.atomic():
                Order.objects.bulk_create(to_insert_orders)
                OrderExtra.objects.bulk_create(to_insert_orderextras)
                OrderInfo.objects.bulk_create(to_insert_orderinfos)
                ServiceSnapshot.objects.bulk_create(to_insert_servicesnapshots)
                OrderCouponInfo.objects.bulk_create(to_insert_ordercouponinfos)
                return to_insert_orders
        except IntegrityError:
            max_retry_count -= 1
            logging_exception()

    return gen(CODES.SETTLEMENT_CREATE_FAIL)


_datetime_json_format = '%Y-%m-%d %H:%M:%S'


def _create_order_model(order_id, order_obj_dict):
    o = Order()
    o.id = order_id

    for k, v in order_obj_dict.items():
        setattr(o, k, v)

    return o


def _create_order_extra_model(order_id, order_extra_obj_dict):
    oe = OrderExtra()
    oe.order_id = order_id

    for k, v in order_extra_obj_dict.items():
        setattr(oe, k, v)

    return oe


def _create_orderinfo_model(order_id, orderinfo_obj_dict):
    oi = OrderInfo()
    oi.order_id = order_id

    for k, v in orderinfo_obj_dict.items():
        setattr(oi, k, v)

    return oi


def _create_ordercouponinfo_model(order_id, ordercouponinfo_dict):
    oci = OrderCouponInfo()
    oci.order_id = order_id

    for k, v in ordercouponinfo_dict.items():
        setattr(oci, k, v)

    return oci


def _create_service_snapshot_model(order_id, service_snapshot_obj_dict):
    ss = ServiceSnapshot()
    ss.order_id = order_id

    start_time_str = service_snapshot_obj_dict.pop('start_time', None)
    end_time_str = service_snapshot_obj_dict.pop('end_time', None)
    update_time_str = service_snapshot_obj_dict.pop('update_time', None)

    ss.start_time = datetime.datetime.strptime(start_time_str, _datetime_json_format) if start_time_str else None
    ss.end_time = datetime.datetime.strptime(end_time_str, _datetime_json_format) if end_time_str else None
    ss.update_time = datetime.datetime.strptime(update_time_str, _datetime_json_format) if update_time_str else None

    for k, v in service_snapshot_obj_dict.items():
        setattr(ss, k, v)

    return ss


def _create_snapshot_obj_dict(service_obj):
    # from ServiceSnapshotManager.create
    service_id = service_obj.id

    start_time = service_obj.start_time.strftime(_datetime_json_format) if service_obj.start_time else None
    end_time = service_obj.end_time.strftime(_datetime_json_format) if service_obj.end_time else None
    update_time = service_obj.update_time.strftime(_datetime_json_format) if service_obj.update_time else None

    doctor_id = service_obj.doctor.id if service_obj.doctor else None
    business_partener_id = service_obj.doctor.business_partener.id if service_obj.doctor and service_obj.doctor.business_partener else None

    hospital_id = service_obj.hospital.id if service_obj.hospital else None
    bodypart_subitem_id = service_obj.bodypart_subitem.id if service_obj.bodypart_subitem else None
    wiki_id = service_obj.wiki.id if service_obj.wiki else None

    service_snapshot_obj_dict = {
        'service_id': service_id,  # diff
        'name': service_obj.name,
        'short_description': service_obj.short_description,
        'detail_description': service_obj.detail_description,
        'exchange_points_ceiling': service_obj.exchange_points_ceiling,
        'doctor_id': doctor_id,  # diff
        'hospital_id': hospital_id,  # diff
        'special_remind': service_obj.special_remind,
        'ceiling_price': service_obj.ceiling_price,
        'payment_type': service_obj.payment_type,
        'channel': service_obj.channel,
        'service_flag': service_obj.service_flag,
        'phone': service_obj.phone,
        'sms_phone': service_obj.sms_phone,
        'pm_content': service_obj.pm_content,
        'address': service_obj.address,
        'is_online': service_obj.is_online,
        'is_sale': service_obj.is_sale,
        'is_voucher': service_obj.is_voucher,
        'ordering': service_obj.ordering,
        'total_num': service_obj.total_num,
        'start_time': start_time,  # diff
        'end_time': end_time,  # diff
        'update_time': update_time,  # diff
        'only_use_points': service_obj.only_use_points,
        'single_user_buy_limit': service_obj.single_user_buy_limit,
        'need_address': service_obj.need_address,
        'need_sms_alert': service_obj.need_sms_alert,
        'bodypart_subitem_id': bodypart_subitem_id,  # diff
        'image_header': service_obj.image_header,
        'image_detail': service_obj.image_detail,
        'is_multiattribute': service_obj.is_multiattribute,
        'share_get_cashback': service_obj.share_get_cashback,
        'refund_anytime': service_obj.refund_anytime,
        'compensation_in_advance': service_obj.compensation_in_advance,
        'cash_back_rate': service_obj.cash_back_rate,
        'fake_sold_num': service_obj.fake_sold_num,
        'notes': service_obj.notes,
        'show_location': service_obj.show_location,
        'tip': service_obj.tip,
        'rating': service_obj.rating,
        'operation_effect_rating': service_obj.operation_effect_rating,
        'doctor_attitude_rating': service_obj.doctor_attitude_rating,
        'hospital_env_rating': service_obj.hospital_env_rating,
        'is_operation': service_obj.is_operation,
        'is_vouchsafe': service_obj.is_vouchsafe,
        'wiki_id': wiki_id,  # diff
        'is_stage': service_obj.is_stage,
        'reservation': service_obj.reservation,
        'valid_duration': service_obj.valid_duration,
        'points_deduction_percent': service_obj.points_deduction_percent,
        'recommend_usecase': service_obj.recommend_usecase,
        'is_self_support': service_obj.is_self_support,
        'business_partener_id': business_partener_id,  # diff
        'self_support_discount': service_obj.self_support_discount,
        'discount': service_obj.discount,
        'pre_payment_price': service_obj.pre_payment_price,
        'total_price': service_obj.total_price,
        'original_price': service_obj.original_price,
        'gengmei_price': service_obj.gengmei_price,
        'max_gengmei_price': service_obj.gengmei_price,
        'is_floor_price': service_obj.is_floor_price,
    }
    return service_snapshot_obj_dict


def _get_ordercouponinfo_dict(coupon_deduction, coupon_and_coupon_info_dict):
    coupon_type = coupon_and_coupon_info_dict['coupon_type']

    platform_coupon_deduction = 0
    gengmei_percent = coupon_and_coupon_info_dict['gengmei_percent']
    assert 0 <= gengmei_percent <= 100

    coupon_gengmei_value = 0
    coupon_doctor_value = 0

    doctor_coupon_deduction = 0

    if coupon_type == COUPON_TYPES.PLATFORM:
        platform_coupon_deduction = coupon_deduction
        coupon_gengmei_value = int(math.floor(coupon_deduction * gengmei_percent / 100.00))
        coupon_doctor_value = coupon_deduction - coupon_gengmei_value

    if coupon_type == COUPON_TYPES.DOCTOR:
        doctor_coupon_deduction = coupon_deduction

    assert coupon_gengmei_value >= 0
    assert coupon_doctor_value >= 0

    ordercouponinfo = {
        'coupon_id': coupon_and_coupon_info_dict['coupon_id'],
        'coupon_info_id': coupon_and_coupon_info_dict['coupon_info_id'],
        'coupon_name': coupon_and_coupon_info_dict['name'],
        'coupon_value': coupon_and_coupon_info_dict['value'],
        'coupon_type': coupon_type,

        'platform_coupon_deduction': platform_coupon_deduction,
        'coupon_gengmei_percent': gengmei_percent,
        'coupon_gengmei_value': coupon_gengmei_value,
        'coupon_doctor_value': coupon_doctor_value,

        'doctor_coupon_deduction': doctor_coupon_deduction,
    }

    return ordercouponinfo


def _create_order_v3(service_obj, service_item_ordering_dict, ordering_price_info, user_id, source=ORDER_SOURCE.UNKNOW,
                     coupon_deduction=0, points_deduction=0, is_doctor_see=False,
                     user_extra_dict=None, coupon_and_coupon_info_dict=None, invite_code=None,
                     groupbuy_team_id=None,
                     service_id_to_tag_ids=None,
                     platform_coupon_deduction=0, platform_coupon_and_coupon_info_dict=None,
                     doctor_coupon_deduction=0, doctor_coupon_and_coupon_info_dict=None,
                     promotion_info=None,
                     ):
    '''
    promotion_info : {
        'promotion_id': int,
        'promotion_type': string,
        'promotion_discount': float,
        'advance_discount': float,
        'final_discount': float,
        'advance_payment': float,
        'final_payment': float,
    }
    '''
    order_obj_dict = {
        'user_id': user_id,
        'service_id': service_obj.id,
        'source': source,
        'is_stage': service_obj.is_stage,
        'points_deduction': points_deduction,
        'coupon_deduction': 0,  # coupon_deduction
        'is_vouchsafe': service_obj.is_vouchsafe,
        'is_doctor_see': is_doctor_see,
    }

    # 这个是order上面的json快照，之后计划要被废弃的
    service_snapshot = service_obj.snapshot

    service_snapshot_obj_dict = _create_snapshot_obj_dict(service_obj)

    image_url = service_item_ordering_dict["image_url"]

    service_snapshot["image_header"] = image_url
    service_snapshot_obj_dict["image_header"] = image_url

    name = "【{}】{}".format(
        service_item_ordering_dict["project_type_name"], service_obj.short_description
    )

    service_snapshot['name'] = name
    service_snapshot_obj_dict["name"] = name

    service_snapshot_obj_dict['invite_code'] = invite_code or ''

    service_snapshot['service_type'] = service_obj.service_type
    service_snapshot_obj_dict['service_type'] = service_obj.service_type

    service_snapshot_obj_dict['cost_price'] = service_item_ordering_dict["cost_price"]

    price_info = ordering_price_info
    original_price = price_info["original_price"]
    self_support_discount = price_info["self_support_discount"]
    cash_back_rate = price_info["cash_back_rate"]
    cash_back_fee = price_info["cash_back_fee"]
    if promotion_info:
        pre_payment_price = promotion_info["advance_payment"]
        gengmei_price = promotion_info["advance_payment"] + promotion_info["final_payment"]
        discount = promotion_info["commission"]
        service_price = promotion_info["activity_price"]
        total_price = promotion_info["activity_price"]
        # 为了处理 活动结算单的问题
        promotion_advance_payment = promotion_info["promotion_advance_payment"]
        promotion_final_payment = promotion_info["promotion_final_payment"]
    else:
        pre_payment_price = price_info["pre_payment_price"]
        gengmei_price = price_info["gengmei_price"]
        discount = price_info["discount"]
        service_price = price_info["gengmei_price"]
        total_price = price_info["pre_payment_price"]
        promotion_advance_payment = price_info["pre_payment_price"]
        promotion_final_payment = price_info["gengmei_price"] - pre_payment_price

    join_groupbuy_team_id = None
    groupbuy_status = GROUPBUY_STATUS.NOT_GROUPBUY

    if groupbuy_team_id:
        #  参团
        join_groupbuy_team_id = groupbuy_team_id
        groupbuy_status = GROUPBUY_STATUS.GROUPBUY_STARTED
    elif 'selling_rule' in price_info and 'activity_type' in price_info['selling_rule'] \
            and price_info['selling_rule']['activity_type'] == ACTIVITY_TYPE_ENUM.GROUPBUY:
        #  开团
        groupbuy_status = GROUPBUY_STATUS.GROUPBUY_NOT_STARTED
    elif promotion_info.get('promotion_type') == AllPromotionType.GROUP_BOOK:
        groupbuy_status = GROUPBUY_STATUS.GROUPBUY_NOT_STARTED

    order_obj_dict["groupbuy_status"] = groupbuy_status
    order_obj_dict["join_groupbuy_team_id"] = join_groupbuy_team_id

    service_snapshot_obj_dict['total_num'] = service_item_ordering_dict["sku_stock"]
    service_snapshot_obj_dict['sell_num_limit'] = service_item_ordering_dict["sku_stock"]

    order_obj_payment = pre_payment_price - points_deduction
    order_obj_real_payment = pre_payment_price - points_deduction

    service_snapshot['payment_without_coupon'] = order_obj_payment
    service_snapshot_obj_dict['payment_without_coupon'] = order_obj_payment

    ordercouponinfo_dict_list = []

    if platform_coupon_deduction > 0 or doctor_coupon_deduction > 0:
        if platform_coupon_deduction > 0:
            ordercouponinfo_dict = _get_ordercouponinfo_dict(
                platform_coupon_deduction, platform_coupon_and_coupon_info_dict
            )
            order_obj_payment -= platform_coupon_deduction
            order_obj_real_payment -= platform_coupon_deduction
            order_obj_dict['coupon_deduction'] = platform_coupon_deduction
            ordercouponinfo_dict_list.append(ordercouponinfo_dict)

        if doctor_coupon_deduction > 0:
            ordercouponinfo_dict = _get_ordercouponinfo_dict(
                doctor_coupon_deduction, doctor_coupon_and_coupon_info_dict
            )
            ordercouponinfo_dict_list.append(ordercouponinfo_dict)

    elif coupon_deduction > 0 and coupon_and_coupon_info_dict:
        # if coupon used, add payment_without_coupon, coupon_value, coupon_name
        # into snapshot
        service_snapshot['payment_without_coupon'] = order_obj_payment
        service_snapshot_obj_dict['payment_without_coupon'] = order_obj_payment

        # 医生券抵扣
        doctor_coupon_deduction = 0
        if coupon_and_coupon_info_dict['coupon_type'] == COUPON_TYPES.PLATFORM:
            order_obj_payment -= coupon_deduction
            order_obj_real_payment -= coupon_deduction
            order_obj_dict['coupon_deduction'] = coupon_deduction

        else:
            doctor_coupon_deduction = coupon_deduction

        service_snapshot['coupon_value'] = order_obj_dict['coupon_deduction']
        service_snapshot['coupon_name'] = coupon_and_coupon_info_dict['name']
        service_snapshot['coupon_id'] = coupon_and_coupon_info_dict['coupon_info_id']

        service_snapshot_obj_dict['coupon_value'] = order_obj_dict['coupon_deduction']
        service_snapshot_obj_dict['coupon_name'] = coupon_and_coupon_info_dict['name']
        service_snapshot_obj_dict['coupon_info_id'] = coupon_and_coupon_info_dict['coupon_info_id']
        service_snapshot_obj_dict['coupon_type'] = coupon_and_coupon_info_dict['coupon_type']

        # 美券更美承担比例的数据由于会通过新的API读取，因此不需要同步写入到order的快照json中去
        service_snapshot_obj_dict['coupon_id'] = coupon_and_coupon_info_dict['coupon_id']

        coupon_gengmei_value = 0
        coupon_doctor_value = 0
        gengmei_percent = coupon_and_coupon_info_dict['gengmei_percent']
        assert 0 <= gengmei_percent <= 100
        if coupon_and_coupon_info_dict['coupon_type'] == COUPON_TYPES.PLATFORM:
            coupon_gengmei_value = int(math.floor(coupon_deduction * gengmei_percent / 100.00))
            coupon_doctor_value = coupon_deduction - coupon_gengmei_value

        assert coupon_gengmei_value >= 0
        assert coupon_doctor_value >= 0

        service_snapshot_obj_dict['coupon_gengmei_percent'] = gengmei_percent
        service_snapshot_obj_dict['coupon_gengmei_value'] = coupon_gengmei_value
        service_snapshot_obj_dict['coupon_doctor_value'] = coupon_doctor_value

        service_snapshot_obj_dict['coupon_info'] = json.dumps({
            'coupon_id': coupon_and_coupon_info_dict['coupon_id'],
            'coupon_value': order_obj_dict['coupon_deduction'],
            'coupon_name': coupon_and_coupon_info_dict['name'],
            'coupon_info_id': coupon_and_coupon_info_dict['coupon_info_id'],
            'coupon_type': coupon_and_coupon_info_dict['coupon_type'],
            'coupon_gengmei_percent': gengmei_percent,
            'coupon_gengmei_value': coupon_gengmei_value,
            'coupon_doctor_value': coupon_doctor_value,
            'doctor_coupon_deduction': doctor_coupon_deduction,
            'payment_without_coupon': service_snapshot_obj_dict['payment_without_coupon'],
        })

        #  旧的下单方式的券数据会写入新的地方
        platform_coupon_deduction = coupon_deduction if coupon_and_coupon_info_dict[
                                                            'coupon_type'] == COUPON_TYPES.PLATFORM else 0
        old_ordercouponinfo_dict = {
            'coupon_id': coupon_and_coupon_info_dict['coupon_id'],
            'coupon_info_id': coupon_and_coupon_info_dict['coupon_info_id'],
            'coupon_name': coupon_and_coupon_info_dict['name'],
            'coupon_value': coupon_and_coupon_info_dict['value'],
            'coupon_type': coupon_and_coupon_info_dict['coupon_type'],

            'platform_coupon_deduction': platform_coupon_deduction,
            'coupon_gengmei_percent': gengmei_percent,
            'coupon_gengmei_value': coupon_gengmei_value,
            'coupon_doctor_value': coupon_doctor_value,

            'doctor_coupon_deduction': doctor_coupon_deduction,
        }
        ordercouponinfo_dict_list.append(old_ordercouponinfo_dict)

    order_obj_dict['service_price'] = service_price

    # ........................Delete........................................
    service_snapshot['items'] = service_item_ordering_dict["items_name"]
    service_snapshot['item_key'] = ""  # 没有意义的字段，新的代码统一空字符串
    service_snapshot['price'] = gengmei_price
    service_snapshot['discount'] = discount
    service_snapshot['pre_payment_price'] = promotion_advance_payment
    service_snapshot['total_price'] = total_price
    service_snapshot['original_price'] = original_price
    service_snapshot['gengmei_price'] = promotion_advance_payment + promotion_final_payment
    service_snapshot['self_support_discount'] = self_support_discount
    # .......................Delete.........................................

    service_snapshot_obj_dict['items'] = json.dumps(service_item_ordering_dict["items_name"])
    service_snapshot_obj_dict['item_key'] = ""  # 没有意义的字段，新的代码统一空字符串
    service_snapshot_obj_dict['price'] = gengmei_price
    service_snapshot_obj_dict['discount'] = discount
    service_snapshot_obj_dict['pre_payment_price'] = promotion_advance_payment
    service_snapshot_obj_dict['total_price'] = total_price
    service_snapshot_obj_dict['original_price'] = original_price
    service_snapshot_obj_dict['gengmei_price'] = promotion_advance_payment + promotion_final_payment
    service_snapshot_obj_dict['self_support_discount'] = self_support_discount

    service_snapshot_obj_dict['max_gengmei_price'] = 0
    # ......................Replace.......................................

    hospital_payment = gengmei_price - pre_payment_price
    if doctor_coupon_deduction > 0:
        hospital_payment -= doctor_coupon_deduction

    assert hospital_payment >= 0

    order_obj_dict['service_item_key'] = ""  # 没有意义的字段，新的代码统一空字符串
    order_obj_dict['service_item_id'] = service_item_ordering_dict["id"]
    order_obj_dict['service_item_price_id'] = price_info['id']

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

        service_snapshot_obj_dict['share_get_cashback'] = share_get_cashback
        service_snapshot['share_get_cashback'] = share_get_cashback

        if not share_get_cashback:
            cash_back_rate = 0
            cash_back_fee = 0

    # 现在虽然记录了cash_back_fee，但是实际order的cash_back_fee是根据cash_back_rate计算的
    service_snapshot['cash_back_fee'] = cash_back_fee
    service_snapshot_obj_dict['cash_back_fee'] = cash_back_fee
    # 更美价 减 美分 美券
    real_pay = gengmei_price - order_obj_dict['coupon_deduction'] - points_deduction
    # order.cash_back_fee = math.ceil(order.service_price * cash_back_rate / 100.0)
    order_obj_dict['cash_back_fee'] = math.floor(real_pay * cash_back_rate / 100.0)

    # add by lipeng hera_588
    order_obj_dict['discount'] = service_snapshot_obj_dict['self_support_discount'] if service_snapshot_obj_dict[
        'is_self_support'] else \
        service_snapshot_obj_dict['discount']
    order_obj_dict['is_self_support'] = service_snapshot_obj_dict['is_self_support']
    # end

    current_price_info_json = _json_dump_current_price_info(price_info)

    service_snapshot_obj_dict['price_info'] = current_price_info_json

    order_obj_dict['postscript'] = ''

    is_default_price = price_info['is_default_price']

    if promotion_info:
        order_obj_dict['promotion_id'] = promotion_info.get('promotion_id')
        order_obj_dict['promotion_type'] = promotion_info.get('promotion_type')

    # 活动相关信息
    if 'selling_rule' in price_info:
        rule = price_info['selling_rule']
        order_obj_dict['activity_id'] = rule.get('activity_id')
        if promotion_info and promotion_info.get('promotion_type') == AllPromotionType.GROUP_BOOK:
            activity_type = ACTIVITY_TYPE_ENUM.GROUPBUY
        else:
            activity_type = rule['activity_type']
        order_obj_dict['activity_type'] = activity_type

    order_obj_dict['service_mode'] = SERVICE_MODE.NORMAL if is_default_price else SERVICE_MODE.ACTIVITY

    # 用户相关信息
    if user_extra_dict:
        order_obj_dict['name'] = user_extra_dict['name']
        order_obj_dict['phone'] = user_extra_dict['phone']
        order_obj_dict['address'] = user_extra_dict['address']
    cash_back_desc = u'<div style="color:red">免责声明：因平台返现比例调整，返现规则以购买须知为准。</div>'
    origin_rich_text = service_snapshot.get('richtext')
    rich_text = u'{}{}'.format(origin_rich_text, cash_back_desc)

    service_snapshot['richtext'] = rich_text
    service_snapshot_obj_dict['rich_text'] = rich_text

    order_obj_dict['service_snapshot'] = json.dumps(service_snapshot)

    # 订单支付金额、实际支付金额及抵扣
    order_obj_dict['payment'] = order_obj_payment
    order_obj_dict['real_payment'] = order_obj_real_payment

    # assert order_obj_dict['payment'] > 0
    # assert order_obj_dict['real_payment'] > 0

    online_item_tag_ids = []
    if service_id_to_tag_ids and service_obj.id in service_id_to_tag_ids and service_id_to_tag_ids[service_obj.id]:
        online_item_tag_ids = service_id_to_tag_ids[service_obj.id]

    online_item_tag_ids_json = json.dumps(online_item_tag_ids)

    create_order_info_dict = {
        'price_info': current_price_info_json,
        'online_item_tag_ids': online_item_tag_ids_json,
    }
    create_order_info_json = json.dumps(create_order_info_dict)

    orderinfo_obj_dict = {
        'create_order_info': create_order_info_json
    }

    return order_obj_dict, service_snapshot_obj_dict, orderinfo_obj_dict, ordercouponinfo_dict_list


def create(user, service, postscript, points, itemkey=None,
           source=ORDER_SOURCE.UNKNOW, coupon_info_id=None, service_mode=None,
           coupon_deduction=0, points_deduction=0, is_doctor_see=False):
    while True:
        try:
            with transaction.atomic():
                # 秒杀模式时 判断用户是否超过秒杀限额
                sss = None
                if service_mode == SERVICE_MODE.SECKILL:
                    order_exclude = Q(status=ORDER_STATUS.CANCEL)
                    # sss = SpecialSeckillService.fetch(service, itemkey)
                    service.is_item_seckill(itemkey)
                    sss = getattr(service, service.SECKILL_ITEM_OBJS, None)
                    order_num = Order.objects.filter(user=user, service=service, service_item_key=itemkey).exclude(order_exclude).count()
                    if sss.buy_up_to > 0 and order_num >= sss.buy_up_to:
                        raise gen(CODES.LIMITED)
                order = _create_order(
                    service, itemkey, points, postscript, user,
                    source, service_mode, coupon_deduction, points_deduction, sss, is_doctor_see,
                )
                order.save()
                if coupon_deduction and coupon_info_id:
                    coupon_info = CouponInfo.objects.get(pk=coupon_info_id)
                    coupon_info_dict = coupon_info.coupon_info_data()
                    # if coupon used, add payment_without_coupon, coupon_value, coupon_name
                    # into snapshot
                    service_snapshot = json.loads(order.service_snapshot)
                    service_snapshot['payment_without_coupon'] = order.payment

                    order.payment = order.payment - coupon_deduction
                    order.real_payment = order.real_payment - coupon_deduction
                    if order.payment < 0:
                        order.payment = 0

                    service_snapshot['coupon_value'] = coupon_deduction
                    service_snapshot['coupon_name'] = coupon_info_dict['name']
                    service_snapshot['coupon_id'] = coupon_info_dict['id']
                    order.service_snapshot = json.dumps(service_snapshot)
                    order.save()
                assert order.real_payment > 0
                break
        except IntegrityError:
            # order id
            logging_exception()
            order = None

        except CouponFrozenException:
            logging_exception()
            raise gen(CODES.USE_COUPON_FAIL)

    clean_discount(user.id, service.id)
    return order


def create_v2(user, service_obj, service_item_obj, points,
              source=ORDER_SOURCE.UNKNOW, coupon_info_id=None,
              coupon_deduction=0, points_deduction=0, is_doctor_see=False, order_id=None):
    while True:
        try:
            with transaction.atomic():
                order = _create_order_v2(
                    service_obj, service_item_obj, points, user,
                    source, coupon_info_id, coupon_deduction, points_deduction, is_doctor_see,
                    order_id,
                )

                break
        except IntegrityError:
            # order id
            logging_exception()
            order = None

        except CouponFrozenException:
            logging_exception()
            raise gen(CODES.USE_COUPON_FAIL)

    return order


def create_v3(user_id, service_obj, service_item_ordering_dict, ordering_price_info,
              source=ORDER_SOURCE.UNKNOW,
              coupon_deduction=0, points_deduction=0, is_doctor_see=False,
              user_extra_dict=None, coupon_and_coupon_info_dict=None, invite_code=None,
              service_id_to_tag_ids=None,
              platform_coupon_deduction=0, platform_coupon_and_coupon_info_dict=None,
              doctor_coupon_deduction=0, doctor_coupon_and_coupon_info_dict=None,
              groupbuy_team_id=None, promotion_info=None,
              ):
    order, servicesnapshot, order_info, ordercouponinfo_dict_list = _create_order_v3(
        service_obj, service_item_ordering_dict, ordering_price_info, user_id,
        source, coupon_deduction, points_deduction, is_doctor_see,
        user_extra_dict, coupon_and_coupon_info_dict, invite_code,
        groupbuy_team_id=groupbuy_team_id,
        service_id_to_tag_ids=service_id_to_tag_ids,
        platform_coupon_deduction=platform_coupon_deduction,
        platform_coupon_and_coupon_info_dict=platform_coupon_and_coupon_info_dict,
        doctor_coupon_deduction=doctor_coupon_deduction,
        doctor_coupon_and_coupon_info_dict=doctor_coupon_and_coupon_info_dict,
        promotion_info=promotion_info,
    )

    to_create_order_info = {
        'api_order_dict': order,
        'api_orderinfo_dict': order_info,
        'pay_servicesnapshot_dict': servicesnapshot,
        'api_ordercouponinfo_dict_list': ordercouponinfo_dict_list,
    }

    return to_create_order_info


def get_order_by_id(order_id):
    if not order_id:
        return None

    try:
        order = Order.objects.get(id=order_id)
        return order
    except Order.DoesNotExist:
        return None


def deal(user, service, postscript, points, itemkey=None,
         source=ORDER_SOURCE.UNKNOW, coupon_info_id=None, service_mode=None,
         coupon_deduction=0, points_deduction=0, is_doctor_see=False):
    if service.end_time and service.end_time < datetime.datetime.now():
        raise gen(CODES.SERVICE_OUT_OF_DATE)

    # service.can_sell_items已经失效 这里保留只是为了参考
    service_item_key = itemkey or service.can_sell_items()[0].key

    if service.payment_type == PAYMENT_TYPE.EXCHANGE_GIFT and service.exchange_points_ceiling > points:
        raise gen(CODES.SERVICE_NO_POINT)

    order_num = Order.objects.filter(user=user, service=service, service_item_key=service_item_key,
                                     status__in=[ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAYING]).count()
    if order_num > 0:
        raise gen(CODES.SETTLEMENT_HAS_UNPAID)

    # if service_mode == SERVICE_MODE.SECKILL:
    #     # if service.seckill_status != SECKILL_STATUS.UNDERWAY:
    #     #     raise gen(CODES.SECKILL_UNAVAILABLE)
    #     if not service.is_item_seckill(itemkey):
    #         raise gen(CODES.SECKILL_UNAVAILABLE)

    # 所有的美购 single_user_buy_limit or seckill_single_user_limit 都 > 0
    order_exclude = Q(status=ORDER_STATUS.CANCEL) | Q(status=ORDER_STATUS.REFUNDED)
    query = Order.objects.filter(service=service, service_item_key=service_item_key).exclude(order_exclude)

    if service_mode == SERVICE_MODE.SECKILL:
        query = query.filter(service_mode=service_mode)
        number_limit = service.seckill_single_user_buy_limit
    else:
        number_limit = service.single_user_buy_limit
    dt = DatetimeTool()
    query = query.filter(created_time__gte=dt.get_first_day_month(), created_time__lte=dt.get_last_day_month())
    user_query = query.filter(user=user)
    # filter user, service, service_item_key service_mode(if is seckill)
    # exclude order_exclude
    if user_query.count() >= number_limit > 0:
        raise gen(CODES.SETTLEMENT_BOUGHT_SERVICE_EXCEED_LIMIT)

    if settings.SWITCH_PURCHASE_PHONE_RESTRICT:
        phone_query = query.filter(phone=user.userextra.phone)
        if phone_query.count() >= number_limit > 0:
            raise gen(CODES.SETTLEMENT_BOUGHT_SERVICE_EXCEED_LIMIT)

    order = create(user, service, postscript, points, itemkey, source, coupon_info_id, service_mode,
                   coupon_deduction, points_deduction, is_doctor_see)
    return order


def deal_v2(user, service_obj, service_item_obj, points,
            source=ORDER_SOURCE.UNKNOW, coupon_info_id=None,
            coupon_deduction=0, points_deduction=0, is_doctor_see=False, order_id=None):

    if service_obj.end_time and service_obj.end_time < datetime.datetime.now():
        raise gen(CODES.SERVICE_OUT_OF_DATE)

    if service_obj.payment_type == PAYMENT_TYPE.EXCHANGE_GIFT and service_obj.exchange_points_ceiling > points:
        raise gen(CODES.SERVICE_NO_POINT)

    # order_num = Order.objects.filter(user=user, service=service_obj, service_item_key=service_item_obj.key,
    #                                  status__in=[ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAYING]).count()
    # if order_num > 0:
    #     raise gen(CODES.SETTLEMENT_HAS_UNPAID)

    service_single_user_buy_limit = service_obj.single_user_buy_limit
    if service_single_user_buy_limit > 0:

        order_exclude = Q(status=ORDER_STATUS.CANCEL) | Q(status=ORDER_STATUS.REFUNDED)
        query = Order.objects.filter(service=service_obj, service_item_key=service_item_obj.key).exclude(order_exclude)

        dt = DatetimeTool()
        query = query.filter(created_time__gte=dt.get_first_day_month(), created_time__lte=dt.get_last_day_month())

        user_query = query.filter(user=user)
        user_query_count = user_query.count()

        if user_query_count >= service_single_user_buy_limit:
            raise gen(CODES.SETTLEMENT_BOUGHT_SERVICE_EXCEED_LIMIT)

        if settings.SWITCH_PURCHASE_PHONE_RESTRICT:
            phone_query = query.filter(phone=user.userextra.phone)
            phone_query_count = phone_query.count()
            if phone_query_count >= service_single_user_buy_limit:
                raise gen(CODES.SETTLEMENT_BOUGHT_SERVICE_EXCEED_LIMIT)

    price_info = service_item_obj.get_current_price_info()

    price_id = price_info.get('id')
    single_user_buy_limit = price_info.get('single_user_buy_limit')

    is_seckill_price = False

    if 'selling_rule' in price_info and 'activity_type' in price_info['selling_rule'] \
            and price_info['selling_rule']['activity_type'] == ACTIVITY_TYPE_ENUM.SECKILL:
        is_seckill_price = True

    if is_seckill_price and single_user_buy_limit > 0:

        order_exclude = Q(status=ORDER_STATUS.CANCEL) | Q(status=ORDER_STATUS.REFUNDED)
        query = Order.objects.filter(service=service_obj, service_item_key=service_item_obj.key).exclude(order_exclude)

        user_query = query.filter(user=user, service_item_price_id=price_id)
        user_query_count = user_query.count()

        if user_query_count >= single_user_buy_limit:
            raise gen(CODES.LIMITED)

        if settings.SWITCH_PURCHASE_PHONE_RESTRICT:
            phone_query = query.filter(phone=user.userextra.phone, service_item_price_id=price_id)
            phone_query_count = phone_query.count()
            if phone_query_count >= single_user_buy_limit:
                raise gen(CODES.LIMITED)

    order = create_v2(user, service_obj, service_item_obj, points, source, coupon_info_id,
                      coupon_deduction, points_deduction, is_doctor_see, order_id)
    return order


def place_order(user, service_id, postscript="", itemkey=None,
                source=ORDER_SOURCE.UNKNOW, coupon_info_id=None,
                coupon_deduction=0, points_deduction=0, is_doctor_see=False):
    points = point.get_point_for(user)

    try:
        service = Service.objects.get(id=service_id)
    except Service.DoesNotExist:
        raise gen(CODES.SERVICE_NOT_EXSIT)

    if service.is_multiattribute and itemkey is None:
        raise gen(CODES.PARAMS_INCOMPLETE)
    # assert service.is_multiattribute and  itemkey
    service_mode = (
        service.is_item_seckill(itemkey) and
        SERVICE_MODE.SECKILL or SERVICE_MODE.NORMAL
    )
    # 减库存
    with transaction.atomic():
        if service_mode == SERVICE_MODE.SECKILL:
            seckill_obj = SpecialSeckillService.fetch(service, itemkey)
            service.is_item_seckill(itemkey)
            if not seckill_obj:
                raise gen(CODES.SECKILL_UNAVAILABLE)

            pk = seckill_obj.id
            row_count = SpecialSeckillService.objects.filter(stock__gt=0, id=pk).update(stock=F('stock') - 1)

            if row_count == 0:
                raise gen(CODES.SERVICE_SOLD_OUT)

        else:

            from django.db import connection

            cursor = connection.cursor()
            SQL = "UPDATE `api_service` SET `sell_num_limit` = (`api_service`.`sell_num_limit` - 1) WHERE (`sell_num_limit` > 0 AND `api_service`.`id` = %s)" % service_id
            row_count = cursor.execute(SQL)

            if row_count == 0:
                raise gen(CODES.SERVICE_SOLD_OUT)

            now = datetime.datetime.now()

            # 如果有必要，之后可以通过检查 changetime_row_count > 0 来确定是不是出现sell_num_limit=0，然后进行通知
            SQL = "UPDATE `api_service` SET `sellcount_changetime` = '%s' WHERE (`sell_num_limit` = 0 AND `api_service`.`id` = %s)" % (now.strftime('%Y-%m-%d %H:%M:%S'), service_id)
            changetime_row_count = cursor.execute(SQL)

            reloaded_service = Service.objects.get(id=service_id)
            _stock_monitor(reloaded_service, now)

    order = deal(user, service, postscript, points, itemkey, source, coupon_info_id, service_mode,
                 coupon_deduction, points_deduction, is_doctor_see)

    return order


def place_order_v2(user, service_obj, service_item_obj,
                   source=ORDER_SOURCE.UNKNOW, coupon_info_id=None,
                   coupon_deduction=0, points_deduction=0, is_doctor_see=False, order_id=None):
    points = point.get_point_for(user)

    if service_obj.end_time and service_obj.end_time < datetime.datetime.now():
        raise gen(CODES.SERVICE_OUT_OF_DATE)

    if service_obj.is_multiattribute and (service_item_obj.key is None or service_item_obj.key == ''):
        raise gen(CODES.PARAMS_INCOMPLETE)

    if service_obj.payment_type == PAYMENT_TYPE.EXCHANGE_GIFT and service_obj.exchange_points_ceiling > points:
        raise gen(CODES.SERVICE_NO_POINT)

    # 减库存
    with transaction.atomic():
        price_id = service_item_obj.get_current_price_info().get('id')

        # 应该先对可销售数量进行处理，然后对库存进行处理。
        decrease_price_sale_limit_success = service_item_obj.decrease_price_sale_limit(price_id=price_id,
                                                                                       sale_limit_must_gte_count=True)

        if not decrease_price_sale_limit_success:
            raise gen(CODES.SERVICE_SOLD_OUT)

        decrease_stock_success = service_item_obj.decrease_stock(sku_stock_must_gte_count=True)

        if not decrease_stock_success:
            raise gen(CODES.SERVICE_SOLD_OUT)

        now = datetime.datetime.now()

        current_stock = service_item_obj.sku_stock - 1
        _stock_monitor_v2(service_obj, current_stock, now)

    order = deal_v2(user, service_obj, service_item_obj, points, source, coupon_info_id,
                    coupon_deduction, points_deduction, is_doctor_see, order_id)

    return order


def _stock_monitor(service, now):
    if service is None:
        # exclude when the object is first created
        return
    try:
        if service.sell_num_limit == settings.STOCK_ALERT_THRESHOLD:
            if service.is_online:
                if now > service.start_time:
                    if service.doctor.business_partener:
                        business_partener_email = service.doctor.business_partener.email
                        stock_alert.delay(
                            service_id=service.id,
                            service_name=service.name,
                            current_stock=service.sell_num_limit,
                            email=business_partener_email,
                        )
    except:
        logging_exception()


def _stock_monitor_v2(service_obj, current_stock, now):
    if service_obj is None:
        return
    try:
        if current_stock == settings.STOCK_ALERT_THRESHOLD:
            if service_obj.is_online:
                if now > service_obj.start_time:
                    if service_obj.doctor.business_partener:
                        business_partener_email = service_obj.doctor.business_partener.email
                        stock_alert.delay(
                            service_id=service_obj.id,
                            service_name=service_obj.name,
                            current_stock=current_stock,
                            email=business_partener_email,
                        )
    except:
        logging_exception()


def stock_monitor_v3(service_obj, sku_stocks, now):
    if service_obj is None:
        return
    try:
        threshold = settings.STOCK_ALERT_THRESHOLD
        if any([s == threshold for s in sku_stocks]):
            if service_obj.is_online and now > service_obj.start_time:
                if service_obj.doctor and service_obj.doctor.business_partener:
                    business_partener_email = service_obj.doctor.business_partener.email
                    stock_alert.delay(
                        service_id=service_obj.id,
                        service_name=service_obj.name,
                        current_stock=threshold,
                        email=business_partener_email,
                    )
    except:
        logging_exception()


def _build_order_event_message(event_name, order):
    used_time = order.validate_time.strftime('%Y-%m-%d %H:%M:%S') if order.validate_time else None
    refunded_time = order.refund_time.strftime('%Y-%m-%d %H:%M:%S') if order.refund_time else None
    person_id = order.user.person.id.hex
    invite_code = order.servicesnapshot.invite_code
    service_type = order.servicesnapshot.service_type

    order_event_message = {'name': event_name,
                           'data': {
                               'order_id': order.id,
                               'order_user_person_id': person_id,
                               'order_gengmei_price': order.service_price,
                               'order_points_deduction': order.points_deduction,
                               'order_coupon_deduction': order.coupon_deduction,
                               'used_time': used_time,
                               'refunded_time': refunded_time,
                               'invite_code': invite_code,
                               'service_type': service_type,
                           },
                           }
    return order_event_message


def send_settlement_paid_event(settlement):
    orders = []
    all_order_ids = []
    user = None

    for item in settlement.items.all():
        order = item.order
        all_order_ids.append(order.id)
        user = order.user

        om = _build_order_event_message('', order)
        orders.append(om['data'])

    already_has_order = Order.objects.filter(user=user).exclude(
        status__in=(ORDER_STATUS.CANCEL, ORDER_STATUS.NOT_PAID)
    ).exclude(id__in=all_order_ids).exists()

    is_first_order = not already_has_order

    message = {'name': 'settlement_paid',
               'data': {
                   'settlement_id': settlement.id,
                   'is_first_order': is_first_order,
                   'orders': orders,
               }
               }

    settlement_task.settlement_event_for_apollo.delay(message)


def send_order_used_event(order):
    message = _build_order_event_message('order_used', order)
    # TODO: 改为异步
    order_task.order_event_for_apollo.delay(message)


def send_order_refunded_event(order):
    message = _build_order_event_message('order_refunded', order)
    # TODO: 改为异步
    order_task.order_event_for_apollo.delay(message)


def apply_refund_order_by_user(order_id, comment):
    try:
        with transaction.atomic():
            order = Order.objects.select_for_update().select_related("service__doctor__hospital").get(pk=order_id)

            if order.status != ORDER_STATUS.PAID:
                return False, CODES.ORDER_CAN_NOT_REFUND
            else:
                _apply_refund_order_core(order, order.user, comment)

                # 极速退款
                user_can_speed_refund = int(order.user.userextra.user_rights_level) >= 3
                is_groupbuy_fail_order = order.groupbuy_status == GROUPBUY_STATUS.GROUPBUY_FAIL

                start_refund_now = user_can_speed_refund or is_groupbuy_fail_order

                if start_refund_now:
                    doctor = order.service.doctor
                    optype = ORDER_OPERATION_TYPE.GROUPBUY_FAIL if is_groupbuy_fail_order else ORDER_OPERATION_TYPE.DOCTOR_APPROVE

                    order.operate(doctor.user.person,
                                  optype,
                                  ORDER_OPERATION_ROLE.SYSTEM)
                    # order_task.speed_refund.delay(refund_obj)
                    order_task.start_refund.delay(order.id)

                return True, None

    except:
        logging_exception()
        return False, CODES.ORDER_CAN_NOT_REFUND


def refund_can_not_refundorder(order):
    """
        预留给下单时候说明不能退款，或者其他原因比如验证之后还要退款的处理方法
        这类需求一般来源于一些运营没有考虑到情况，或者不可抗力
        （比如发生过抽奖的美购抽到之后发现做不了，或者客户真的要闹然后客服顶不住，好像活动说明不能退但是...）
        因此退款流程修改的时候也需要考虑这里的情况。
    :return:
    """
    from pay.tool.new_order_tool import get_actual_refund_amount

    can_change_order_status = (ORDER_STATUS.PAID, ORDER_STATUS.USED)

    if order.status not in can_change_order_status:
        raise gen(CODES.ORDER_CAN_NOT_REFUND)

    if not order.payment > 0:
        raise gen(CODES.ORDER_REFUDN_PAYMENT_ERROR)

    # ["计划有变，不想做了",""]
    comment = u'["\u8ba1\u5212\u6709\u53d8\uff0c\u4e0d\u60f3\u505a\u4e86",""]'
    operator = Person.objects.get(user_id=settings.BOSS)

    with transaction.atomic():
        # 之前那个方法返回的process_ok对于这个场景没有意义，所以不处理返回值了
        _apply_refund_order_core(order, order.user, comment)

        order.operate(operator, ORDER_OPERATION_TYPE.REFUNDED, ORDER_OPERATION_ROLE.SYSTEM)

        ro = order.refund
        if ro and ro.fee is None:
            # 因为以前的逻辑，是在退款回调回来的时候，才会去把对应的fee填上去
            # 但是对于这种不会有退款请求的就会少了相关参数，导致数据组那边拿到奇怪的数据
            # 所以这里也需要和退款回调的时候一样，补充fee，我也觉得以前这种一定要等到退款回调才算fee的行为很奇怪...

            ro.fee = get_actual_refund_amount(order)
            ro.save(update_fields=['fee'])

    from api.tool.order_tool import send_momo_stat_log_info_when_order_refunded
    send_momo_stat_log_info_when_order_refunded(order)

    message = _build_order_event_message('order_refunded_after_used', order)
    order_task.order_event_for_apollo.delay(message)


def _apply_refund_order_core(order, user, comment):

    with transaction.atomic():
        # RefundOrder依赖refund_comment不能为null
        order.refund_comment = comment if comment else ''
        order.operate(user.person, ORDER_OPERATION_TYPE.APPLY_REFUND, ORDER_OPERATION_ROLE.USER)
        order.save()

        city_and_doctor_name = order.service.doctor.name
        if order.service.doctor.hospital and order.service.doctor.hospital.city:
            city_and_doctor_name = u'{}@{}'.format(order.service.doctor.hospital.city.name,
                                                   order.service.doctor.name)

        notification_content = (
            u'您的【{}】（订单号：{}）退款申请已受理，如有任何疑问，请拨打更美客服：{}转6666'
        )
        notification_content = notification_content.format(
            city_and_doctor_name, order.id, PhoneService.get_phone_prefix('6666')
        )
        send_notification(
            user.id,
            u'退款申请已受理',
            notification_content,
            PushUrlProtocol.ORDER_DETAIL.format(id=order.id)
        )

        ss = DoctorNotifyService.get_service(order.service.doctor_id)
        ss.notify_new_refund(order.refund)

        from hippo.views.doctormessage import add_order_message
        from gm_types.doctor import DOCTOR_ORDER_MESSAGE_TYPE

        add_order_message(DOCTOR_ORDER_MESSAGE_TYPE.REFUND, [order.refund.id])


def cancel_refund(order, user):
    try:
        with transaction.atomic():
            order.operate(user.person, ORDER_OPERATION_TYPE.CANCEL_REFUND, ORDER_OPERATION_ROLE.USER)
            order_task.refund_cancle_event.delay(order.id)
    except:
        logging_exception()
        return gen(CODES.REFUND_CAN_NOT_CANCLE)


def _is_order_discount_lt_coupon(order, used_coupon_info):
    '''
    如果订单关联的美券有抽成限制, 抽成小于等于 满减券金额, 则未参加满减计算, 可直接退
    如果没有抽成限制, 则已参加满减券计算, 则不允许直接退
    '''
    if not used_coupon_info.discount_limit():
        return False
    discount = json.loads(order.service_snapshot).get('discount')
    return discount < used_coupon_info.value


def can_refund_directly(order, used_coupon_info):
    '''
    无条件退款
    适用情况:
        1. 秒杀福利
        2. 是否底价
        3. 商家回扣
    '''
    is_floor_price = ServiceSnapshot.objects.filter(order=order).values_list('is_floor_price', flat=True)

    return order.is_seckill_order or is_floor_price or _is_order_discount_lt_coupon(order, used_coupon_info)


def cancel_order(order):
    with transaction.atomic():
        # update order status
        is_success = order.operate(order.user.person, ORDER_OPERATION_TYPE.CANCEL, ORDER_OPERATION_ROLE.USER)
        increase_inventory(order)
        if is_success:
            # return coupon
            # success, coupon_info = return_coupon(order.id)
            return gen(CODES.SUCCESS)
        else:
            return False, gen(CODES.ORDER_CAN_NOT_CANCEL)
