# coding=utf-8

from collections import deque, OrderedDict
import itertools
import json
import random
from operator import itemgetter
import logging
from django.conf import settings
from gm_types.gaia import ADVER_MANAGEMENT_POSITION_TYPE, DOCTOR_TYPE, SERVICE_ORDER_TYPE
from gm_types.doris import STRATEGY_TYPE
from gm_types.doris import RANK_MODE
from datetime import datetime
from search.utils.doris import get_params

from api.models.types import SERVICE_ORDER_TYPE
from api.models import City
from hippo.tool.merchant_tool import doctor_merchant_map
from rpc.decorators import cache_page
from rpc.context import get_rpc_remote_invoker
from rpc.tool.log_tool import search_logger
from search.interpose.service_interpose import search_interpose
from search.interpose.service_interpose import filter_interpose
from .common import area_tag_id_filter
from .es import get_es, get_highlight, es_index_adapt, tzlc, get_highlight_query

# TODO: this param is heavily depending on
# elasticsearch setting "index.max_result_window"
MAX_SERVICE_COUNT = 9999

SMARTRANK_SORT = (
    SERVICE_ORDER_TYPE.DEFAULT_SPECIAL,
    SERVICE_ORDER_TYPE.REGION,
    SERVICE_ORDER_TYPE.DEFAULT,
    SERVICE_ORDER_TYPE.DEFAULT_REALTIME,
    SERVICE_ORDER_TYPE.DIARY_RECOMMENDED,
    SERVICE_ORDER_TYPE.DEFAULT2,
    SERVICE_ORDER_TYPE.NEARBY_REGION,
    SERVICE_ORDER_TYPE.HIGHEST_SALES_FOR_SMART_RANK2,
)


def process_filters(filters):
    # 过滤器部分
    f = [
        {'term': {'is_online': True}},  # 只返回上线的福利
    ]
    now = tzlc(datetime.now())

    for k, v in filters.items():
        if k == 'ids' and isinstance(v, list):
            f.append({
                'terms': {'id': v}
            })
        elif k == 'province_tag_id' and isinstance(v, (long, int)):
            f.append({
                'bool': {
                    'should': [
                        {'term': {'doctor.hospital.city_province_tag_id': v}},
                        {'term': {'hospital.city_province_tag_id': v}},
                    ]
                }
            })
        elif k == 'city_tag_id' and isinstance(v, (long, int)):
            f.append({
                'bool': {
                    'should': [
                        {'term': {'doctor.hospital.city_tag_id': v}},
                        {'term': {'hospital.city_tag_id': v}},
                    ]
                }
            })
        elif k == 'city_tag_ids' and isinstance(v, list):
            f.append({
                'bool': {
                    'should': [
                        {'terms': {'doctor.hospital.city_tag_id': v}},
                        {'terms': {'hospital.city_tag_id': v}},
                    ]
                }
            })
        elif k == 'area_tag_id' and isinstance(v, (long, int, str, unicode)):
            f.append(area_tag_id_filter(['doctor.hospital.', 'hospital.'], v))
        elif k == 'bodypart_tag_id':
            # compatibility
            if isinstance(v, list) and v:
                f.append({
                    'terms': {'closure_tag_ids': v}
                })
            elif isinstance(v, (int, long)):
                f.append({
                    'term': {'closure_tag_ids': v}
                })
        elif k == 'channel':
            f.append({
                'term': {'channel': v}
            })
        elif k == 'doctor_id':
            f.append({
                'term': {'doctor.id': v},
            })
        elif k == 'doctor_ids' and isinstance(v, list):
            f.append({
                'terms': {'doctor.id': v}
            })
        elif k == 'hospital_id' and isinstance(v, (str, unicode)):
            f.append({
                'bool': {
                    'should': [
                        {'term': {'doctor.hospital.id': v}},
                        {'term': {'hospital.id': v}},
                    ]
                }
            })
        elif k == 'tag_ids' and isinstance(v, list) and v:
            f.append({
                'terms': {'closure_tag_ids': v},
            })
        elif k == 'special_id':
            f.append({
                'term': {'special_rank.special_id': v},
            })
        elif k == 'special_ids_and' and isinstance(v, list) and v:
            f.append({
                'terms': {'special_rank.special_id': v, 'execution': 'and'},
            })
        elif k == 'is_floor_price' and isinstance(v, bool):
            f.append({
                'term': {'is_floor_price': v}
            })
        elif k == 'share_get_cashback' and isinstance(v, bool):
            f.append({
                'term': {'share_get_cashback': v}
            })
        elif k == 'rating_gte' and isinstance(v, (float, int, long)):
            f.append({
                'range': {'rating': {'gte': v}}
            })
        elif k == 'tip':
            f.append({
                'term': {'tip': v}
            })
        elif k == 'tip_list_and':
            f.append({
                'terms': {'tip': v, 'execution': 'and'}
            })
        elif k == 'famous_doctor' and isinstance(v, bool) and v:
            f.append({
                'term': {'doctor.famous_doctor': True}
            })
        elif k == 'hospital_type':
            f.append({
                'term': {'doctor.hospital.hospital_type': v}
            })
        elif k == 'is_seckill' and isinstance(v, bool):
            f.append({
                'nested': {
                    'path': 'seckill_time',
                    'query': {
                        'bool': {
                            'filter': [{
                                'range': {'seckill_time.start_time': {'lte': now}}
                            }, {
                                'range': {'seckill_time.end_time': {'gt': now}}
                            }]
                        }
                    }
                }
            })
        elif k == 'gengmei_price_interval' and isinstance(v, list) and v:
            periodic_price_interval = []
            for interval in v:
                assert isinstance(interval, dict)
                for op in interval.keys():
                    assert op in ['le', 'lte', 'ge', 'gte']
                # legal price interval list
                periodic_price_interval.append({
                    'range': {'periodic_price.price': interval}
                })

            periodic_time_filter = [{
                'range': {'periodic_price.start_time': {'lte': now}}
            }, {
                'range': {'periodic_price.end_time': {'gt': now}}
            }]

            query = {
                'nested': {
                    'path': 'periodic_price',
                    'query': {
                        'bool': {
                            'should': periodic_price_interval,
                            'filter': periodic_time_filter,
                        }
                    }
                }
            }
            f.append(query)
        elif k == 'is_stage' and isinstance(v, bool):
            f.append({
                'term': {'is_fenqi': v}
            })
        elif k == 'is_fenqi' and isinstance(v, bool):
            f.append({
                'term': {'is_fenqi': v}
            })
        elif k == 'is_insurance' and isinstance(v, bool):
            f.append({
                'term': {'is_insurance': v}
            })
        elif k == 'adver_position':  # 广告展示位置
            f.append({
                'term': {'advertise_position': v}
            })
            if v == ADVER_MANAGEMENT_POSITION_TYPE.SREACH_RESULT and 'adver_word' in filters:
                filter_searchword = filters['adver_word']
                f.append({
                    'term': {'advertise_searchwords': filter_searchword}
                })
            else:
                filter_searchword = None

            if 'advertise_tag_ids' in filters:
                ad_tag_ids = filters['advertise_tag_ids']
            else:
                ad_tag_ids = None

            if "advertise_user_city_tag_id" in filters:
                advertise_user_city_tag_id = filters["advertise_user_city_tag_id"]
            else:
                advertise_user_city_tag_id = None

            f.append({
                'script': {
                    'script_file': 'filter_service-advertise2',
                    'lang': settings.ES_SCRIPT_LANG,
                    'params': {
                        'adver_position': v,
                        'adver_searchword': filter_searchword,
                        'ad_tag_ids': ad_tag_ids,
                        "advertise_user_city_tag_id": advertise_user_city_tag_id
                    }
                }
            })
        elif k == 'adver_word':
            pass  # see above
        elif k == 'advertise_tag_ids':
            pass
        elif k == 'advertise_user_city_tag_id':
            pass
        # elif k == 'advertise_tag_ids' and isinstance(v, list) and v:
        #     f.append({
        #         'terms':{'advertise_tag_ids':v},
        #     })
        elif k == 'ordered_user_id':
            f.append({
                'term': {'ordered_user_ids': v},
            })
        elif k == 'service_type':
            f.append({
                'term': {'service_type': v},
            })
        elif k == 'hospital_brand':
            should = []
            if 'city_count' in v:
                should.append(
                    {'range': {'doctor.hospital.city_count': {'gte': v['city_count']}}}
                )
            if 'chain_count' in v:
                should.append(
                    {'range': {'doctor.hospital.chain_count': {'gte': v['chain_count']}}}
                )
            if 'is_high_quality' in v:
                should.append(
                    {'term': {'doctor.hospital.is_high_quality': v['is_high_quality']}}
                )
            f.append({
                'bool': {
                    'should': should
                }
            })
        elif k == 'doctor_title':
            f.append({
                'terms': {'doctor.title': v}
            })
        elif k == 'hospital_size':
            should = []
            for condition in v:
                should.append(
                    {'range': {'doctor.hospital.area_count': {'gte': condition['gte'], 'lt': condition['lt']}}}
                )
            f.append({
                'bool': {
                    'should': should
                }
            })
        # elif k == 'hospital_oversea':
        #     f.append({
        #         'term':{'doctor.hospital.is_oversea': v},
        #     })
        elif k == 'hospital_type_list':
            f.append({
                'terms': {'doctor.hospital.hospital_type2': v},
            })
        elif k == 'service_coupons':
            f.append({
                'range': {'gift_rank': {'gte': v}}
            })
        elif k == 'service_ids':
            if isinstance(v, list):
                f.append({
                    'terms': {'id': v},
                })
            elif isinstance(v, (int, long)):
                f.append({
                    'term': {'id': v}
                })
        elif k == 'is_online':
            f.append({'term': {'is_online': v}})
        else:
            if settings.DEBUG:
                raise ValueError("unexpected filter: {}".format(k))

    return f


def process_sorting(sort_type, sort_params, append_score=False):
    sorting = [  # 所有福利排序，下沉的与不可售的都需要放后面
        {'_script': {
            'lang': settings.ES_SCRIPT_LANG,
            'script_file': 'sort_service-sink',
            'type': 'number',
            'order': 'asc',
        }},

        {'_script': {
            'lang': settings.ES_SCRIPT_LANG,
            'script_file': 'sort_service-time-valid',
            'type': 'number',
            'order': 'desc',
        }},

    ]

    # 机构罚单下沉
    sorting += [
        {
            '_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-sink-by-org',
                'type': 'number',
                'order': 'asc',
            }
        }
    ]

    if sort_params.get('query') and sort_type != SERVICE_ORDER_TYPE.DORIS_SMART:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-tag-first',
                'type': 'number',
                'params': {
                    'query': sort_params['query'],
                },
                'order': 'desc',
            }},
        ]

    if sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_SALES:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'sales_count': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_LOWEST_PRICE:
        now_str = tzlc(datetime.now()).isoformat()
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-price-3',
                'type': 'number',
                'order': 'asc',
                'params': {
                    'now_str': now_str
                }
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.CASE_COUNT:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'case_count': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT_SPECIAL:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default-special',
                'type': 'number',
                'params': {
                    'special_id': sort_params['special_id'],
                },
                'order': 'asc',
                '_cache': True,
            }},
            {'ordering': {'order': 'asc'}},
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.LOWEST_PRICE_SPECIAL:
        now_str = tzlc(datetime.now()).isoformat()
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-price-3',
                'type': 'number',
                'order': 'asc',
                'params': {
                    'now_str': now_str
                }
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.REGION:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['city_tag_id'] if ('city_tag_id' in sort_params) else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            # {'recommend_rank.is_recommend':{'order':'desc'}},
            # {'recommend_rank.rank':{'order':'asc'}},
            {'smart_rank2': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DISTANCE:
        # NOTE: 20161124, service tab order by time in zone detail page
        # use this sort type, since it's the closest alg.
        # sort_params will be empty {}
        # if this sort type alg needs to be updted, pls let me know:
        # pengfei.x
        if ('lon' in sort_params and sort_params['lon'] is not None) and (
                'lat' in sort_params and sort_params['lat'] is not None):  # check if param exists
            sorting.append({'_geo_distance': {
                'location': {'lon': sort_params['lon'], 'lat': sort_params['lat']},
                'order': 'asc',
                'unit': 'km',
            }})

        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_EVALUATE:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'rating': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.POPULARITY:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'pv': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT:
        sorting += [
            {
                '_script': {
                    'lang': settings.ES_SCRIPT_LANG,
                    'script_file': 'sort_service-region-related',
                    'type': 'number',
                    'params': {
                        'user_city_tag_id': sort_params.get('user_city_tag_id', -1),
                        'in_whitelist': int(sort_params.get('in_whitelist', False))
                    },
                    'order': 'desc',
                    '_cache': True
                }
            },
            {"is_promote": {"order": "desc"}},
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT2:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ADVERTISE_ORDER:
        sorting = [  # force not concerning is_can_be_sold or is_sink
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-advertise',
                'type': 'number',
                'params': {
                    'adver_position': sort_params['adver_position'],
                    'adver_searchword': sort_params['adver_word'] if 'adver_word' in sort_params else None,
                },
                'order': 'asc',
            }}
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DIARY_RECOMMENDED:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-region',
                'type': 'number',
                'params': {
                    'city_tag_id': sort_params['city_tag_id'] if 'city_tag_id' in sort_params else -1,
                    'city_province_tag_id': sort_params[
                        'city_province_tag_id'] if 'city_province_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'smart_rank2': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DORIS_SMART:
        doris_params = get_params(STRATEGY_TYPE.DOCTOR_ORDER)
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-doris-smart',
                'type': 'number',
                'params': {
                    'query': sort_params['query'],
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                    'weight_a': doris_params['a'] if doris_params.get('a') else 1,
                    'weight_b': doris_params['b'] if doris_params.get('b') else 1,
                    'weight_c': doris_params['c'] if doris_params.get('c') else 1,
                    'weight_d': doris_params['d'] if doris_params.get('d') else 1,
                    'weight_e': doris_params['e'] if doris_params.get('e') else 1,
                },
                'order': 'desc',
                '_cache': True,
            }},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ORDERED_USER:
        sorting += [
            {'sales_count': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_PRICE:
        now_str = tzlc(datetime.now()).isoformat()
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-price-3',
                'type': 'number',
                'order': 'desc',
                'params': {
                    'now_str': now_str
                }
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DOCTOR_CUSTOMIZE_SORT:  # 医生自定义排序
        sorting += [
            {'doctor_customize_sort': {'order': 'asc'}},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.HOSPITAL_CUSTOMIZE_SORT:  # 医院自定义排序
        sorting += [
            {'hospital_customize_sort': {'order': 'asc'}},
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.NEARBY_REGION:
        sorting += [
            {
                '_script': {
                    'lang': settings.ES_SCRIPT_LANG,
                    'script_file': 'sort_service-region-related',
                    'type': 'number',
                    'params': {
                        'user_city_tag_id': sort_params.get('user_city_tag_id', -1),
                        'in_whitelist': int(sort_params.get('in_whitelist', False))
                    },
                    'order': 'desc'
                }
            },
            {'smart_rank2': {'order': 'desc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.HIGHEST_SALES_FOR_SMART_RANK2:
        sorting += [
            {'_script': {
                'lang': settings.ES_SCRIPT_LANG,
                'script_file': 'sort_service-default',
                'type': 'number',
                'params': {
                    'user_city_tag_id': sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1,
                },
                'order': 'desc',
                '_cache': True,
            }},
            {'smart_rank2': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]

    if append_score:
        sorting.append('_score')

    return sorting


def search_service(
        query='',
        offset=0, size=5,
        sort_type=SERVICE_ORDER_TYPE.DEFAULT,
        filters=None, sort_params=None
):
    """
    @param query: 搜索词
    @param offset: 偏移量
    @param size: 返回个数
    @param sort_type: 排序方式[sale_count<销量>, price<更美价>, start_time<上架时间>]
    @param filters: 筛选器{"province_tag_id":<省份tag id>, "bodypart_tag_id":<一级tag id>}

    福利搜索
    搜索域:[
        1.一句话描述
        2.医生
        3.地点
        4.医院
        5.tag
        ]
    默认排序:[
        本地在前
        匹配度
        展示顺序(ordering)，小的在前
        最后上架时间，新的在前
        ]
    其它排序:[
        销量，从高到低
        价格，更美价从低到高
        最新上架，上架时间从新到旧
        ]
    """
    # 参数验证
    size = min(size, settings.COUNT_LIMIT)
    filters = filters or {}
    sort_params = sort_params or {}
    # adaptibility
    if 'special_id' in filters and 'special_id' not in sort_params:
        sort_params['special_id'] = filters['special_id']

    # 搜索关键字部分
    # 搜索域
    multi_fields = {
        'short_description': 8,
        'name': 7,
        'doctor.name': 4,
        'doctor.hospital.name': 3,
        'hospital.name': 3,
        'doctor.hospital.city_name': 2,
        'doctor.hospital.city_province_name': 2,
        'closure_tags': 2,  # 5.9版 搜索所有tag
        'doctor.hospital.officer_name': 3  # 搜索机构管理者
        # 'item_wiki_tags':2,
        # 'detail_description':1,   # 5.3版 去掉搜索域: 项目简介
    }
    fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.iteritems()]

    multi_match = {
        'query': query,
        'type': 'cross_fields',
        'operator': 'and',
        'fields': fields,
    }

    q = {
        'multi_match': multi_match,
    }

    # 过滤器部分
    f = process_filters(filters=filters)

    q = {
        'query': {'filtered': {
            'query': q,
            'filter': {'bool': {'must': f}}
        }}
    }
    sort_params['query'] = query
    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params,
                                append_score=True)

    # 高亮部分
    q['highlight'] = get_highlight(multi_fields.keys())
    # q['track_scores'] = True
    #
    res = search_interpose(offset=offset, size=size,
                           keyword=query, filters=filters, sort_type=sort_type
                           )
    if res:
        return res

    es = get_es()
    index = es_index_adapt(
        index_prefix=settings.ES_INDEX_PREFIX,
        doc_type='service',
        rw='read'
    )
    res = es.search(
        index=index,
        doc_type='service',
        timeout=settings.ES_SEARCH_TIMEOUT,
        body=q,
        from_=offset,
        size=size)
    # tmp = res
    # res = {
    #     'service_ids':[int(s['_id']) for s in tmp['hits']['hits']],
    #     'total_count':tmp['hits']['total'],
    # }
    if sort_type in SMARTRANK_SORT:
        rank_mode = RANK_MODE.CPC
    else:
        rank_mode = RANK_MODE.DEFAULT
    res["rank_mode"] = rank_mode

    return res

def search_service_smart_rank(f, offset=0, size=1000):

    # 过滤器部分

    q = {"query": {"bool":{"must":{"terms":{"id":f}}}}}

    es = get_es()
    index = es_index_adapt(
        index_prefix=settings.ES_INDEX_PREFIX,
        doc_type='service',
        rw='read'
    )
    res = es.search(
        index=index,
        doc_type='service',
        timeout=settings.ES_SEARCH_TIMEOUT,
        body=q,
        from_=offset,
        size=size)

    return res

def filter_service(
        offset=0, size=5,
        sort_type=SERVICE_ORDER_TYPE.DEFAULT,
        filters=None, sort_params=None,
        group_by=None, skip_size_check=False,
        raw_result=False, query=""
):
    # 参数验证
    if not skip_size_check:
        size = min(size, settings.COUNT_LIMIT)
    filters = filters or {}
    sort_params = sort_params or {}
    # adaptibility
    if 'special_id' in filters and 'special_id' not in sort_params:
        sort_params['special_id'] = filters['special_id']

    # 过滤器部分
    f = process_filters(filters=filters)
    if query:
        # 外显的美购需要根据关键词显示出来
        multi_fields = {
            'short_description': 8,
            'name': 7,
            'closure_tags': 2,  # 5.9版 搜索所有tag
            # 'item_wiki_tags':2,
            # 'detail_description':1,   # 5.3版 去掉搜索域: 项目简介
        }
        fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.iteritems()]

        multi_match = {
            'query': query,
            'type': 'cross_fields',
            'operator': 'and',
            'fields': fields,
        }

        q = {
            'query': {'filtered': {
                'query': {'multi_match': multi_match},
                'filter': {'bool': {'must': f}}
            }}
        }
    else:
        q = {
            'query': {'filtered': {
                'query': {'match_all': {}},
                'filter': {'bool': {'must': f}}
            }}
        }

    if 'adver_position' in filters and 'adver_word' in filters:
        query = filters['adver_word']

    # 特殊逻辑 对于广告 需要返回具体的广告ID
    if 'adver_position' in filters:
        q['script_fields'] = {
            'adver_id': {
                'script': {
                    'file': 'field_service-advertise',
                    'params': {
                        'adver_position': filters['adver_position'],
                        'adver_searchword': filters['adver_word'] if 'adver_word' in filters else None,
                    }
                }
            }
        }
    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params)
    q['highlight'] = get_highlight_query(["short_description"], query)

    index = es_index_adapt(
        index_prefix=settings.ES_INDEX_PREFIX,
        doc_type='service',
        rw='read'
    )
    search_kwargs = {
        'index': index,
        'doc_type': 'service',
        'timeout': settings.ES_SEARCH_TIMEOUT,
        'body': q,
        'from_': offset,
        'size': size,
    }
    search_logger.info('[ES_SEARCH] get es data by prms: %s' % (q))
    # totally different when grouping by
    if group_by == 'doctor_id':
        search_kwargs['from_'] = 0
        search_kwargs['size'] = MAX_SERVICE_COUNT
        search_kwargs['_source'] = ['id', 'doctor.id']

    es = get_es()
    res = es.search(**search_kwargs)
    if raw_result:  # if you wants to get score information
        return res

    if group_by == 'doctor_id':
        service_info = {}
        for rank, s in enumerate(res['hits']['hits']):
            _source = s['_source']
            service_id = _source['id']
            if 'doctor' not in _source:
                continue

            doctor_id = _source['doctor']['id']
            if _source['doctor']['id'] not in service_info:
                service_info[doctor_id] = {
                    'doctor_id': doctor_id,
                    'rank': rank,
                    'service_ids': []
                }
            service_info[doctor_id]['service_ids'].append(service_id)

        service_info = service_info.values()
        service_info.sort(key=lambda x: x['rank'])
        service_info = service_info[offset:offset + size]
        for service in service_info:
            del service['rank']

        res = {
            'service_ids': service_info,
            # 'total_count':
        }

    else:
        tmp = res
        # zhanghang 注销 2019-4-10
        # res = {
        #     'service_ids': [int(s['_id']) for s in tmp['hits']['hits']],
        #     'total_count': tmp['hits']['total'],
        #     'doctor_ids': [s['_source']['doctor']['id'] for s in tmp['hits']['hits'] if '_source' in s],
        # }
        # res.update(sku_list=[{
        #     'city_tag_id': s['_source']['doctor']['hospital']['city_tag_id'],
        #     'nearby_city_tags': s['_source'].get('nearby_city_tags', [])
        # } for s in tmp['hits']['hits'] if '_source' in s])
        # end zhanghang 注销 2019-4-10

        # zhanghang 新增 2019-4-10
        res = {
            'total_count': tmp['hits']['total']
        }
        _inner_list_doctor_ids = list()
        _inner_list_service_ids = list()
        _inner_list_sku_id = list()
        _inner_service_high_light = dict()
        for single_hit in tmp['hits']['hits']:
            if '_source' in single_hit:
                _inner_list_doctor_ids.append(single_hit['_source']['doctor']['id'])
                _inner_list_sku_id.append({
                    'city_tag_id': single_hit['_source']['doctor']['hospital']['city_tag_id'],
                    'nearby_city_tags': single_hit['_source'].get('nearby_city_tags', [])
                })
            _inner_list_service_ids.append(int(single_hit['_id']))
            if single_hit.get('highlight'):
                _inner_service_high_light.update({single_hit['_id']: single_hit.get('highlight')})

        res.update({
            'service_ids': _inner_list_service_ids,
            'doctor_ids': _inner_list_doctor_ids,
            'sku_list': _inner_list_sku_id,
            'highlight': _inner_service_high_light
        })
        # end zhanghang 新增 2019-4-10


        # 特殊逻辑 对于广告 需要返回具体的广告ID
        if 'adver_position' in filters:
            res['adver_id_map'] = {int(s['_id']): s['fields']['adver_id'][0] for s in tmp['hits']['hits']}

    if sort_type in SMARTRANK_SORT:
        rank_mode = RANK_MODE.CPC
    else:
        rank_mode = RANK_MODE.DEFAULT
    res["rank_mode"] = rank_mode
    return res


def filter_service_v2(
        offset=0, size=5,
        sort_type=SERVICE_ORDER_TYPE.DEFAULT,
        filters=None, sort_params=None,
        or_filters=[], user_city_tag_id=None, query=''
):
    rpc_client = get_rpc_remote_invoker()
    res = rpc_client['doris/search/query_spu'](
        filters=filters,
        offset=offset,
        size=size,
        sort_type=sort_type,
        sort_params=sort_params,
        or_filters=or_filters,
        query=query,
        user_city_tag_id=user_city_tag_id
    ).unwrap()
    return res


def scatter_ids_by(sku_ids, hospital_ids, next_page):
    dim_0 = min(8, (len(sku_ids) + 9) / 10)

    # we keep first dim_0 loading order
    org_2darr = [[] for j in range(dim_0)]
    sku_2darr = [[] for j in range(dim_0)]

    # sku_ids hospital_ids should keep in sync
    # get first 20 skus for every iter
    step = 50

    sku_candidates = sku_ids[:step]
    # slice 20 skus from all
    sku_ids = sku_ids[step:]

    hospital_candidates = hospital_ids[:step]
    hospital_ids = hospital_ids[step:]

    row = 0
    while sku_candidates:
        if row >= dim_0:
            break

        for index, sku_id in enumerate(sku_candidates):
            # already found 10 skus break
            if len(org_2darr[row]) >= 10:
                # return left sku_ids, hospital back
                s1 = []
                s2 = []
                for i, j in enumerate(sku_candidates):
                    if j not in sku_2darr[row]:
                        s1.append(j)

                        h = hospital_candidates[i]
                        s2.append(h)

                sku_ids = s1 + sku_ids
                hospital_ids = s2 + hospital_ids
                break

            # if hospital already in this row, push it back to orig array
            hospital_id = hospital_candidates[index]
            if hospital_id in org_2darr[row]:
                # push to front of sku, hospital ids
                sku_ids.insert(0, sku_id)
                hospital_ids.insert(0, hospital_id)

            else:
                # push item into row
                org_2darr[row].append(hospital_id)
                sku_2darr[row].append(sku_id)

        if len(sku_2darr[row]) < 10:
            supp = 10 - len(sku_2darr[row])
            sku_2darr[row].extend(sku_ids[:supp])
            org_2darr[row].extend(hospital_ids[:supp])

            # reset sku_ids, hospital_ids
            sku_ids = sku_ids[supp:]
            hospital_ids = hospital_ids[supp:]

        sku_candidates = sku_ids[:step]
        # slice 20 skus from all
        sku_ids = sku_ids[step:]

        hospital_candidates = hospital_ids[:step]
        hospital_ids = hospital_ids[step:]

        row += 1

    if next_page >= dim_0:
        sku_ids = []
    else:
        sku_ids = sku_2darr[next_page]
    return sku_ids


def scatter(iterable, pred=None, size=10):
    if pred is None:
        pred = lambda x: x

    src = deque((pred(item), item) for item in iterable)
    dst = []

    while src:
        haved, tmp, recover = set(), [], []
        while src and len(haved) < size:
            key, item = src.popleft()
            if key in haved:
                recover.append((key, item))
            else:
                haved.add(key)
                tmp.append((key, item))

        src.extendleft(reversed(recover))

        # supply from remain elements if not insufficient.
        if len(haved) < size:
            diff = size - len(haved)
            while diff and src:
                diff -= 1
                tmp.append(src.popleft())

        # copy with scatter in per page.
        mp = OrderedDict()
        for (key, item) in tmp:
            mp.setdefault(key, []).append(item)

        rs = filter(None, itertools.chain.from_iterable(itertools.izip_longest(*mp.values())))
        dst.extend(rs)

    return dst


def classfy_by_city(iterable, city_tag_id):
    local, nearby, nation = [], [], []
    for item in iterable:
        if item['city_tag_id'] == city_tag_id:
            local.append(item)
        elif city_tag_id in item['nearby_city_tags']:
            nearby.append(item)
        else:
            nation.append(item)

    return local, nearby, nation


def classfy_nearby_or_loal(iterable, city_tag_id):
    local_or_nearby, nation = [], []
    for item in iterable:
        if item['city_tag_id'] == city_tag_id or city_tag_id in item['nearby_city_tags']:
            local_or_nearby.append(item)
        else:
            nation.append(item)

    return local_or_nearby, nation


def service_scatter_by_city(iterable, city_tag_id, size=10, merge_nearby=False):
    if merge_nearby:
        local_or_nearby, nation = classfy_nearby_or_loal(iterable, city_tag_id)
        local_and_nearby = scatter(local_or_nearby, itemgetter('merchant_id'), size)
        nation = scatter(nation, itemgetter('merchant_id'), size)
        result = local_and_nearby + nation
    else:
        local, nearby, nation = classfy_by_city(iterable, city_tag_id)
        lo = scatter(local, itemgetter('merchant_id'), size)
        nb = scatter(nearby, itemgetter('merchant_id'), size)
        na = scatter(nation, itemgetter('merchant_id'), size)
        result = lo + nb + na

    return result


@cache_page(120)
def filter_special_promtions(query, user_city_tag_id, offset, size, sort_type, filters, sort_params, or_filters):
    rpc_client = get_rpc_remote_invoker()

    # for loading count > 3, get from es directly
    # next_page = offset / 10
    # if sort_type != SERVICE_ORDER_TYPE.DEFAULT or (sort_type == SERVICE_ORDER_TYPE.DEFAULT and next_page >= 8):
    #     res = rpc_client["doris/search/query_sku"](
    #         query=query,
    #         user_city_tag_id=user_city_tag_id,
    #         offset=offset,
    #         size=size,
    #         sort_type=sort_type,
    #         filters=filters,
    #         sort_params=sort_params,
    #         or_filters=or_filters
    #     ).unwrap()
    #     # return res['sku_ids'], res['rank_mode']
    #     return res

    if user_city_tag_id != -1:
        try:
            obj = City.objects.only('id', 'tag_id').get(tag_id=user_city_tag_id)
            rs = rpc_client['doris/hera/in_city_whitelist'](city_ids=[obj.id]).unwrap()
            sort_params['in_whitelist'] = int(bool(rs and rs[0]))
        except (City.DoesNotExist, City.MultipleObjectsReturned):
            sort_params['in_whitelist'] = 0
    else:
        sort_params['in_whitelist'] = 0

    # ok, we just get first 5 page from top 100.
    res = rpc_client["doris/search/query_sku"](
        query=query,
        user_city_tag_id=user_city_tag_id,
        offset=offset,
        size=size,
        sort_type=sort_type,
        filters=filters,
        sort_params=sort_params,
        or_filters=or_filters
    ).unwrap()

    # sku_ids = res['sku_ids']
    # rank_mode = res['rank_mode']
    # covert to merchant id
    # doctor_merchant_dict = doctor_merchant_map(res['doctor_ids'])
    # hospital_ids = [doctor_merchant_dict[i] for i in res['doctor_ids']]
    # sku_list = res['sku_list']

    # items = []
    # for idx, sku_id in enumerate(sku_ids):
    #     item = {
    #         'sku_id': sku_id,
    #         'merchant_id': hospital_ids[idx],
    #         'city_tag_id': sku_list[idx]['city_tag_id'],
    #         'nearby_city_tags': sku_list[idx]['nearby_city_tags']
    #     }

    #     items.append(item)

    # in_whitelist = sort_params.get('in_whitelist', 0)
    # data = service_scatter_by_city(items, user_city_tag_id, 10, merge_nearby=not in_whitelist)
    # sku_ids = map(itemgetter('sku_id'), data)
    # rs = sku_ids[offset:offset + size], rank_mode
    # res['sku_ids'] = sku_ids[offset:offset + size]
    return res


@cache_page(120)
def filter_special_promtions_new(
        query='', user_city_tag_id=None, offset=0, size=10,
        sort_type=SERVICE_ORDER_TYPE.DEFAULT, filters={},
        sort_params={}, or_filters=[]):
    rpc_client = get_rpc_remote_invoker()
    res = rpc_client['doris/search/as_query_sku'](
        query=query,
        user_city_tag_id=user_city_tag_id,
        offset=offset,
        size=size,
        sort_type=sort_type,
        filters=filters,
        sort_params=sort_params,
        or_filters=or_filters,
    ).unwrap()
    return res['sku_ids'], res['rank_mode']


@cache_page(600)
def get_exterior_service_ids(doctor_type, doctor_id=None, hospital_id=None, keywords="", size=3):
    """获取外显的美购信息

    :param unicode doctor_type: 医生类型
    :param unicode doctor_id: 医生ID
    :param unicode hospital_id: 医院ID
    :param unicode keywords: 关键字(预留字段)
    :rtype: List[str] 返回美购IDS列表
    """

    if doctor_type == DOCTOR_TYPE.DOCTOR:
        filters = {"doctor_id": doctor_id}
        order_by = SERVICE_ORDER_TYPE.DOCTOR_CUSTOMIZE_SORT
    else:
        filters = {"hospital_id": hospital_id}
        order_by = SERVICE_ORDER_TYPE.HOSPITAL_CUSTOMIZE_SORT

    result = filter_service(filters=filters, sort_type=order_by, size=size, query=keywords)
    service_ids = result.get("service_ids") or []

    #  supplement to size
    counts = len(service_ids)
    if counts < size:
        result = filter_service(filters=filters, sort_type=order_by, size=size)
        tmp_service_ids = result.get("service_ids") or []

        total_ids = service_ids + tmp_service_ids
        service_ids = sorted(list(set(total_ids)), key=total_ids.index)[:size]
    return service_ids


@cache_page(600)
def get_exterior_service_ids_extend(doctor_type, doctor_id=None, hospital_id=None, keywords="", size=3):
    """
    目前搜索用到
    zhanghang add @2019-04-10
    获取外显的美购信息
    """

    if doctor_type == DOCTOR_TYPE.DOCTOR:
        filters = {"doctor_id": doctor_id}
        order_by = SERVICE_ORDER_TYPE.DOCTOR_CUSTOMIZE_SORT
    else:
        filters = {"hospital_id": hospital_id}
        order_by = SERVICE_ORDER_TYPE.HOSPITAL_CUSTOMIZE_SORT

    result = filter_service(filters=filters, sort_type=order_by, size=size, query=keywords)
    service_ids = result.get("service_ids") or []
    high_light = result.get('highlight', {})
    counts = len(service_ids)
    if counts < size:
        result = filter_service(filters=filters, sort_type=order_by, size=size)
        tmp_service_ids = result.get("service_ids") or []

        total_ids = service_ids + tmp_service_ids
        service_ids = sorted(list(set(total_ids)), key=total_ids.index)[:size]
    return service_ids, high_light
