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

import six
import json
import random
import datetime
from collections import OrderedDict
from hashlib import md5

import xlrd
from helios.rpc.internal.exceptions import RPCTimeoutException

try:
    import cPickle as pickle
except ImportError:
    import pickle

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
import gevent

from rpc.cache import service_info_list_cache
from rpc.tool.param_check import assert_uint
from rpc.all import get_rpc_remote_invoker
from api.models import (
    Service, ServiceItem, SpecialItem, ServiceFavor, SeckillNotify, Special,
    City, ServiceVideo, Hospital_Extra, PeriodHospital
)
from api.tool.image_utils import get_full_path
from api.tool.log_tool import logging_exception,search_logger, cache_logger
from api.manager import coupon_manager
from gm_types.gaia import DOCTOR_TYPE, SERVICE_ORDER_TYPE, SECKILL_STATUS, ACTIVITY_TYPE_ENUM, SERVICE_HOME_SECKILL_STATUS
from gm_types.doctor import HOSPITAL_SCALE
from api.tool.user_tool import get_user_city_tagid
from api.tool.geo_tool import get_location_tag_id_by_city_id
from api.util.gevent_util import init_tracer, wrap_tracer
from api.util.cache_util import cache_wrap
from gm_types.gaia import SKU_SHOW_PRICE_TYPE
from search.utils.diary import filter_diary


_service_info_cache_key_format = "service_info_v4"
_service_diary_count_cache_key_format = "service_diary_count_v2"
_service_topic_count_cache_key_format = "stc_v1:{}"
_service_insurance_info_cache_key_format = "sii_v1:{}"
_service_item_default_price_info_cache_key_format = "sidpi_v1:{}"
_service_item_current_price_info_cache_key_format = 'sicpi_v1:{}'
_service_item_seckill_price_info_cache_key_format = "sispi_v1:{}:{}"
_doris_search_seckill_info_cache_key_format = "dssi_v1:{}"
_seckill_info_cache_key_format = "seckill_v1:{}:{}"

_load_seckill_info_from_pre_write_cache = True

_service_info_cache_time_factor = 15

try:
    _load_seckill_info_from_pre_write_cache = bool(settings.LOAD_SECKILL_INFO_FROM_PRE_WRITE_CACHE)
except:
    pass

def add_double_eleven_image():
    service_ids = []
    return service_ids


def _flatten_izip_logest_without_none(list_of_list):
    return [item for sublist in list(six.moves.zip_longest(*list_of_list)) for item in sublist if item is not None]


def get_insurance_info_by_insurance_service_id(insurance_service_id):
    """
        这个方法是会针对RPC调用成功和失败的情况进行缓存，只是缓存时间不同。

    :param insurance_service_id: 保险id
    :return: None 如果没有对应数据 或者 RPC调用失败
    """
    if not insurance_service_id or not insurance_service_id > 0:
        return None

    cache_key = _service_insurance_info_cache_key_format.format(insurance_service_id)
    si_info = service_info_list_cache.get(cache_key)
    if si_info:
        info = json.loads(si_info)
        return info if info else None
    else:
        get_insurance_success = False
        info = None

        try:
            timeout = 0.16
            info = get_rpc_remote_invoker().invoket(
                method='plutus/insurance/service/info',
                timeout=timeout,
                params={"insurance_service_id": insurance_service_id},
            ).unwrap()
            get_insurance_success = True
        except:
            logging_exception()

        if get_insurance_success:
            json_str = json.dumps(info)
            cache_time = random.randint(40, 60) * _service_info_cache_time_factor
            service_info_list_cache.setex(cache_key, cache_time, json_str)

            return info
        else:
            # 其他地方成功了，那么直接返回Cache内容
            si_info = service_info_list_cache.get(cache_key)
            if si_info:
                info = json.loads(si_info)
                return info if info else None
            else:
                json_str = "{}"
                cache_time = random.randint(4, 6) * _service_info_cache_time_factor
                service_info_list_cache.setex(cache_key, cache_time, json_str)

                return None


def _get_service_id_to_topic_count_dict(service_ids):
    """

    :param service_ids: [1,2,3]
    :return: { 1:0, 2:9, 3:2 }
    """
    result = {}

    if len(service_ids) > 0:

        not_in_cache_service_ids = []

        for sid in service_ids:
            cache_key = _service_topic_count_cache_key_format.format(sid)
            si = service_info_list_cache.get(cache_key)
            if si:
                try:
                    topic_count = int(si)
                except:
                    topic_count = 0

                result[sid] = topic_count
            else:
                not_in_cache_service_ids.append(sid)

        chunk_size = 10

        for sids in [not_in_cache_service_ids[i:i + chunk_size] for i in
                     range(0, len(not_in_cache_service_ids), chunk_size)]:
            get_topic_count_success = False

            try:
                timeout = 0.36
                info = get_rpc_remote_invoker().invoket(
                    method='diary/get_topic_count',
                    timeout=timeout,
                    params={"service_ids": sids},
                ).unwrap()

                service_id_to_topic_count = info["services"]

                cache_time = random.randint(40, 60) * _service_info_cache_time_factor

                for sid in sids:
                    cache_key = _service_topic_count_cache_key_format.format(sid)
                    topic_count = service_id_to_topic_count.get(str(sid), 0)

                    service_info_list_cache.setex(cache_key, cache_time, topic_count)
                    result[sid] = topic_count

                get_topic_count_success = True
            except:
                logging_exception()

            if get_topic_count_success is False:
                # 其他地方成功了，那么直接返回Cache内容
                cache_time = random.randint(4, 6) * _service_info_cache_time_factor
                for sid in sids:
                    cache_key = _service_topic_count_cache_key_format.format(sid)
                    topic_count = 0

                    si = service_info_list_cache.get(cache_key)
                    if si:
                        try:
                            topic_count = int(si)
                        except:
                            pass
                    else:
                        service_info_list_cache.setex(cache_key, cache_time, topic_count)
                    result[sid] = topic_count

    return result

service_diary_cache_wrap = cache_wrap(service_info_list_cache, pre_fix=_service_diary_count_cache_key_format, expire=40*_service_info_cache_time_factor, time_range=20*_service_info_cache_time_factor)

@service_diary_cache_wrap.batch
def get_service_id_to_diary_count_dict(service_ids):
    """
    :param service_ids: [1,2,3]
    :return: { 1:0, 2:9, 3:2 }
    """
    result = {}
    for sid in service_ids:
        result[sid] = 0

    if not service_ids:
        return result
    cache_logger.info('[CACHE_SERVICE_DIARY] service_diary_count entry for count: %s, service_ids(%s)' % (len(service_ids), service_ids))
    try:
        diary_info_list = get_rpc_remote_invoker().invoket(
            method='diary/diary_info_by_service_ids',
            timeout=2,
            params={"service_ids": service_ids, "count_only": True}
        ).unwrap()
    except RPCTimeoutException:
        return result
    except BaseException:
        logging_exception()
    else:
        service_id_to_diary_count = {
            diary_info['service_id']: diary_info['diary_count']
            for diary_info in diary_info_list
        }
        for sid, diary_count in service_id_to_diary_count.items():
            try:
                diary_count = int(diary_count)
            except:
                logging_exception()
                diary_count = 0

            result[sid] = diary_count

    return result


service_cache_wrap = cache_wrap(service_info_list_cache, pre_fix=_service_info_cache_key_format, expire=40*_service_info_cache_time_factor, time_range=20*_service_info_cache_time_factor)

@service_cache_wrap.batch
def _get_service_id_to_service_info_dict(service_ids):
    """
        根据service_id获取美购信息的方法，这个方法可能会加上缓存
        因此和用户相关的信息，以及和价格库存相关的信息，以及和时间敏感度比较高的信息 不应该在这个方法内获取。
    """

    if not service_ids:
        return {}
    cache_logger.info('[CACHE_SER_INFO] service_info entry for count: %s, service_ids(%s)' % (len(service_ids), service_ids))
    service_id_to_service_info_dict = {}
    service_objs = [s for s in Service.objects.filter(
        id__in=service_ids)
        .select_related('project_type')
        .select_related('doctor__hospital__city')]
    # TODO: 这一个查询太大了，可以考虑优化为两个查询，就是将doctor部分手动查出来

    exists_sids = [s.id for s in service_objs]

    hids = {s.doctor.hospital_id for s in service_objs}
    period_hospital_ids = set(PeriodHospital.get_hospital_ids_in_list(hospital_ids=hids))

    hid_to_scale = {hid: scale for hid, scale in
                    Hospital_Extra.objects.filter(hospital_id__in=hids).values_list('hospital_id', 'scale')}

    sid_to_optag = Service.get_operate_tag_info_by_service_id(exists_sids)

    has_video_service_ids_set = set(ServiceVideo.objects.filter(
        service__in=exists_sids).values_list('service_id', flat=True))

    service_id_list = add_double_eleven_image()
    for s in service_objs:
        sid = s.id
        doctor_name = s.doctor and s.doctor.name or ''
        doctor_title = s.doctor and s.doctor.doctor_title_display
        doctor_portrait = s.doctor and get_full_path(s.doctor.portrait) or ''

        hospital = s.doctor and s.doctor.hospital or None

        hospital_id = hospital and hospital.id or ''
        hospital_name = hospital and hospital.name or ''
        hospital_alias_name = hospital and hospital.alias_name or ''

        city_id = ''
        city = ''
        if s.doctor and s.doctor.hospital and s.doctor.hospital.city:
            city_id = s.doctor.hospital.city_id
            city = s.doctor.hospital.city.name

        baidu_loc_lng = (
            s.doctor and s.doctor.hospital and
            s.doctor.hospital.baidu_loc_lng or 0.0
        )

        baidu_loc_lat = (
            s.doctor and s.doctor.hospital and
            s.doctor.hospital.baidu_loc_lat or 0.0
        )

        hospital_scale = hid_to_scale.get(s.doctor.hospital_id, HOSPITAL_SCALE.NONE)
        op_tag = sid_to_optag.get(sid, [])

        is_stage = s.is_stage and s.doctor.hospital_id in period_hospital_ids
        #  这里保存的数据不能太多，否则可能会被给redis带来较大的存储压力
        double_eleven_image = ''
        if sid in service_id_list:
            double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'
        service_info_data = {
            'service_id': sid,
            'doctor_name': doctor_name,
            'doctor_id': s.doctor_id,
            'doctor_title': doctor_title,
            'doctor_type': s.doctor.doctor_type,
            'doctor_portrait': doctor_portrait,
            'city': city,
            'service_name': s.name,
            'project_type_name': s.project_type.name if s.project_type else '',
            'hospital_id': hospital_id,
            'hospital_name': hospital_name,
            'hospital_alias_name': hospital_alias_name,
            'service_image': s.image_header,
            'insurance_info': s.insurance_info,
            'is_multiattribute': s.is_multiattribute,
            'is_can_be_sold': s.is_can_be_sold(check_any_sku_has_stock=False),
            'operate_tag': op_tag,
            'sell_amount': s.sell_amount_display,

            'is_stage': is_stage,
            'hospital_lng': baidu_loc_lng,
            'hospital_lat': baidu_loc_lat,
            'has_video': True if sid in has_video_service_ids_set else False,
            'hospital_scale': hospital_scale,
            'is_operation': s.is_operation,
            'city_id': city_id,
            'double_eleven_image': double_eleven_image,
            'doctor_badges': s.doctor_badges(),
        }

        service_id_to_service_info_dict[sid] = service_info_data

    return service_id_to_service_info_dict


def get_seckill_sku_info(special_id, skus_info, user=None, now=None):
    sku_ids = [str(info['sku_id']) for info in skus_info]
    # seckill_sku_info_from_pre_write_cache = _get_seckill_sku_info_from_pre_write_cache(special_id, sku_ids)
    seckill_sku_info_from_pre_write_cache = {}

    to_load_skus_info = [si for si in skus_info if str(si['sku_id']) not in seckill_sku_info_from_pre_write_cache]

    seckill_sku_info_from_db_list = get_seckill_sku_info_from_db(special_id, to_load_skus_info, user, now)

    seckill_sku_info_from_db = {}
    for sidb in seckill_sku_info_from_db_list:
        sku_id_str = str(sidb["service_item_id"])
        seckill_sku_info_from_db[sku_id_str] = sidb

    result = []
    for sinfo in skus_info:
        sku_id_str = str(sinfo['sku_id'])
        if sku_id_str in seckill_sku_info_from_pre_write_cache:
            info_from_cache = seckill_sku_info_from_pre_write_cache[sku_id_str]
            if info_from_cache is not None:
                #  无法售卖的会有key但是对应value为None，不在cache里面的连key都没有
                result.append(seckill_sku_info_from_pre_write_cache[sku_id_str])
        elif sku_id_str in seckill_sku_info_from_db:
            result.append(seckill_sku_info_from_db[sku_id_str])

    return result


def get_seckill_sku_info_from_db(special_id, skus_info, user=None, now=None):
    if len(skus_info) == 0 or not special_id > 0:
        return []

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

    special = Special.objects.filter(id=special_id).first()
    if special is None:
        return []

    special_is_ongoing = False
    if special.start_time <= now <= special.end_time:
        special_is_ongoing = True

    result_sid_to_siids = {}  # service_id 为key 的sku id列表
    for sku_info in skus_info:
        sid = sku_info['service_id']
        if sid not in result_sid_to_siids:
            result_sid_to_siids[sid] = []
        result_sid_to_siids[sid].append(sku_info['sku_id'])

    service_id_to_service_info_dict = _get_service_id_to_service_info_dict(result_sid_to_siids.keys())

    siid_to_seckill_list_info = _get_service_item_id_to_seckill_list_info(
        now, special_id, special_is_ongoing, user, result_sid_to_siids, service_id_to_service_info_dict
    )

    service_info_list = []
    # 输出需要保持输入的service_ids的次序
    for sku_info in skus_info:
        siid = sku_info['sku_id']
        if siid in siid_to_seckill_list_info:
            sku_card_info = siid_to_seckill_list_info[siid]
            sku_card_info['__src_type'] = sku_info.get('__src_type', '')
            service_info_list.append(sku_card_info)
    return service_info_list


def _get_service_item_id_to_seckill_list_info(now, special_id, special_is_ongoing, user, sid_to_siids,
                                              service_id_to_service_info_dict):
    from api.manager import coupon_manager

    siid_to_sid = {}
    all_siids = []
    all_sids = sid_to_siids.keys()
    for sid, siids in sid_to_siids.items():
        for siid in siids:
            all_siids.append(siid)
            siid_to_sid[siid] = sid
    siid_to_items_name = ServiceItem.get_items_name(all_siids)
    siid_to_tip = {}
    sp_list = SpecialItem.objects.filter(
        serviceitem_id__in=all_siids,
        special_id=special_id
    ).values_list('id', 'serviceitem_id', 'tip')
    for _, service_item_id, tip in sp_list:
        if tip:
            siid_to_tip[service_item_id] = tip
    siid_to_sku_stock = {
        si[0]: si[1] for si in
        ServiceItem.objects.filter(id__in=all_siids).values_list('id', 'sku_stock')
        }
    spi = _get_seckill_price_info_by_service_item_ids(service_item_ids=all_siids, special_id=special_id)
    cpi = _get_current_price_info_by_service_item_ids(service_item_ids=all_siids, now=now)
    dpi = _get_default_price_info_by_service_item_ids(service_item_ids=all_siids)
    siid_to_mark_notice = {}
    sid_to_is_favored = {}
    if user:
        # 是否设置秒杀提醒状态
        if user and hasattr(user, 'person') and special_id:
            user_notify_service_item_ids = SeckillNotify.objects.filter(
                person=user.person, special_id=special_id, service_item_id__in=all_siids
            ).values_list('service_item_id', flat=True)

            for siid in user_notify_service_item_ids:
                siid_to_mark_notice[siid] = True

        if user and not isinstance(user, AnonymousUser):
            is_favored_ids = ServiceFavor.objects.filter(
                user=user, service_id__in=all_sids, is_deleted=False
            ).values_list('service_id', flat=True)

            for sid in is_favored_ids:
                sid_to_is_favored[sid] = True

    siid_to_seckill_list_info = {}
    for service_item_id, price_info in spi.items():
        current_price_info = cpi.get(service_item_id, None)
        default_price_info = dpi.get(service_item_id, None)

        if price_info and current_price_info:
            sid = siid_to_sid[service_item_id]
            si = service_id_to_service_info_dict[sid]

            project_type_name = si['project_type_name']
            items_name = ''.join(siid_to_items_name.get(service_item_id, []))
            service_item_name = '{}:{}'.format(project_type_name, items_name) if project_type_name else items_name

            # 由于 service_id_to_service_info_dict 的数据可能是从cache读取的
            # 如果在里面增加了新的字段，在刚刚上线的时候新的字段可能还不在cache数据中
            # 读取的时候需要做好处理，比如下面就针对新加的列使用了get和默认值的处理方法
            seckill_list_info = {
                'service_id': si['service_id'],
                'service_item_id': service_item_id,
                'city': si['city'],
                'service_name': si['service_name'],
                'service_item_name': service_item_name,
                'hospital_name': si['hospital_name'],
                'doctor_name': si['doctor_name'],
                'doctor_type': si['doctor_type'],
                'service_image': si['service_image'],
                'insurance_info': si['insurance_info'],
                'operate_tag': si.get('operate_tag', []),
                'sell_amount': si.get('sell_amount', 0),
                'double_eleven_image': si.get('double_eleven_image', ''),
            }

            seckill_list_info["coupon_gifts"] = []  # 这个字段废弃
            seckill_list_info["best_coupon_gifts"] = []

            # 上面是service信息部分，大概可以缓存的？不过manager内有通用的方法

            sold_percent = None
            is_seckill_price = False
            can_be_sold = si['is_can_be_sold']
            tip = siid_to_tip.get(service_item_id, '')
            is_seckill = price_info["price_type"] == ACTIVITY_TYPE_ENUM.SECKILL
            left_limit = price_info.get("sale_limit", 0)
            total_num = price_info.get('total_num', 0),


            gengmei_price = current_price_info['gengmei_price'] if special_is_ongoing else price_info['gengmei_price']
            original_price = current_price_info['original_price'] if special_is_ongoing else price_info['original_price']

            price_market = default_price_info['gengmei_price'] if default_price_info else gengmei_price

            if current_price_info:
                price_sold_out = price_info['sale_limit'] <= 0 or siid_to_sku_stock.get(service_item_id, 0) <= 0
                is_seckill_price = special_is_ongoing
                if is_seckill_price:
                    sold_percent = 100.0 if price_sold_out else price_info['sold_percent']
            else:
                # !!不能下单？
                pass

            seckill_list_info['is_support_renmai_payment'] = False
            seckill_list_info['is_seckill'] = is_seckill
            seckill_list_info['renmai_period_price'] = 0
            seckill_list_info['is_support_yirendai_payment'] = False
            seckill_list_info['yirendai_period_price'] = 0

            seckill_list_info['sold_percent'] = sold_percent
            seckill_list_info['is_can_be_sold'] = can_be_sold
            seckill_list_info['is_seckill_price'] = is_seckill_price
            seckill_list_info['left_limit'] = left_limit
            seckill_list_info['total_num'] = total_num
            seckill_list_info['tip'] = tip
            seckill_list_info['price_gengmei'] = gengmei_price  # 这个字段废弃，之后把M站接受的部分改回去
            seckill_list_info['original_price'] = original_price
            seckill_list_info['gengmei_price'] = gengmei_price
            seckill_list_info['price_market'] = price_market

            # 上面是价格相关的部分，一般不能缓存

            seckill_list_info['is_favored'] = sid_to_is_favored.get(sid, False)
            seckill_list_info['mark_notice'] = siid_to_mark_notice.get(service_item_id, False)

            # 上面使用户相关的部分，一般不能缓存

            siid_to_seckill_list_info[service_item_id] = seckill_list_info
    return siid_to_seckill_list_info


def _get_service_source(service, source):
    source['short_description'] = service.short_description
    source['detail_description'] = service.detail_description
    source['image_detail'] = service.image_detail
    source['service_type'] = service.service_type
    source['doctor_id'] = service.doctor_id
    source['payment_type'] = service.payment_type

    source['is_price_range'] = True if source['is_multiattribute'] else False

    source['service_status_text'] = service.limit_hint if source['is_can_be_sold'] else u'抢完'

    source['is_available_soon'] = service.is_available_soon
    source['label'] = service.label

    source['tags'] = []  # 这个只有SKU才输出？SPU版本没有这个字段
    source['tip'] = ""  # 这个只有SKU才输出？SPU版本没有这个字段
    source['limit_hint'] = service.limit_hint  # 之前SKU版本里面没有这个字段，估计之后可以去掉

    source["coupon_gifts"] = []  # 不再使用的字段
    return source


def _get_service_spu_id_to_price_info(service_ids, now):
    '''
    获取spu列表页对应外显价
    :param service_ids:
    :param now:
    :return:
    '''
    result = {}
    cache_keys = []
    loss_cache_service_ids = []
    _spu_price_format = 'spu_price_v1:{}'
    for sid in service_ids:
        cache_key = _spu_price_format.format(sid)
        cache_keys.append(cache_key)
    price_list = service_info_list_cache.mget(cache_keys)
    for idx, sid in enumerate(service_ids):
        price_info = price_list[idx]
        if price_info:
            result[sid] = json.loads(price_info)
        else:
            loss_cache_service_ids.append(sid)
    if not loss_cache_service_ids:
        return result

    def get_expire_time():
        return random.randint(40, 60) * _service_info_cache_time_factor

    service_ids = loss_cache_service_ids

    cache_logger.info('[CACHE_SPU_PRICE] service_price entry for count: %s, service_item_ids(%s)' % (len(service_ids), service_ids))

    loss_result = {}

    sid_to_siids = Service.get_service_ids_to_can_sell_item_ids(service_ids)

    # search_logger.info("可以销售的sku id----")
    # search_logger.info(sid_to_siids)

    all_try_get_siids = _flatten_izip_logest_without_none(sid_to_siids.values())

    all_try_siid_to_cpi = Service.get_current_price_info_by_service_item_ids(
        service_item_ids=all_try_get_siids,
        now=now,
    )

    siid_to_gbpi = Service.get_current_groupbuy_price_info_by_service_item_ids(
        service_item_ids=all_try_get_siids
    )
    siid_to_mpi = Service.get_current_multibuy_price_info_by_service_item_ids(all_try_get_siids)

    # search_logger.info("serviceitem ids to groupbuy price---")
    # search_logger.info(siid_to_gbpi)

    def get_current_service_item_price_key(price_info):
        return price_info["gengmei_price"], price_info["pre_payment_price"], -price_info["service_item_id"]

    def get_current_groupbuy_service_item_price_key(price_info):
        rule = price_info['selling_rule']
        return price_info['gengmei_price'], rule['groupbuy_nums'], rule['groupbuy_type'], -price_info['service_item_id']

    def get_current_multibuy_service_item_price_key(price_info):
        rule = price_info['selling_rule']
        return price_info['single_price'], rule['more_buy_count']

    dp_siids = []

    # 同一个SPU下面，从所有秒杀SKU（没有的话就是所有SKU）中找一个最低的SKU作为SPU的选中SKU, 如果SPU下有SKU参加拼团，则添加拼团价格信息
    for sid, siids in sid_to_siids.items():
        seckill_cpis = []
        groupbuy_cpis = []
        multibuy_cpis = []
        all_cpis = []

        for siid in siids:
            if siid_to_gbpi.get(siid):
                gbpi=siid_to_gbpi.get(siid)
                groupbuy_cpis.append(gbpi)

            if all_try_siid_to_cpi.get(siid):
                cpi = all_try_siid_to_cpi[siid]
                all_cpis.append(cpi)
                if cpi["price_type"] == ACTIVITY_TYPE_ENUM.SECKILL:
                    seckill_cpis.append(cpi)
            if siid_to_mpi.get(siid):
                multibuy_cpis.append(siid_to_mpi[siid])

        # search_logger.info("groupbuy_cpis---")
        # search_logger.info(groupbuy_cpis)

        gbpi_for_service = None
        if len(groupbuy_cpis):
            #找到价格更低的团购
            gbpi_for_service = min(groupbuy_cpis, key=get_current_groupbuy_service_item_price_key)

        cpi_for_service = None
        if len(seckill_cpis) > 0:
            cpi_for_service = min(seckill_cpis, key=get_current_service_item_price_key)
        elif len(all_cpis) > 0:
            cpi_for_service = min(all_cpis, key=get_current_service_item_price_key)

        mpi_for_service = None
        if len(multibuy_cpis) > 0:
            mpi_for_service = min(multibuy_cpis, key=get_current_multibuy_service_item_price_key)

        if not cpi_for_service:
            continue

        d = {
            "is_seckill": False,
            "is_groupbuy": False,
            'is_multibuy': False,
            "service_item_id": cpi_for_service["service_item_id"],
            'selling_rule': cpi_for_service.get('selling_rule') or {},
            'original_price': cpi_for_service['original_price'],
            'gengmei_price': cpi_for_service['gengmei_price'],
            "selected_service_item_id": cpi_for_service['service_item_id'],
            'service_item_id_new': cpi_for_service['service_item_id'],
            "pre_payment_price": cpi_for_service["pre_payment_price"],

            "post_price": max(0, cpi_for_service['gengmei_price'] - cpi_for_service['pre_payment_price']),
            "final_payment_price": max(0, cpi_for_service['gengmei_price'] - cpi_for_service['pre_payment_price']),
            "new_final_payment_price": max(0, cpi_for_service['gengmei_price'] - cpi_for_service['pre_payment_price']),
            'default_price': 0,
            'groupbuy_nums':0,
        }

        loss_result[sid] = d

        # 当价格为拼团或者秒杀时， gengmei_price 变更为默认价
        is_seckill = cpi_for_service["price_type"] == ACTIVITY_TYPE_ENUM.SECKILL
        if is_seckill:
            d['is_seckill'] = True
            d['seckill_price'] = cpi_for_service["gengmei_price"]
            d['seckill_selling_rule'] = cpi_for_service.get('selling_rule') or {}
            dp_siids.append(cpi_for_service["service_item_id"])

        if gbpi_for_service:
            sku_no_groupbuy_price = all_try_siid_to_cpi.get(gbpi_for_service.get("service_item_id"), {}).get("gengmei_price", 0)
            d['is_groupbuy'] = True

            selling_rule=gbpi_for_service.get('selling_rule')
            d['groupbuy_nums']=selling_rule.get("groupbuy_nums")
            d['active_type']=selling_rule.get("active_type")
            d['sale_limit']=gbpi_for_service["sale_limit"]
            d['groupbuy_price'] = gbpi_for_service['gengmei_price']
            d['original_price'] = gbpi_for_service['original_price']
            d['final_payment_price'] = max(0, gbpi_for_service['gengmei_price'] - gbpi_for_service['pre_payment_price'])
            d["new_final_payment_price"] = max(0, gbpi_for_service['gengmei_price'] - gbpi_for_service['pre_payment_price'])
            d["sku_no_groupbuy_price"] = sku_no_groupbuy_price
            d['selected_service_item_id'] = gbpi_for_service['service_item_id']
            d['service_item_id_new'] = gbpi_for_service['service_item_id']
            d['groupbuy_selling_rule'] = gbpi_for_service.get('selling_rule') or {}
            if cpi_for_service['service_item_id'] not in dp_siids:
                dp_siids.append(cpi_for_service['service_item_id'])

        if mpi_for_service:
            d['is_multibuy'] = True
            d['multibuy_price'] = mpi_for_service['single_price']
            d['original_price'] = mpi_for_service['original_price']
            d['service_item_id_new'] = mpi_for_service['parent_id']
            d["new_final_payment_price"] = max(0, mpi_for_service['gengmei_price'] - mpi_for_service['pre_payment_price'])
            d['multibuy_selling_rule'] = mpi_for_service.get('selling_rule') or {}
            if mpi_for_service['service_item_id'] not in dp_siids:
                dp_siids.append(mpi_for_service['service_item_id'])

        order_keys = ['seckill_price', 'gengmei_price',  'groupbuy_price', 'multibuy_price']
        exist_price_keys = []
        for ok in order_keys:
            if ok in d:
                exist_price_keys.append(ok)
        show_price_key = exist_price_keys[0]
        show_price_value = d[show_price_key]
        for epk in exist_price_keys:
            if d[epk] < show_price_value:
                show_price_key = epk
        d['show_price_key'] = show_price_key

    siid_to_default_gengmei_price = {}
    if dp_siids:
        siid_to_dpi = Service.get_default_price_info_by_service_item_ids(
            service_item_ids=dp_siids,
        )
        for siid, dpi in siid_to_dpi.items():
            if not dpi:
                continue
            siid_to_default_gengmei_price[siid] = dpi["gengmei_price"]

        for sid, info in loss_result.items():
            info['default_price'] = max(
                siid_to_default_gengmei_price.get(info["service_item_id"], 0),
                info.get('groupbuy_price', 0),
                info.get('seckill_price', 0),
                info.get('multibuy_price', 0),
                info['gengmei_price'],
            )
            if info['is_seckill'] and siid_to_default_gengmei_price.get(info['service_item_id'], 0) > info['seckill_price']:
                info['gengmei_price'] = siid_to_default_gengmei_price[info['service_item_id']]

    for k, v in loss_result.items():
        if not v:
            continue

        cache_key = _spu_price_format.format(k)
        cache_expire_time = get_expire_time()
        normal_expire_time = v.pop('selling_rule', {}).get('end_time') or None
        seckill_expire_time = v.pop('seckill_selling_rule', {}).get('end_time') or None
        groupbuy_expire_time = v.pop('groupbuy_selling_rule', {}).get('end_time') or None
        multibuy_expire_time = v.pop('multibuy_selling_rule', {}).get('end_time') or None
        time_list = filter(None, [normal_expire_time, seckill_expire_time, groupbuy_expire_time, multibuy_expire_time])
        price_expire_time = time_list and min(time_list) or None
        if price_expire_time:
            delta_time = (price_expire_time - now).total_seconds()
            cache_expire_time = min(cache_expire_time, delta_time)

        json_v = json.dumps(v)
        try:
            service_info_list_cache.setex(cache_key, int(cache_expire_time), json_v)
        except:
            logging_exception()
    result.update(loss_result)
    return result

def _get_service_sku_id_to_price_info(service_item_ids, now):
    '''
    获取sku对应最优券
    :param service_item_ids:
    :param now:
    :return:
    '''
    result = {}
    cache_keys = []
    loss_cache_item_ids = []
    _sku_price_format = 'sku_price_v1:{}'
    for item_id in service_item_ids:
        cache_key = _sku_price_format.format(item_id)
        cache_keys.append(cache_key)
    price_list = service_info_list_cache.mget(cache_keys)
    for idx, siid in enumerate(service_item_ids):
        price_info = price_list[idx]
        if price_info:
            result[siid] = json.loads(price_info)
        else:
            loss_cache_item_ids.append(siid)
    if not loss_cache_item_ids:
        return result

    def get_expire_time():
        return random.randint(40, 60) * _service_info_cache_time_factor

    service_item_ids = loss_cache_item_ids
    loss_result = {}
    cache_logger.info('[CACHE_SKU_PRICE] service_item_price entry for count: %s, service_item_ids(%s)' % (len(service_item_ids), service_item_ids))
    siid_to_gbpi = Service.get_current_groupbuy_price_info_by_service_item_ids(
        service_item_ids=service_item_ids,
    )
    siid_to_cpi = Service.get_current_price_info_by_service_item_ids(
        service_item_ids=service_item_ids,
        now=now,
    )
    siid_to_mpi = Service.get_current_multibuy_price_info_by_service_item_ids(service_item_ids)

    siid_to_pi = {}
    dp_siids = []

    for siid, cpi in siid_to_cpi.items():
        if not cpi or siid in siid_to_pi:
            continue
        siid_to_pi[siid] = cpi
        dp_siids.append(siid)

    for siid, gbpi in siid_to_gbpi.items():
        if siid not in dp_siids:
            dp_siids.append(siid)

    def _format_price_info(service_item_ids_2_price_info):
        """ 如果下单价是拼团，那么增加groupbuy_price, 如果下单价是秒杀价，那么增加seckill_price，
            拼团和秒杀同时原有的gengmei_price替换为默认价格的gengmei_price
        """
        siid_to_default_gengmei_price = {}
        if dp_siids:
            siid_to_dpi = Service.get_default_price_info_by_service_item_ids(
                service_item_ids=dp_siids,
            )

            for siid, dpi in siid_to_dpi.items():
                if not dpi:
                    continue
                siid_to_default_gengmei_price[siid] = dpi["gengmei_price"]

        for siid, pi in service_item_ids_2_price_info.items():

            d = {
                "is_seckill": False,
                "is_groupbuy": False,
                'is_multibuy': False,
                'selling_rule': pi.get('selling_rule') or {},
                "service_id": pi['service_id'],
                "service_item_id": siid,

                'original_price': pi['original_price'],
                'gengmei_price': pi['gengmei_price'],
                "pre_payment_price": pi['pre_payment_price'],
                "post_price": max(0, pi['gengmei_price'] - pi['pre_payment_price']),
                'final_payment_price': max(0, pi['gengmei_price'] - pi['pre_payment_price']),
                'new_final_payment_price': max(0, pi['gengmei_price'] - pi['pre_payment_price']),
            }

            is_seckill = pi["price_type"] == ACTIVITY_TYPE_ENUM.SECKILL
            if is_seckill is True:
                d['is_seckill'] = True
                d['seckill_price'] = pi['gengmei_price']
                d['seckill_selling_rule'] = pi.get('selling_rule')
                if siid_to_default_gengmei_price.get(siid, 0) > d['seckill_price']:
                    d['gengmei_price'] = siid_to_default_gengmei_price[siid]

            # 拼团信息
            if siid_to_gbpi.get(siid):
                gbpi = siid_to_gbpi.get(siid)
                sku_no_groupbuy_price = siid_to_cpi.get(siid, {}).get("gengmei_price", 0)
                d['is_groupbuy'] = True
                d['groupbuy_selling_rule'] = gbpi.get('selling_rule')
                d['groupbuy_price'] = gbpi['gengmei_price']
                d['original_price'] = gbpi['original_price']
                d['pre_payment_price'] = gbpi['pre_payment_price']
                d['final_payment_price'] = max(0, gbpi['gengmei_price'] - gbpi['pre_payment_price'])
                d['new_final_payment_price'] = max(0, gbpi['gengmei_price'] - gbpi['pre_payment_price'])
                d['price_id'] = gbpi['id']
                d["sku_no_groupbuy_price"] = sku_no_groupbuy_price
                selling_rule=gbpi.get("selling_rule")
                d['groupbuy_nums']=selling_rule.get('groupbuy_nums',0)
                d['sale_limit']=gbpi.get('sale_limit',0)
                d['active_type']=selling_rule.get('active_type',0)

            from api.view.goods_service.service_utils import fill_discount_price
            # -------------------- calculate discount price ---------------------
            fill_discount_price(d, bool(d.get('groupbuy_price')))

            ##### 加入多买逻辑
            if siid_to_mpi.get(siid):
                multibuy_price = siid_to_mpi[siid]
                d['is_multibuy'] = True
                d['multibuy_selling_rule'] = multibuy_price.get('selling_rule') or {}
                d['multibuy_price'] = multibuy_price['single_price']
                d['total_multibuy_price'] = multibuy_price['gengmei_price']
                more_buy_count = (multibuy_price.get('selling_rule') or {}).get('more_buy_count') or 1
                d['pre_payment_price'] = multibuy_price['pre_payment_price'] / (1.0 * more_buy_count)
                d['new_final_payment_price'] = max(0, multibuy_price['gengmei_price'] - multibuy_price['pre_payment_price'])

            # gengmei_price, seckill_price, groupbuy_price, multibuy_price
            # 按序来
            order_keys = ['seckill_price', 'gengmei_price', 'groupbuy_price', 'multibuy_price']
            exist_price_keys = []
            for ok in order_keys:
                if ok in d:
                    exist_price_keys.append(ok)
            show_price_key = exist_price_keys[0]
            show_price_value = d[show_price_key]
            for epk in exist_price_keys:
                if d[epk] < show_price_value:
                    show_price_key = epk

            d['show_price_key'] = show_price_key
            tem_cal_price = {
                'gengmei_price': d[show_price_key],
                'groupbuy_price': d.get('groupbuy_price'),
                'pre_payment_price': d['pre_payment_price'],
                'price_id': d.get('price_id'),
                'service_id': d['service_id'],
                'service_item_id': d['service_item_id']
            }
            new_discount_info = fill_discount_price(tem_cal_price, is_groupbuy=bool(show_price_key=='groupbuy_price'))
            d['can_discount_new'] = new_discount_info.get('can_discount', False)
            d['discount_price_new'] = new_discount_info.get('discount_price')
            # if d['can_discount_new']:
            #     d['show_price_key'] = 'discount_price_new'
            d['default_price'] = max(
                d.get('seckill_price', 0),
                d.get('groupbuy_price', 0),
                d.get('multibuy_price', 0),
                siid_to_default_gengmei_price.get(siid, 0),
                d['gengmei_price']
            )

            loss_result.setdefault(siid, d)

    _format_price_info(siid_to_pi)
    for k, v in loss_result.items():
        if not v:
            continue
        cache_key = _sku_price_format.format(k)
        cache_expire_time = get_expire_time()
        normal_expire_time = v.pop('selling_rule', {}).get('end_time') or None
        seckill_expire_time = v.pop('seckill_selling_rule', {}).get('end_time') or None
        groupbuy_expire_time = v.pop('groupbuy_selling_rule', {}).get('end_time') or None
        multibuy_expire_time = v.pop('multibuy_selling_rule', {}).get('end_time') or None
        time_list = filter(None, [normal_expire_time, seckill_expire_time, groupbuy_expire_time, multibuy_expire_time])
        price_expire_time = time_list and min(time_list) or None
        if price_expire_time:
            delta_time = (price_expire_time - now).total_seconds()
            cache_expire_time = min(cache_expire_time, delta_time)

        json_v = json.dumps(v)
        try:
            service_info_list_cache.setex(cache_key, int(cache_expire_time), json_v)
        except:
            logging_exception()
    result.update(loss_result)
    return result

def get_toc_spu_info_list_mapping_by_spu_ids(service_ids, now=None):
    """
        如果 对应的spu不在线，或者spu对应的doctor不在线，也不会有结果（ 和输入了不存在的id一样）

        :param service_ids: [ 2, -1, 2, "1", 3 ] 假设 id为3 的数据不存在
        :param now:
        :return: { "2": { ...}, "1": {} }  注意只有key部分的id变成了str
    """
    result = {}
    service_id_list = add_double_eleven_image()
    if service_ids:
        cache_logger.info('[CACHE_TOC] main entry for count: %s, service_ids(%s)' % (len(service_ids), service_ids))
        now = datetime.datetime.now() if now is None else now


        #过滤出在线以及医生在线的美购
        all_service_ids = list(Service.objects.filter(
            id__in=service_ids, is_online=True, doctor__is_online=True,
        ).values_list('id', flat=True))

        if not all_service_ids:
            return result

        #service_info信息
        service_mapping_info = _get_service_id_to_service_info_dict(all_service_ids)
        #日记数量
        diary_mapping_count = get_service_id_to_diary_count_dict(all_service_ids)
        # service_id_to_topic_count_dict = _get_service_id_to_topic_count_dict(all_service_ids)
        #service ids转美券信息
        best_coupon_gift_mapping_info = coupon_manager.get_best_coupon_gift_info_for_service_card(all_service_ids)
        #取10条数据
        chunk_size = 10

        #分页返回数据
        for service_ids in [all_service_ids[i:i + chunk_size] for i in
                     range(0, len(all_service_ids), chunk_size)]:

            service_list = list(Service.objects.filter(
                id__in=service_ids
            ))
            # search_logger.info("ss---")
            # search_logger.info(ss)

            #获取价格信息列表
            price_mapping_info = _get_service_spu_id_to_price_info(service_ids, now)

            #servie_items的id列表
            serviceitem_list_ids = [price_info["service_item_id"] for sid, price_info in price_mapping_info.items()]

            #团购item的id列表
            groupbuy_serviceitem_list_ids = [price_info['selected_service_item_id'] for price_info in price_mapping_info.values()]

            serviceitem_list_ids.extend(groupbuy_serviceitem_list_ids)
            serviceitem_mapping_name = ServiceItem.get_items_name(serviceitem_list_ids)

            for service in service_list:
                service_id = service.id
                if service_id in price_mapping_info and service_id in service_mapping_info:

                    service_item_id = price_mapping_info[service_id]["service_item_id"]
                    selected_service_item_id = price_mapping_info[service_id]['selected_service_item_id']
                    service_info = service_mapping_info[service_id]

                    search_logger.info("servie item id----")
                    search_logger.info(service_item_id)
                    # group_info=group_sku_info[siid]
                    # search_logger.info(group_info)

                    double_eleven_image = ''
                    if service_id in service_id_list:
                        double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'
                    #通过service.id获取价格信息
                    price_info = price_mapping_info[service_id]

                    source = _build_base_source(service_info)

                    source['id'] = service_id  # 兼容，之后不应该被使用
                    source['service_id'] = service_id
                    source['service_item_id'] = service_item_id  # 兼容旧版本
                    source['service_item_name'] = ''.join(serviceitem_mapping_name.get(service_item_id, []))

                    source['is_price_range'] = True if source['is_multiattribute'] else False

                    source['selected_service_item_id'] = selected_service_item_id
                    source['selected_service_item_name'] = ''.join(serviceitem_mapping_name.get(service_item_id, []))

                    source['service_item_id_new'] = price_mapping_info[service_id].get("service_item_id_new")


                    source = _get_service_source(service, source)  # TODO：如果有时间的话之后再根据下面记录的数据来进行分解和批量获取

                    source['pre_payment_price'] = price_info["pre_payment_price"]
                    source['gengmei_price'] = price_info["gengmei_price"]
                    source['default_price'] = price_info['default_price']
                    source['groupbuy_nums']=price_info.get('groupbuy_nums')
                    source['sale_limit']=price_info.get('sale_limit')
                    source['active_type']=price_info.get('active_type')

                    original_price = str(price_info['original_price'])
                    source['original_price_display'] = original_price
                    source['original_price'] = original_price

                    #最后支付价格
                    post_price = price_info["post_price"]
                    final_payment_price = price_info['final_payment_price']
                    new_final_payment_price = price_info['new_final_payment_price']
                    is_stage = service_info["is_stage"]

                    support_installment_info = {}
                    if is_stage and post_price >= 1:
                        huabei_info = Service.get_huabei_period_info(post_price)
                        period = "12"
                        if period in huabei_info:
                            huabei = {
                                "fee": huabei_info[period]["fee"],
                                "pay": huabei_info[period]["pay"],
                                "period": huabei_info[period]["period"],
                            }
                            support_installment_info["huabei"] = huabei

                    installment_info = {}
                    if is_stage and final_payment_price >= 1:
                        huabei_info = Service.get_huabei_period_info(final_payment_price)
                        period = "12"
                        if period in huabei_info:
                            huabei = {
                                "fee": huabei_info[period]["fee"],
                                "pay": huabei_info[period]["pay"],
                                "period": huabei_info[period]["period"],
                            }
                            installment_info["huabei"] = huabei

                    new_installment_info = {}
                    if is_stage and new_final_payment_price >= 1:
                        huabei_info = Service.get_huabei_period_info(new_final_payment_price)
                        period = "12"
                        if period in huabei_info:
                            huabei = {
                                "fee": huabei_info[period]["fee"],
                                "pay": huabei_info[period]["pay"],
                                "period": huabei_info[period]["period"],
                            }
                            new_installment_info["huabei"] = huabei

                    source["support_installment_info"] = support_installment_info
                    source['installment_info'] = installment_info
                    source['new_installment_info'] = new_installment_info

                    source['is_groupbuy'] = price_info['is_groupbuy']
                    source['is_seckill'] = price_info["is_seckill"]
                    source['is_multibuy'] = price_info['is_multibuy']
                    source['sku_no_groupbuy_price']=price_info.get('sku_no_groupbuy_price', 0)

                    #如果有团购价
                    if source['is_groupbuy'] is True:
                        source['groupbuy_price'] = price_info['groupbuy_price']
                    if source['is_seckill'] is True:
                        source['seckill_status'] = SECKILL_STATUS.UNDERWAY
                        source['seckill_price'] = price_info['seckill_price']
                    if not source['is_seckill']:
                        # 对于SPU卡片，只有在秒杀和拼团的时候才会返回 service_item_id 和 service_item_name
                        # 让客户端可以跳转的时候选中sku，以及显示sku名称
                        source['service_item_id'] = 0
                        source['service_item_name'] = ""
                    if not (source['is_groupbuy'] or source['is_seckill']):
                        source['selected_service_item_id'] = 0
                        source['selected_service_item_name'] = ""
                    if not any([source['is_seckill'], source['is_groupbuy'], source['is_multibuy']]):
                        source['service_item_id_new'] = 0

                    source["best_coupon_gifts"] = best_coupon_gift_mapping_info.get(service_id, [])
                    source['diary_count'] = diary_mapping_count.get(service_id, 0)
                    source['topic_count'] = 0
                    source['double_eleven_image'] = double_eleven_image
                    """
                    多买需求新增字段
                    """
                    source['multibuy_price'] = price_info.get('multibuy_price', '')
                    source['ori_show_price_key'] = price_info.get('show_price_key')
                    source['show_price_key'] = SKU_SHOW_PRICE_TYPE.getDesc(price_info.get('show_price_key'))
                    # source['discount_price_new'] = price_info.get('discount_price_new')
                    source['can_discount_new'] = price_info.get('can_discount_new', False)
                    result[str(service_id)] = source

    return result


def get_toc_sku_info_list_mapping_by_sku_ids(service_item_ids, now=None):
    """
        如果 对应的spu不在线，或者spu对应的doctor不在线，也不会有结果（ 和输入了不存在的id一样）

        :param service_item_ids: [ 2, -1, 2, "1", 3 ] 假设 id为3 的数据不存在
        :param now:
        :return: { "2": { ...}, "1": {} }  注意只有key部分的id变成了str
    """
    result = {}
    service_id_list = add_double_eleven_image()

    cache_logger.info('[CACHE_TOC] main entry for count: %s, service_item_ids(%s)' % (len(service_item_ids), service_item_ids))
    if service_item_ids:
        now = datetime.datetime.now() if now is None else now

        all_siid_to_sid = {siid: sid for siid, sid in ServiceItem.objects.filter(
            id__in=service_item_ids, service__is_online=True, service__doctor__is_online=True,
        ).values_list('id', 'service_id')}

        all_siids = [k for k, v in all_siid_to_sid.items()]
        all_sids = [v for k, v in all_siid_to_sid.items()]

        if not all_sids:
            return result
        trace_info = init_tracer()
        _wrap = wrap_tracer(**trace_info)
        jobs = [
            gevent.spawn(_wrap(_get_service_id_to_service_info_dict), all_sids),
            gevent.spawn(_wrap(get_service_id_to_diary_count_dict), all_sids),
            gevent.spawn(_wrap(coupon_manager.get_best_coupon_gift_info_for_service_card), all_sids),
            gevent.spawn(_wrap(_get_service_sku_id_to_price_info), all_siids, now)
        ]
        gevent.joinall(jobs)

        # import ipdb
        # ipdb.set_trace()
        service_id_to_service_info_dict = jobs[0].value
        service_id_to_diary_count_dict = jobs[1].value
        service_id_to_best_coupon_gift_info = jobs[2].value
        id2price_for_sku = jobs[3].value or {}

        service_item_data = list(
            ServiceItem.objects.filter(
                id__in=all_siids
            ).select_related('service'))

        siid_to_items_name = ServiceItem.get_items_name(all_siids)

        for si in service_item_data:
            sid = si.service_id
            siid = si.id

            if siid in id2price_for_sku and sid in service_id_to_service_info_dict:
                double_eleven_image = ''
                if sid in service_id_list:
                    double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'
                service_info_dict = service_id_to_service_info_dict[sid]
                price_info = id2price_for_sku[siid]

                source = _build_base_source(service_info_dict)

                source['id'] = sid  # 兼容，之后不应该被使用
                source['service_id'] = sid
                source['service_item_id'] = siid
                source['service_item_name'] = ''.join(siid_to_items_name.get(siid, []))

                source = _get_service_sku_source(si.service, source)

                source['pre_payment_price'] = price_info["pre_payment_price"]
                source['gengmei_price'] = price_info["gengmei_price"]
                source['default_price'] = price_info['default_price']

                original_price_str = str(price_info['original_price'])
                source['original_price_display'] = original_price_str
                source['original_price'] = original_price_str

                post_price = price_info["post_price"]
                final_payment_price = price_info['final_payment_price']
                new_final_payment_price = price_info['new_final_payment_price']
                is_stage = service_info_dict["is_stage"]

                support_installment_info = {}
                if is_stage and post_price >= 1:
                    huabei_info = Service.get_huabei_period_info(post_price)
                    period = "12"
                    if period in huabei_info:
                        huabei = {
                            "fee": huabei_info[period]["fee"],
                            "pay": huabei_info[period]["pay"],
                            "period": huabei_info[period]["period"],
                        }
                        support_installment_info["huabei"] = huabei

                installment_info = {}
                if is_stage and final_payment_price >= 1:
                    huabei_info = Service.get_huabei_period_info(final_payment_price)
                    period = "12"
                    if period in huabei_info:
                        huabei = {
                            "fee": huabei_info[period]["fee"],
                            "pay": huabei_info[period]["pay"],
                            "period": huabei_info[period]["period"],
                        }
                        installment_info["huabei"] = huabei

                new_installment_info = {}
                if is_stage and new_final_payment_price >= 1:
                    huabei_info = Service.get_huabei_period_info(new_final_payment_price)
                    period = "12"
                    if period in huabei_info:
                        huabei = {
                            "fee": huabei_info[period]["fee"],
                            "pay": huabei_info[period]["pay"],
                            "period": huabei_info[period]["period"],
                        }
                        new_installment_info["huabei"] = huabei

                source["support_installment_info"] = support_installment_info
                source['installment_info'] = installment_info
                source['new_installment_info'] = new_installment_info

                source['is_seckill'] = price_info["is_seckill"]
                source["is_groupbuy"] = price_info["is_groupbuy"]
                source['sku_no_groupbuy_price']=price_info.get('sku_no_groupbuy_price')

                if source['is_seckill'] is True:
                    source['seckill_status'] = SECKILL_STATUS.UNDERWAY
                    source['seckill_price'] = price_info['seckill_price']

                if source['is_groupbuy'] is True:
                    source['groupbuy_price'] = price_info['groupbuy_price']
                    source['groupbuy_price_id'] = price_info['price_id']
                    source['groupbuy_nums']=price_info['groupbuy_nums']
                    source['sale_limit']=price_info.get('sale_limit')
                    source['active_type']=price_info.get('active_type')


                source["best_coupon_gifts"] = service_id_to_best_coupon_gift_info.get(sid, [])
                source['diary_count'] = service_id_to_diary_count_dict.get(sid, 0)
                source['topic_count'] = 0
                source['double_eleven_image'] = double_eleven_image

                """
                多买需求新增字段
                """
                source['multibuy_price'] = price_info.get('multibuy_price', '')
                source['show_price_key'] = SKU_SHOW_PRICE_TYPE.getDesc(price_info.get('show_price_key'))
                source['discount_price'] = price_info.get('discount_price')
                source['can_discount'] = price_info.get('can_discount') or False
                source['discount_price_new'] = price_info.get('discount_price_new')
                source['can_discount_new'] = price_info.get('can_discount_new', False)
                result[str(siid)] = source

    return result


def _get_service_sku_source(service, source):
    source['short_description'] = service.short_description
    source['detail_description'] = service.detail_description
    source['image_detail'] = service.image_detail
    source['service_type'] = service.service_type
    source['doctor_id'] = service.doctor_id
    source['payment_type'] = service.payment_type

    source['is_price_range'] = False  # SKU的信息，这里一定是False

    source['service_status_text'] = service.limit_hint if source['is_can_be_sold'] else u'抢完'

    source['is_available_soon'] = service.is_available_soon
    source['label'] = service.label

    source['tags'] = service.get_tags()  # 这个只有SKU才输出？SPU版本没有这个字段
    source['tip'] = service.first_tip  # 这个只有SKU才输出？SPU版本没有这个字段
    source['limit_hint'] = ""  # 之前SKU版本里面没有这个字段，估计之后可以去掉

    source["coupon_gifts"] = []  # 不再使用的字段

    return source


def _build_base_source(service_info_dict):
    source = {
        'city': service_info_dict['city'],
        'city_id': service_info_dict['city_id'],
        'is_multiattribute': service_info_dict['is_multiattribute'],
        'is_can_be_sold': service_info_dict['is_can_be_sold'],

        'project_type': service_info_dict['project_type_name'],
    }

    service_name = service_info_dict['service_name']
    source['name'] = service_name  # 兼容
    source['service_name'] = service_name

    source['is_operation'] = service_info_dict["is_operation"]

    service_image = service_info_dict['service_image']
    source['image_header'] = service_image
    source['service_image'] = service_image  # 兼容

    source['insurance_info'] = service_info_dict['insurance_info']
    source['is_support_insurance'] = True if service_info_dict['insurance_info'] else False

    source['hospital_lng'] = service_info_dict["hospital_lng"]
    source['hospital_lat'] = service_info_dict["hospital_lat"]

    source['has_video'] = service_info_dict.get('has_video', False)
    source['hospital_scale'] = service_info_dict.get('hospital_scale', HOSPITAL_SCALE.NONE)

    hospital_name = service_info_dict['hospital_name']
    source['hospital'] = hospital_name
    source['hospital_id'] = service_info_dict.get("hospital_id", "")
    source['hospital_name'] = hospital_name
    source['hospital_alias_name'] = service_info_dict.get('hospital_alias_name', '')
    source['organization_name'] = hospital_name  # 兼容

    source['doctor_id'] = service_info_dict.get('doctor_id', None)
    source['doctor_title'] = service_info_dict.get('doctor_title', '')
    source['doctor_name'] = service_info_dict['doctor_name']
    source['doctor_type'] = service_info_dict['doctor_type']

    source['operate_tag'] = service_info_dict['operate_tag']
    source['sell_amount'] = service_info_dict['sell_amount']
    source['doctor_badges'] = service_info_dict.get('doctor_badges', {})

    source['exchange_points_ceiling'] = 0  # 兼容，应该没有用了
    source['sell_num_limit'] = -1  # 兼容，应该没有用了
    source['renmai_period_price'] = 0  # 兼容，应该没有用了
    source['yirendai_period_price'] = 0  # 兼容，应该没有用了
    source['is_support_renmai_payment'] = False  # 兼容，应该没有用了
    source['is_support_yirendai_payment'] = False  # 兼容，应该没有用了

    return source


def get_seckill_info_for_service_home_list(ctx, special_id=None, city_id=None, sort_p=None):
    sort_params = {'special_id': special_id}
    if sort_p:
        sort_params.update(sort_p)

    # 虽然只需要3条，但是为了避免因为下单价不是专场价导致数据不足，多读取第一页的秒杀数据，
    load_count = 10
    result_count = 3

    result = filter_service_v2_seckill_list_for_special(
        ctx=ctx,
        order_by=SERVICE_ORDER_TYPE.DEFAULT,
        filters={'special_id': special_id},
        extra_filters=(),
        offset=0,
        count=load_count,
        sort_params=sort_params,
        current_city_id=city_id,
    )
    service_info_list = result['service_list']

    return service_info_list[:result_count]


def filter_service_v2_seckill_list_for_special(
        ctx,
        order_by=SERVICE_ORDER_TYPE.DEFAULT,
        filters=None,
        extra_filters=(),  # TODO: add reference to http://wiki.gengmei.cc/pages/viewpage.action?pageId=1050558
        offset=0, count=10,
        sort_params=None,
        current_city_id=None,
):
    from api.view.service import api_filter_service

    special_id = filters.pop('special_id', None)
    user = ctx.session.user

    user_city_tag_id = get_user_city_tagid(user.id)
    if user_city_tag_id is not None:
        user_city_tag_id = assert_uint(user_city_tag_id)

    country_tagid, city_tagid = get_location_tag_id_by_city_id(current_city_id)
    user_city_tag_id = city_tagid and city_tagid or user_city_tag_id
    if user_city_tag_id is not None:
        city = City.objects.get(tag_id=user_city_tag_id)
        user_city_info = {'user_city_id': city.id, 'user_city_tag_id': user_city_tag_id}
    else:
        user_city_info = None

    try:
        hash_key = _build_doris_search_seckill_hash_key(
            special_id, user_city_info, filters, order_by, sort_params, offset, count
        )
    except:
        logging_exception()
        hash_key = None
    # filter_result = _get_doris_search_seckill_info_from_cache(hash_key)
    filter_result = None
    if filter_result is None:
        filter_result = ctx.rpc_remote['doris/search/seckill'](
            special_id=special_id,
            user_city_info=user_city_info,
            filters=filters,
            sort_type=order_by,
            sort_params=sort_params,
            offset=offset,
            size=count,
        ).unwrap()
        _set_doris_search_seckill_info_to_cache(hash_key, filter_result)

    # 将返回sku信息补充上service id
    skus_info = filter_result['skus']

    _sku_ids = [v['sku_id'] for v in skus_info]

    sku_ids = []
    for sku_id_or_list in _sku_ids:
        if isinstance(sku_id_or_list, list):
            sku_ids.extend(sku_id_or_list)
        else:
            sku_ids.append(sku_id_or_list)

    sku_objs = ServiceItem.objects.filter(id__in=sku_ids, is_delete=False).values('id', 'service_id')
    sku_id_map_service_id = {v['id']: v['service_id'] for v in sku_objs}

    skus_info_list, assemble_sku_infos = [], []
    for sku_info in skus_info:
        src_type = sku_info['__src_type']
        sku_id_int_or_list = sku_info['sku_id']
        if isinstance(sku_id_int_or_list, list):
            sku_id = sku_id_int_or_list[0]
        else:
            sku_id = sku_id_int_or_list
        assemble_sku_infos.append({
            '__src_type': src_type,
            'sku_id': sku_id,
        })

    for sku_info in assemble_sku_infos:
        sku_id = sku_info['sku_id']
        if sku_id in sku_id_map_service_id:
            sku_info['service_id'] = sku_id_map_service_id[sku_info['sku_id']]
            skus_info_list.append(sku_info)

    service_info_list = get_seckill_sku_info(special_id, skus_info_list, user) if skus_info_list else []

    special, seckill_status = None, -1
    if special_id:
        now = datetime.datetime.now()
        special = Special.objects.get(id=special_id)
        if special.start_time <= now <= special.end_time:
            seckill_status = SERVICE_HOME_SECKILL_STATUS.ACTIVE
        elif now > special.end_time:
            seckill_status = SERVICE_HOME_SECKILL_STATUS.NONE
        elif now < special.start_time:
            seckill_status = SERVICE_HOME_SECKILL_STATUS.NOT_START

    [service_info.update({'seckill_status': seckill_status}) for service_info in service_info_list]
    result = {
        'service_list': service_info_list,
        'rank_mode': filter_result['rank_mode'],
        'special_data': special.data()
    }

    return result


def set_seckill_sku_info_to_pre_write_cache(special_id, sku_id, seckill_sku_info):
    """
        如果sku_id是不能卖的，传入{}，否则传入info的dict
    """
    cache_key = _seckill_info_cache_key_format.format(special_id, sku_id)

    now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    seckill_sku_info["__pre_write_time"] = now_str

    cache_time = 60 * 30  # 30min
    service_info_list_cache.setex(cache_key, cache_time, json.dumps(seckill_sku_info))


def _get_pre_write_cache_write_time(special_id, sku_id):
    cache_key = _seckill_info_cache_key_format.format(special_id, sku_id)
    c = service_info_list_cache.get(cache_key)
    time_str = None
    if c:
        try:
            cd = json.loads(c)
            time_str = cd.pop("__pre_write_time", None)
        except:
            logging_exception()

    return time_str


def _get_seckill_sku_info_from_pre_write_cache(special_id, sku_ids):
    """
        如果一个key不在result，代表缓存中没有数据，如果在result但是为None，代表sku不能卖
    :param special_id:
    :param skus_info:
    :return: { str(sku_id): None, str(sku_id): seckill_dict_info }
    """
    if _load_seckill_info_from_pre_write_cache and special_id and sku_ids:
        result = {}

        cache_key_list = []
        for siid in sku_ids:
            sku_id = str(siid)
            cache_key = _seckill_info_cache_key_format.format(special_id, sku_id)
            cache_key_list.append(cache_key)

        sis = service_info_list_cache.mget(cache_key_list)

        for index in range(len(sku_ids)):
            si = sis[index]
            siid = str(sku_ids[index])

            if si is not None:
                try:
                    cd = json.loads(si)
                    cd.pop("__pre_write_time", None)
                    if cd:
                        result[siid] = cd
                    else:
                        result[siid] = None
                except:
                    logging_exception()

        return result
    else:
        return {}


def get_current_price_info_by_sku_ids(service_item_ids):
    """
        根据skuid，获取1. SPU能卖 2.还有库存并且没有被删除的 SKU的 当前价格信息
        返回 {  1: None, 2: {'price_info'}, -999:None }
    """
    all_service_items_data = list(ServiceItem.objects.filter(
        id__in=service_item_ids, is_delete=False, sku_stock__gt=0
    ).values_list('id', 'service_id'))

    all_service_item_ids = [siid for siid, sid in all_service_items_data]
    all_service_ids = [sid for siid, sid in all_service_items_data]
    service_item_id_to_service_id = {siid: sid for siid, sid in all_service_items_data}

    siid_to_price_info = Service.get_current_price_info_by_service_item_ids(all_service_item_ids)

    all_services = list(Service.objects.select_related('doctor').filter(id__in=all_service_ids))

    all_can_be_sold_service_ids = {s.id for s in all_services if s.is_can_be_sold(
        check_any_sku_has_stock=False
    )}

    result = {}
    for siid in service_item_ids:
        pi = None
        if siid in service_item_id_to_service_id and service_item_id_to_service_id[siid] in all_can_be_sold_service_ids:
            pi = siid_to_price_info.get(siid, None)
        result[siid] = pi

    return result


def _get_seckill_price_info_by_service_item_ids(service_item_ids, special_id):
    """
    :param service_item_ids:
    :param special_id:
    :return:
    """
    not_in_cache_service_item_ids = []
    service_item_id_to_seckill_price_info_dict = {}
    for siid in service_item_ids:
        cache_key = _service_item_seckill_price_info_cache_key_format.format(special_id, siid)
        seckill_price_info = service_info_list_cache.get(cache_key)
        if seckill_price_info is None:
            not_in_cache_service_item_ids.append(siid)
        else:
            service_item_id_to_seckill_price_info_dict[siid] = json.loads(seckill_price_info)

    result = Service.get_seckill_price_info_by_service_item_ids(not_in_cache_service_item_ids, special_id)

    for siid in not_in_cache_service_item_ids:
        json_cache_data = json.dumps(result[siid], default=str) if result.get(siid, None) is not None else json.dumps({})
        cache_key = _service_item_seckill_price_info_cache_key_format.format(special_id, siid)
        service_info_list_cache.set(cache_key, json_cache_data, ex=5)
    result.update(service_item_id_to_seckill_price_info_dict)
    return result


def _get_current_price_info_by_service_item_ids(service_item_ids, now):
    """

    :param service_item_ids:
    :param now:
    :return:
    """
    not_in_cache_service_item_ids = []
    service_item_id_to_current_price_info_dict = {}
    for siid in service_item_ids:
        cache_key = _service_item_current_price_info_cache_key_format.format(siid)
        current_price_info = service_info_list_cache.get(cache_key)
        if current_price_info is None:
            not_in_cache_service_item_ids.append(siid)
        else:
            service_item_id_to_current_price_info_dict[siid] = json.loads(current_price_info)

    result = Service.get_current_price_info_by_service_item_ids(
        service_item_ids=not_in_cache_service_item_ids,
        now=now,
    )

    for siid in not_in_cache_service_item_ids:
        cache_key = _service_item_current_price_info_cache_key_format.format(siid)
        json_cache_data = json.dumps(result[siid], default=str) if result.get(siid, None) is not None else json.dumps({})
        service_info_list_cache.set(cache_key, json_cache_data, ex=5)
    result.update(service_item_id_to_current_price_info_dict)
    return result


def _get_default_price_info_by_service_item_ids(service_item_ids):
    """

    :param service_item_ids:
    :return:
    """
    not_in_cache_service_item_ids = []
    service_item_id_to_default_price_info_dict = {}
    for siid in service_item_ids:
        cache_key = _service_item_default_price_info_cache_key_format.format(siid)
        default_price_info = service_info_list_cache.get(cache_key)
        if default_price_info is None:
            not_in_cache_service_item_ids.append(siid)
        else:
            service_item_id_to_default_price_info_dict[siid] = json.loads(default_price_info)

    result = Service.get_default_price_info_by_service_item_ids(service_item_ids=not_in_cache_service_item_ids)

    for siid in not_in_cache_service_item_ids:
        cache_key = _service_item_default_price_info_cache_key_format.format(siid)
        json_cache_data = json.dumps(result[siid], default=str) if result.get(siid, None) is not None else json.dumps({})
        service_info_list_cache.set(cache_key, json_cache_data, ex=5)
    result.update(service_item_id_to_default_price_info_dict)
    return result


def _build_doris_search_seckill_hash_key(special_id, user_city_info, filters, order_by, sort_params, offset, count):
    """根据doris使用的筛选条件做hash 生成cache key 缓存结果"""
    if offset >= 10 and count < 20:  # 正常情况下只缓存第一页
        return None
    if offset > 20:  # 针对陌陌需要特殊处理  需要缓存两页
        return None

    if order_by == SERVICE_ORDER_TYPE.DISTANCE:
        return None

    if isinstance(user_city_info, dict):
        user_city_id = user_city_info.get('user_city_id')
    else:
        user_city_id = None

    new_sort_params = OrderedDict()
    if isinstance(sort_params, dict):
        if sort_params.get('special_id'):
            new_sort_params['special_id'] = sort_params['special_id']

    new_filters = OrderedDict()
    if isinstance(filters, dict):
        if filters.get('tag_ids') is not None:
            new_filters['tag_ids'] = filters['tag_ids']
        if filters.get('area_tag_id') is not None:
            new_filters['area_tag_id'] = filters['area_tag_id']

    hash_dict = OrderedDict([
        ('special_id', special_id),
        ("user_city_id", user_city_id),
        ("filters", new_filters),
        ("order_by", order_by),
        ("sort_params", new_sort_params),
        ("offset", offset),
        ("count", count),
    ])

    return pickle.dumps(hash_dict)


def _get_doris_search_seckill_info_from_cache(hash_key):
    if hash_key is None:
        return None
    cache_key = _doris_search_seckill_info_cache_key_format.format(hash_key)
    result = service_info_list_cache.get(cache_key)
    if result is None:
        return None
    return json.loads(result)


def _set_doris_search_seckill_info_to_cache(hash_key, data, expire=None):
    if hash_key is None:
        return
    if expire is None:
        expire = random.randint(2, 3) * 60
    cache_key = _doris_search_seckill_info_cache_key_format.format(hash_key)
    service_info_list_cache.set(cache_key, json.dumps(data), ex=expire, nx=True)
