from datetime import datetime

from django.conf import settings
from gm_types.gaia import SERVICE_ORDER_TYPE, ADVER_MANAGEMENT_POSITION_TYPE, SERVICE_ITEM_PRICE_TYPE
from gm_types.doris import STRATEGY_TYPE
from libs.filters import area_tag_id_filter
from libs.error import logging_exception
from libs.es import tzlc, get_es, es_query, get_highlight, get_highlight_query_analyzer
from libs.debug import pretty_json
import logging
import traceback
import hashlib
from libs.cache import redis_client
import json
import time

logger = logging.getLogger(__name__)

# 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,
)


class ProcessFilters(object):
    ExtralFilterSupportList = ['tag_ids']

    def __init__(self, filters, sort_type=SERVICE_ORDER_TYPE.DEFAULT, query='', or_filters=[], must_filters=False,
                 inner_hits=False):
        self.filters = filters
        self.sort_type = sort_type
        self.query = query
        self.or_filters = or_filters
        self.should_filters = []
        self.must_filters = [{'term': {'is_online': True}}]  # 只返回上线的福利]
        self.bool_must_filters = must_filters
        self.inner_hits = inner_hits

    def process_seckill_gengmei_interval(self, is_seckill, gengmei_interval_list):
        raise NotImplementedError

    def _build_common_must_filters(self):
        # get sku must/should filters to build function score for base common must filters
        base_should, is_seckill, gengmei_interval_list, sku_must_filter = self.analyze_filters(self.filters,
                                                                                               self.bool_must_filters)

        if len(sku_must_filter) > 0:
            self.must_filters.append(sku_must_filter)

        self.should_filters.append({
            'bool':
                {'must': base_should}
        })

        self.process_seckill_gengmei_interval(is_seckill, gengmei_interval_list)

    @staticmethod
    def analyze_filters(filters, bool_must_filters=False):
        now = int(time.time())
        f = []
        sku_must_filter = []
        sku_should_filter = []
        is_seckill = False
        gengmei_inteval_list = []
        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, 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, int):
                f.append({
                    'bool': {
                        'should': [
                            {'term': {'doctor.hospital.city_tag_id': v}},
                            {'term': {'hospital.city_tag_id': v}},
                        ]
                    }
                })
            elif k == 'area_tag_id' and isinstance(v, (int, str)):
                if bool_must_filters:
                    sku_must_filter.append(area_tag_id_filter(['doctor.hospital.', 'hospital.'], v))
                else:
                    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):
                    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):
                f.append({
                    'bool': {
                        'should': [
                            {'term': {'doctor.hospital.id': v}},
                            {'term': {'hospital.id': v}},
                        ]
                    }
                })
            elif k == 'hospital_ids':
                f.append({
                    "terms": {"doctor.hospital.id": v}
                })
            elif k == 'tag_ids' and isinstance(v, list) and v:
                f.append({
                    'terms': {'closure_tag_ids': v},
                })
            elif k == "floor_id":
                f.append({
                    'term': {'floor_id': v},
                })
            elif k == 'special_id':
                if 'user_city_tag_id' in filters and filters['is_posed']:
                    # 当前城市为beijing, 但是从es会拉回不是北京的数据
                    # 不知道这里是历史逻辑还是BUG, 待后续确认
                    # 以下改动将 doctor.hospital.city_tag_id 挪到了must里面,
                    # 老数据的还是走else分支, 只有新专题才会走到if分支里面
                    if filters.get('floor_id'):
                        f.append({
                            'bool': {
                                'must': [
                                    {
                                        "nested": {
                                            "path": "new_sku_special",
                                            "query": {
                                                "bool": {
                                                    "must": [
                                                        {"term": {"new_sku_special.id": v}},
                                                    ]
                                                }
                                            },
                                            "inner_hits": {"size": 1, "sort": ["_score", {"sku_list.price": "asc"}]}
                                        }
                                    }
                                    # {"term": {"doctor.hospital.city_tag_id": filters['user_city_tag_id']}}
                                ],
                                'must_not': [
                                    {
                                        'bool': {
                                            'must': [
                                                {
                                                    "nested": {
                                                        "path": "new_sku_special",
                                                        "query": {
                                                            "bool": {
                                                                "must": [
                                                                    {"term": {"new_sku_special.id": v}},
                                                                    {"term": {"new_sku_special.has_pos": True}}
                                                                ]
                                                            }
                                                        }
                                                    }
                                                },

                                            ]
                                        }
                                    }
                                ]
                            }
                        })
                    else:
                        f.append({
                            'bool': {
                                'must': [
                                    {
                                        "nested": {
                                            "path": "new_sku_special",
                                            "query": {
                                                "bool": {
                                                    "must": [
                                                        {"term": {"new_sku_special.id": v}},
                                                    ]
                                                }
                                            },
                                            "inner_hits": {"size": 1, "sort": ["_score", {"sku_list.price": "asc"}]}
                                        }
                                    }
                                ],
                                'must_not': [
                                    {
                                        'bool': {
                                            'must': [
                                                {
                                                    "nested": {
                                                        "path": "new_sku_special",
                                                        "query": {
                                                            "bool": {
                                                                "must": [
                                                                    {"term": {"new_sku_special.id": v}},
                                                                    {"term": {"new_sku_special.has_pos": True}}
                                                                ]
                                                            }
                                                        }
                                                    }
                                                },
                                                {"term": {"doctor.hospital.city_tag_id": filters['user_city_tag_id']}}
                                            ]
                                        }
                                    }
                                ]
                            }
                        })
                else:
                    f.append({
                        "nested": {
                            "path": "new_sku_special",
                            "query": {
                                "bool": {
                                    "must": [
                                        {"term": {"new_sku_special.id": v}},
                                    ]
                                }
                            },
                            "inner_hits": {"size": 1, "sort": ["_score", {"sku_list.price": "asc"}]}
                        }
                    })
            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)):
                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) and v:
                # sku_must_filter.append(
                #     {
                #         'term': {'sku_list.price_type': SERVICE_ITEM_PRICE_TYPE.SECKILL}
                #     }
                # )
                is_seckill = True
            elif k == 'gengmei_price_interval' and isinstance(v, list) and v:
                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
                    gengmei_inteval_list.append(interval)
                    price_interval.append({
                        'range': {'sku_list.price': interval}
                    })

                query = {
                    'nested': {
                        'path': 'sku_list',
                        'query': {
                            'bool': {
                                'must': price_interval,
                            }
                        }
                    }
                }
                sku_must_filter.append(query)
                # 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}
                    })
                    f.append(
                        {
                            "term": {
                                "advertise_info.searchwords": filter_searchword
                            }
                        }
                    )
                else:
                    filter_searchword = None

                f.append(
                    {
                        "range": {
                            "advertise_info.start_time_epoch": {
                                "lte": now
                            }
                        }
                    }
                )
                f.append(
                    {
                        "range": {
                            "advertise_info.end_time_epoch": {
                                "lte": now
                            }
                        }
                    }
                )
                f.append(
                    {
                        "term": {
                            "advertise_info.position": v
                        }
                    }
                )
                # 'script': {
                #     'script_file': 'filter_service-advertise',
                #     'lang': settings.ES_SCRIPT_LANG,
                #     'params': {
                #         'adver_position': v,
                #         'adver_searchword': filter_searchword,
                #     }
                # }

            elif k == 'adver_word':
                pass  # see above
            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 == 'city_name':
                f.append({
                    "term": {"doctor.hospital.city_name": v}
                })
            elif k == 'is_online':
                f.append({
                    "term": {"is_online": v}
                })
            elif k == "is_sink":
                f.append({
                    "term": {"is_sink": v}
                })
            elif k == 'sales_count_range':  # 售卖量，传入是字典类型{"gte": xx,}
                f.append({
                    "range": {"sales_count": v},
                })
            elif k == 'related_case_count_range':  # 案例数(关联日记本数)，传入是字典类型{"gte": xx,}
                f.append({
                    "range": {"case_count": v},
                })
            else:
                logging_exception()

        return f, is_seckill, gengmei_inteval_list, sku_must_filter

    def _build_or_should_filters(self):
        if not self.or_filters:
            return

        for filters in self.or_filters:
            if (set(filters.keys()) - set(self.ExtralFilterSupportList)):  # if exist not support filter key, return
                return

            should_filters, _, __, sku_must_filter = self.analyze_filters(filters)
            self.should_filters.append({
                'bool':
                    {'must': should_filters}
            })

    def build_should_filters(self):
        self._build_or_should_filters()

    def build_must_filters(self):
        self._build_common_must_filters()

    def build_filters(self):
        self.build_must_filters()
        self.build_should_filters()
        return {
            'bool': {
                'should': self.should_filters,
                "minimum_should_match": 1,
                'must': self.must_filters
            }
        }

    def __call__(self, *args, **kwargs):
        return self.build_filters()


class SKUFiltersProcesser(ProcessFilters):

    def get_sku_inner_hits(self):
        sku_inner_hits = {}

        if self.sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_PRICE:
            sku_inner_hits = {
                "size": 1,
                "sort": [
                    {"sku_list.price": "desc"},
                    "_score"
                ]
            }
        elif self.sort_type == SERVICE_ORDER_TYPE.ORDER_LOWEST_PRICE:
            sku_inner_hits = {
                "size": 1,
                "sort": [
                    {"sku_list.price": "asc"},
                    "_score"
                ]
            }


        else:
            sku_inner_hits = {
                "size": 1,
                "sort": [
                    "_score",
                    {"sku_list.price": "asc"}
                ]
            }

        return sku_inner_hits

    def process_seckill_gengmei_interval(self, is_seckill, gengmei_interval_list):
        sku_should_filter = []
        sku_must_filter = []

        # process gengmei interval filters
        if gengmei_interval_list:
            for interval in gengmei_interval_list:
                sku_should_filter.append({
                    'range': {'sku_list.price': interval}
                })

        # process is_seckill
        if is_seckill:
            sku_must_filter.append(
                {
                    'term': {'sku_list.price_type': SERVICE_ITEM_PRICE_TYPE.SECKILL}
                }
            )

        # sku must filters include innert hits & score function
        sku_must_filter.extend([{
            "range": {
                "sku_list.start_time": {"lte": "now"}
            }
        },
            {
                "range": {
                    "sku_list.end_time": {"gt": "now"}
                }
            }])

        if self.query != "":
            sku_should_filter.extend([
                {"range": {
                    "sku_list.start_time": {"lte": "now"}
                }
                },
                {"range": {
                    "sku_list.end_time": {"gt": "now"}
                }
                },
                {
                    "constant_score": {
                        "filter": {
                            "match_phrase": {
                                "sku_list.name": {
                                    "analyzer": "keyword",
                                    "query": self.query
                                }
                            }
                        }
                    }

                }])

        sku_inner_hits = self.get_sku_inner_hits()

        if len(sku_should_filter) > 0:
            function_score = {
                "query": {
                    "bool": {
                        "must": sku_must_filter,
                        "should": sku_should_filter,
                        "minimum_should_match": 1
                    }
                },
                "boost_mode": "replace",
                "score_mode": "max",
                "min_score": 0
            }
        else:
            function_score = {
                "query": {
                    "bool": {
                        "must": sku_must_filter
                    }
                },
                "boost_mode": "replace",
                "score_mode": "max",
                "min_score": 0
            }

        """
        if self.query != '':
            function_score['functions'] = [
                {
                    "filter": {
                        "match": {
                            "sku_list.name": self.query
                        }
                    },
                    "weight": 5
                }
            ]
        """

        sku_filter = {
            "nested": {
                "path": "sku_list",
                "query": {
                    "function_score": function_score
                },
                "inner_hits": sku_inner_hits
            }
        }

        self.must_filters.append(sku_filter)


class SPUFiltersProcesser(ProcessFilters):
    def process_seckill_gengmei_interval(self, is_seckill, gengmei_interval_list):

        filters = []
        now = tzlc(datetime.now())

        # process spu seckill
        if is_seckill:
            filters.append({
                'nested': {
                    'path': 'seckill_time',
                    'query': {
                        'bool': {
                            'filter': [{
                                'range': {'seckill_time.start_time': {'lte': now}}
                            }, {
                                'range': {'seckill_time.end_time': {'gt': now}}
                            }]
                        }
                    }
                }
            })

        # process spu gengmei price interval list

        if gengmei_interval_list:
            periodic_price_interval = []
            for interval in gengmei_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,
                        }
                    }
                }
            }

            filters.append(query)

        if filters:
            self.must_filters.append(filters)


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

    sku_must_filter = [
        {
            "range": {
                "sku_list.start_time": {"lte": "now"}
            }
        },
        {
            "range": {
                "sku_list.end_time": {"gt": "now"}
            }
        }
    ]
    sku_should_filter = []

    sku_inner_hits = {
        "size": 1,
        "sort": [
            "_score",
            {"sku_list.price": "asc"}
        ]
    }

    if sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_PRICE:
        sku_inner_hits = {
            "size": 1,
            "sort": [
                "_score",
                {"sku_list.price": "desc"}
            ]
        }

    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, 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, int):
            f.append({
                'bool': {
                    'should': [
                        {'term': {'doctor.hospital.city_tag_id': v}},
                        {'term': {'hospital.city_tag_id': v}},
                    ]
                }
            })
        elif k == 'area_tag_id' and isinstance(v, (int, str)):
            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):
                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):
            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)):
            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) and v:
            sku_must_filter.append(
                {
                    'term': {'sku_list.price_type': SERVICE_ITEM_PRICE_TYPE.SECKILL}
                }
            )
        elif k == 'gengmei_price_interval' and isinstance(v, list) and v:
            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
                sku_should_filter.append({
                    'range': {'sku_list.price': interval}
                })
            ''''
            query = {
                'nested':{
                    'path':'sku_list',
                    'query':{
                        'bool':{
                            'should':price_interval,
                        }
                    }
                }
            }
            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}
                })
                f.append(
                    {
                        "term": {
                            "advertise_info.searchwords": filter_searchword
                        }
                    }
                )
            else:
                filter_searchword = None
            f.append(
                {
                    "range": {
                        "advertise_info.start_time_epoch": {
                            "lte": now
                        }
                    }
                }
            )
            f.append(
                {
                    "range": {
                        "advertise_info.end_time_epoch": {
                            "lte": now
                        }
                    }
                }
            )
            f.append(
                {
                    "term": {
                        "advertise_info.position": v
                    }
                }
            )
            # f.append({
            #     'script': {
            #         'script_file': 'filter_service-advertise',
            #         'lang': settings.ES_SCRIPT_LANG,
            #         'params': {
            #             'adver_position': v,
            #             'adver_searchword': filter_searchword,
            #         }
            #     }
            # })


        elif k == 'adver_word':
            pass  # see above
        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}}
            })
        else:
            logging_exception()

    if len(sku_should_filter) > 0:
        function_score = {
            "query": {
                "bool": {
                    "must": sku_must_filter,
                    "should": sku_should_filter,
                    "minimum_should_match": 1

                }
            },
            "boost_mode": "replace",
            "score_mode": "max",
            "min_score": 0
        }
    else:
        function_score = {
            "query": {
                "bool": {
                    "must": sku_must_filter
                }
            },
            "boost_mode": "replace",
            "score_mode": "max",
            "min_score": 0
        }
    if query != '':
        function_score['functions'] = [
            {
                "filter": {
                    "match": {
                        "sku_list.name": query
                    }
                },
                "weight": 5
            }
        ]

    sku_filter = {
        "nested": {
            "path": "sku_list",
            "query": {
                "function_score": function_score
            },
            "inner_hits": sku_inner_hits
        }
    }
    f.append(sku_filter)

    return f


def process_sorting(sort_type, sort_params, append_score=False, query="", recall_again=False, weight_service_cpc=False,
                    weight_tag_score=False, weight_office_score=False, is_service_test=False):
    sort_params = sort_params if sort_params is not None else {}
    now = int(time.time())

    sorting = [  # 所有福利排序，下沉的与不可售的都需要放后面
        {'_script': {
            'type': 'number',
            'order': 'asc',
            "script": {
                "id": "service-sink",
                "params": {"now": now}
            }
        }},
        {'_script': {
            'type': 'number',
            'order': 'desc',
            "script": {
                "id": "service-time-valid",
                "params": {"now": now}
            }
        }},
    ]

    # 机构罚单下沉
    sorting += [
        {
            '_script': {
                'type': 'number',
                'order': 'asc',
                "script": {
                    "id": "service-sink-by-org",
                    "params": {"now": now}
                }
            }
        }
    ]

    if sort_params.get(
            'query') and sort_type not in (
            SERVICE_ORDER_TYPE.DORIS_SMART, SERVICE_ORDER_TYPE.DEFAULT, SERVICE_ORDER_TYPE.ORDER_LOWEST_PRICE,
            SERVICE_ORDER_TYPE.ORDER_HIGHEST_PRICE):
        sorting += [
            {'_script': {
                'order': 'desc',
                'type': 'number',
                "script": {
                    "id": "service-tag-first",
                    "params": {
                        "query": sort_params['query']
                    }

                }
            }},
        ]

    if sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_SALES:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'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': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            },
            {
                "sku_list.price": {
                    "order": "asc",
                    "mode": "min",
                    "nested_path": "sku_list",
                    "nested_filter": {
                        "bool": {
                            "must": [
                                {
                                    "range": {
                                        "sku_list.start_time": {"lte": "now"}
                                    }
                                },
                                {
                                    "range": {
                                        "sku_list.end_time": {"gt": "now"}
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.CASE_COUNT:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'case_count': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT_SPECIAL:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'_script': {
                'type': 'number',
                "script": {
                    "id": "service-default-special",
                    "params": {
                        "special_id": sort_params['special_id'],
                    }
                },
                'order': 'asc',
            }},
            {'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': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'_script': {
                'type': 'number',
                'order': 'asc',
                "script": {
                    "id": "service-price-3",
                    "params": {
                        "now": now
                    }
                }
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.REGION:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'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
        # 为和客户端兼容 使用lng参数替代之前的lon参数 @kula
        if ('lng' in sort_params and sort_params['lng'] 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['lng'], 'lat': sort_params['lat']},
                'order': 'asc',
                'unit': 'km',
            }})

        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_EVALUATE:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'rating': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.POPULARITY:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'pv': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT:  # 智能排序

        if weight_office_score:
            sorting += [
                {"_score": {"order": "desc"}}
            ]

        sorting += [
            {
                '_script': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            }
        ]

        if sort_params.get('query'):
            sorting += [
                {'_script': {
                    'order': 'desc',
                    'type': 'number',
                    "script": {
                        "id": "service-tag-first",
                        "params": {
                            "query": sort_params['query']
                        }

                    }
                }},

            ]
        if query and len(query) > 0:
            sorting += [
                {"_score": {"order": "desc"}}
            ]

        if weight_tag_score:
            sorting += [
                {"_score": {"order": "desc"}}
            ]

        if weight_service_cpc:
            sorting += [

                {"is_promote": {"order": "desc"}}
            ]

        if is_service_test:
            sorting += [
                {'smart_rank_v4': {'order': 'desc'}},
                {'ordering': {'order': 'asc'}},
                {'start_time': {'order': 'desc'}},
            ]
        else:
            sorting += [
                {'smart_rank2': {'order': 'desc'}},
                {'ordering': {'order': 'asc'}},
                {'start_time': {'order': 'desc'}},
            ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME:  # 实时智能排序
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            '_score',
            {'smart_rank2': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DIARY_RECOMMENDED:
        sorting += [
            {'_script': {
                'type': 'number',
                "script": {
                    "id": "service-region",
                    "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',
            }},
            {'smart_rank2': {'order': 'desc'}},
        ]
    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': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            },
            {
                "sku_list.price": {
                    "order": "desc",
                    "mode": "max",
                    "nested_path": "sku_list",
                    "nested_filter": {
                        "bool": {
                            "must": [
                                {
                                    "range": {
                                        "sku_list.start_time": {"lte": "now"}
                                    }
                                },
                                {
                                    "range": {
                                        "sku_list.end_time": {"gt": "now"}
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.NEARBY_REGION:
        sorting += [
            {
                '_script': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            },
            {'smart_rank2': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    if append_score and sort_type != SERVICE_ORDER_TYPE.DEFAULT_REALTIME:
        sorting.append('_score')

    return sorting


def process_sorting_by_new_search_analyze(sort_type, sort_params, append_score=True):
    now = int(time.time())
    sort_params = sort_params if sort_params is not None else {}
    sorting = [  # 所有福利排序，下沉的与不可售的都需要放后面
        {'_script': {
            'type': 'number',
            'order': 'asc',
            "script": {
                "id": "service-sink",
                "params": {"now": now}
            }
        }},
        {'_script': {
            'type': 'number',
            'order': 'desc',
            "script": {
                "id": "service-time-valid",
                "params": {"now": now}
            }
        }},
    ]

    # 机构罚单下沉
    sorting += [
        {
            '_script': {
                'type': 'number',
                'order': 'asc',
                "script": {
                    "id": "service-sink-by-org",
                    "params": {"now": now}
                }
            }
        }
    ]

    if sort_params.get(
            'query') and sort_type != SERVICE_ORDER_TYPE.DORIS_SMART and sort_type != SERVICE_ORDER_TYPE.DEFAULT:
        sorting += [
            {'_script': {
                'order': 'desc',
                'type': 'number',
                "script": {
                    "id": "service-tag-first",
                    "params": {
                        "query": sort_params['query']
                    }

                }
            }},
        ]
        if append_score and sort_type != SERVICE_ORDER_TYPE.DEFAULT_REALTIME:
            sorting.append('_score')
    if sort_type == SERVICE_ORDER_TYPE.ORDER_HIGHEST_SALES:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'sales_count': {'order': 'desc'}})
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_LOWEST_PRICE:  # 价格最低
        now_str = tzlc(datetime.now()).isoformat()
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {
                "sku_list.price": {
                    "order": "asc",
                    "mode": "min",
                    "nested_path": "sku_list",
                    "nested_filter": {
                        "bool": {
                            "must": [
                                {
                                    "range": {
                                        "sku_list.start_time": {"lte": "now"}
                                    }
                                },
                                {
                                    "range": {
                                        "sku_list.end_time": {"gt": "now"}
                                    }
                                }
                            ]
                        }
                    }
                }
            }]
        if append_score:
            sorting.append("_score")
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.CASE_COUNT:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'case_count': {'order': 'desc'}})
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT_SPECIAL:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'_script': {
                'type': 'number',
                "script": {
                    "id": "service-default-special",
                    "params": {
                        "special_id": sort_params['special_id'],
                    }
                },
                'order': 'asc',
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'smart_rank2': {'order': 'desc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.LOWEST_PRICE_SPECIAL:
        now_str = tzlc(datetime.now()).isoformat()
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {'_script': {
                'type': 'number',
                'order': 'asc',
                "script": {
                    "id": "service-price-3",
                    "params": {
                        "now": now
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.REGION:
        sorting += [

            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'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
        # 为和客户端兼容 使用lng参数替代之前的lon参数 @kula
        if ('lng' in sort_params and sort_params['lng'] 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['lng'], 'lat': sort_params['lat']},
                'order': 'asc',
                'unit': 'km',
            }})

        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.ORDER_EVALUATE:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'rating': {'order': 'desc'}})
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.POPULARITY:
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'pv': {'order': 'desc'}})
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT:  # 智能排序
        sorting += [
            {
                '_script': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            },
        ]

        if sort_params.get('query'):
            sorting += [
                {'_script': {
                    'order': 'desc',
                    'type': 'number',
                    "script": {
                        "id": "service-tag-first",
                        "params": {
                            "query": sort_params['query']
                        }

                    }
                }},
            ]
        if append_score:
            sorting.append("_score")
        sorting += [
            {'smart_rank2': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME:  # 实时智能排序
        sorting += [
            {'_script': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            '_score',
            {'smart_rank2': {'order': 'desc'}},
            {'ordering': {'order': 'asc'}},
            {'start_time': {'order': 'desc'}},
        ]
    elif sort_type == SERVICE_ORDER_TYPE.DIARY_RECOMMENDED:
        sorting += [
            {'_script': {
                'type': 'number',
                "script": {
                    "id": "service-region",
                    "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',
            }}]
        if append_score:
            sorting.append("_score")
        sorting.append({'smart_rank2': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.ORDERED_USER:
        sorting.append("_score")
        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': {
                'type': 'number',
                'order': 'desc',
                "script": {
                    "id": "service-default",
                    "params": {
                        "user_city_tag_id": sort_params['user_city_tag_id'] if 'user_city_tag_id' in sort_params else -1
                    }
                }
            }},
            {
                "sku_list.price": {
                    "order": "desc",
                    "mode": "max",
                    "nested_path": "sku_list",
                    "nested_filter": {
                        "bool": {
                            "must": [
                                {
                                    "range": {
                                        "sku_list.start_time": {"lte": "now"}
                                    }
                                },
                                {
                                    "range": {
                                        "sku_list.end_time": {"gt": "now"}
                                    }
                                }
                            ]
                        }
                    }
                }
            }]
        if append_score:
            sorting.append("_score")
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})
    elif sort_type == SERVICE_ORDER_TYPE.NEARBY_REGION:
        sorting += [
            {
                '_script': {
                    'type': 'number',
                    'order': 'desc',
                    "script": {
                        "id": "service-region-related",
                        "params": {
                            "user_city_tag_id": sort_params.get('user_city_tag_id', -1),
                            "in_whitelist": int(sort_params.get('in_whitelist', False))
                        }
                    }
                }
            },

        ]
        if append_score:
            sorting.append("_score")
        sorting.append({'smart_rank2': {'order': 'desc'}})
        sorting.append({'ordering': {'order': 'asc'}})
        sorting.append({'start_time': {'order': 'desc'}})

    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
        # 'item_wiki_tags':2,
        # 'detail_description':1,   # 5.3版 去掉搜索域: 项目简介
    }
    fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]

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

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

    q = {
        'query': {
            'multi_match': multi_match,
            "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(), query)
    res = es_query('service', q, offset, size)
    return res


def filter_service(
        offset=0, size=10,
        sort_type=SERVICE_ORDER_TYPE.DEFAULT,
        filters=None, sort_params=None,
):
    # 参数验证
    # 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 = SKUFiltersProcesser(filters=filters)()
    q = {
        'query': f
    }

    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params)

    logging.info("get filter_service q:%s" % q)

    res = es_query('service', q, offset, size)
    tmp = res
    res = {
        'service_ids': [int(s['_id']) for s in tmp['hits']['hits']],
        'total_count': tmp['hits']['total'],
    }

    return res


# @desc sku召回
# @rd 郑伟
# @param  query                          搜索词
# @param  offset                         起始行数
# @param  size                           返回行数
# @param  sort_type                      排序类型
# @param  filters                        过滤条件
# @param  sort_params                    排序条件
# @date 20170709
def recall_sku(query='',
               offset=0,
               size=10,
               sort_type=SERVICE_ORDER_TYPE.DEFAULT,
               sort_params={},
               filters={},
               or_filters=[],
               fields=[],
               must_filters=False, weight_service_cpc=True, have_read_service_list=list(), recall_again=False,
               inner_hits=False, is_service_test=False):
    size = min(size, settings.COUNT_LIMIT)
    # 限制interest tag时才走实时智能排序
    if sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME and sort_params.get('interest_tags', False):
        return realtime_recall_sku(query, offset, size, sort_type, sort_params, filters)
    # 搜索关键字部分
    # 搜索域

    multi_fields = {
        'short_description': 8,
        'doctor.name': 4,
        'doctor.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  # 搜索机构管理者
    }

    standard_fields_name_list = ["short_description_by_standard_analyzer", "doctor.name_by_standard_analyzer",
                                 "doctor.hospital.name_by_standard_analyzer",
                                 "doctor.hospital.city_name_by_standard_analyzer",
                                 "doctor.hospital.city_province_name_by_standard_analyzer",
                                 "closure_tags_by_standard_analyzer",
                                 "doctor.hospital.officer_name_by_standard_analyzer"]
    query_fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]
    multi_match = {
        'query': query,
        'type': 'cross_fields',
        'operator': 'and',
        'fields': query_fields,
        "analyzer": "keyword"
    }
    sku_query = {
        "nested": {
            "path": "sku_list",
            "query": {
                "multi_match": {
                    "query": query,
                    "fields": ["sku_list.name^2"],
                    'operator': 'and',
                    'type': 'cross_fields'
                }
            },
        }
    }

    # f = process_filters(filters=filters, sort_type=sort_type, query=query)
    #
    # spu_filter = {'bool': {'must': f}}
    # pretty_json(spu_filter)
    sku_filter = SKUFiltersProcesser(filters, sort_type, query, or_filters, must_filters, inner_hits=inner_hits)()

    logging.info("get sku_filter:%s" % sku_filter)
    q = {
        'query': sku_filter
    }

    append_score = True
    if query != '':
        # q['query'] = {
        #     'bool': {
        #         "should": [
        #             {'multi_match': multi_match},
        #             sku_query
        #         ],
        #         "minimum_should_match": 1
        #     }
        # }
        # sort_params['query'] = query
        #
        # if len(have_read_service_list)>0:
        #     q['query']['bool']['must_not'] = {
        #         "terms":{
        #             "id": have_read_service_list
        #         }
        #     }

        q["query"] = {
            "function_score": {
                "query": {
                    'bool': {
                        "should": [
                            {'multi_match': multi_match},
                            sku_query
                        ],
                        "must": [],
                        "minimum_should_match": 1
                    }
                },
                "boost_mode": "replace",
                "score_mode": "max",
                "functions": []
            }
        }
        if len(sku_filter["bool"]["should"]) > 0:
            q['query']["function_score"]["query"]['bool']['should'].append(sku_filter["bool"]["should"])

        if len(sku_filter["bool"]["must"]) > 0:
            q['query']["function_score"]["query"]['bool']['must'].append(sku_filter["bool"]["must"])

        if len(have_read_service_list) > 0:
            q['query']["function_score"]["query"]['bool']['must_not'] = {
                "terms": {
                    "id": have_read_service_list
                }
            }

        # for field_name in multi_fields:
        #     q["query"]["function_score"]["functions"].append(
        #         {
        #             "filter": {
        #                 "match_phrase": {
        #                     field_name: {
        #                         "analyzer": "gm_default_index",
        #                         "query": query
        #                     }
        #                 }
        #             },
        #             "weight": 1000
        #         }
        #     )
        #
        #
        # q["query"]["function_score"]["functions"] += [
        #     {
        #         "filter": {
        #             "nested": {
        #                 "path": "sku_list",
        #                 "query": {
        #                     "match_phrase": {
        #                         "sku_list.name": {
        #                             "query": query,
        #                             "analyzer": "gm_default_index"
        #                         }
        #                     }
        #                 }
        #             }
        #         },
        #         "weight": 1000
        #     }
        # ]

        for field_name in standard_fields_name_list:
            q["query"]["function_score"]["functions"].append(
                {
                    "filter": {
                        "match_phrase": {
                            field_name: {
                                "analyzer": "standard",
                                "query": query
                            }
                        }
                    },
                    "weight": 1000
                }
            )
        q["query"]["function_score"]["functions"] += [
            {
                "filter": {
                    "nested": {
                        "path": "sku_list",
                        "query": {
                            "match_phrase": {
                                "sku_list.name_by_standard_analyzer": {
                                    "query": query,
                                    "analyzer": "standard"
                                }
                            }
                        }
                    }
                },
                "weight": 1000
            }
        ]

        for field_name in multi_fields:
            q["query"]["function_score"]["functions"].append(
                {
                    "filter": {
                        "term": {
                            field_name: query
                        }
                    },
                    "weight": 1000
                }
            )
        q["query"]["function_score"]["functions"].append(
            {
                "filter": {
                    "term": {
                        "sku_list.name": query
                    }
                },
                "weight": 1000
            }
        )

        if query == "眼":
            closure_tags_name_list = ["眼袋修复", "卧蚕手术修复", "提眉修复", "双眼皮修复", "开眼角修复", "切眉（提眉）", "上眼睑祛脂", "上眼睑提肌无力矫正",
                                      "上眼睑下垂矫正",
                                      "眼综合", "自体脂肪填充黑眼圈", "玻尿酸祛黑眼圈", "胶原蛋白填充祛黑眼圈", "激光祛黑眼圈", "泪腺脱垂整形", "开内眼角", "开外眼角",
                                      "自体脂肪填充泪沟", "胶原蛋白填充泪沟", "玻尿酸填充泪沟", "眼部套餐", "切开双眼皮", "埋线双眼>皮", "微创双眼皮",
                                      "眶隔脂肪释放内切祛眼袋",
                                      "外切祛眼袋", "激光祛眼袋", "射频祛眼袋", "玻尿酸垫眉弓膨体垫眉弓", "硅胶垫眉弓", "自体脂肪垫眉弓", "玻尿酸填充卧蚕自体脂肪填充卧蚕",
                                      "真皮填充卧蚕", "眼部修复", "眼睑", "眼部综合", "祛黑眼圈", "开眼角", "填充泪沟", "双眼皮", "祛眼袋", "垫眉弓",
                                      "填充卧蚕"]

            q['query']["function_score"]["query"]['bool']['must'] = [
                {
                    "terms": {
                        "closure_tags": closure_tags_name_list
                    }
                }
            ]

        closure_tags_name_list = redis_client.hget("service_closure_tags", query)
        closure_tags = list()
        if closure_tags_name_list and len(closure_tags_name_list) > 0:
            closure_tags = json.loads(closure_tags_name_list)

        if len(closure_tags) > 0:
            q["query"]["function_score"]["functions"].append(
                {
                    "filter": {
                        "terms": {
                            "closure_tags": closure_tags
                        }
                    },
                    "weight": 1000
                }
            )

            q['query']["function_score"]["query"]['bool']['should'].append(
                {
                    "terms": {
                        "closure_tags": closure_tags
                    }
                }
            )

        sort_params['query'] = query
        append_score = False

    else:
        if len(have_read_service_list) > 0:
            q['query'] = {
                "bool": {
                    "must_not": {
                        "terms": {
                            "id": have_read_service_list
                        }
                    }
                }
            }

    if fields:
        q["_source"] = {
            "include": fields
        }

    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params,
                                append_score=append_score, query=query, recall_again=recall_again,
                                weight_service_cpc=weight_service_cpc, is_service_test=is_service_test)

    q['highlight'] = get_highlight(["short_description_pre", "doctor.hospital.name"])

    logging.info("query_sku es query:%s" % str(q).encode("utf-8"))

    res = es_query('service', q, offset, size)

    return res


def recall_sku_by_new_search_analyze(query='',
                                     offset=0,
                                     size=10,
                                     sort_type=SERVICE_ORDER_TYPE.DEFAULT,
                                     sort_params={},
                                     filters={},
                                     or_filters=[],
                                     fields=[], have_read_service_list=list()):
    size = min(size, settings.COUNT_LIMIT)
    # 限制interest tag时才走实时智能排序
    if sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME and sort_params.get('interest_tags', False):
        return realtime_recall_sku_by_new_search_analyze(query, offset, size, sort_type, sort_params, filters)
    # 搜索关键字部分
    # 搜索域

    multi_fields = {
        'short_description': 8,
        'doctor.name': 4,
        'doctor.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  # 搜索机构管理者
    }
    query_fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]
    multi_match = {
        'query': query,
        'type': 'cross_fields',
        'operator': 'or',
        'fields': query_fields,
        "analyzer": "keyword"
    }
    sku_query = {
        "nested": {
            "path": "sku_list",
            "query": {
                "multi_match": {
                    "query": query,
                    "fields": ["sku_list.name^2"],
                    'operator': 'or',
                    'type': 'cross_fields'
                }
            }
        }
    }

    # f = process_filters(filters=filters, sort_type=sort_type, query=query)
    #
    # spu_filter = {'bool': {'must': f}}
    # pretty_json(spu_filter)
    sku_filter = SKUFiltersProcesser(filters, sort_type, query, or_filters)()
    q = {
        'query': sku_filter
    }

    if query != '':
        q['query'] = {
            'bool': {
                "should": [
                    {'multi_match': multi_match},
                    sku_query,

                ],
                "minimum_should_match": 1,
                "must": []
            }
        }
        sort_params['query'] = query

        if len(sku_filter["bool"]["should"]) > 0:
            q["query"]['bool']['should'].append(sku_filter["bool"]["should"])

        if len(sku_filter["bool"]["must"]) > 0:
            q["query"]['bool']['must'].append(sku_filter["bool"]["must"])

        if len(have_read_service_list) > 0:
            q['query']['bool']['must_not'] = {
                "terms": {
                    "id": have_read_service_list
                }
            }
    else:
        if len(have_read_service_list) > 0:
            q['query'] = {
                "bool": {
                    "must_not": {
                        "terms": {
                            "id": have_read_service_list
                        }
                    }
                }
            }

    if fields:
        q["_source"] = {
            "include": fields
        }

    # 排序规则部分
    q['sort'] = process_sorting_by_new_search_analyze(sort_type=sort_type, sort_params=sort_params)
    q['highlight'] = get_highlight(["doctor.hospital.name", "short_description_pre"])

    logger.info("new analyze sku es query:%s", str(q).encode("utf-8"))
    # pretty_json(q)
    res = es_query('service', q, offset, size)
    return res


# @desc 实时sku召回
# @rd 郑伟
# @param  query                          搜索词
# @param  offset                         起始行数
# @param  size                           返回行数
# @param  sort_type                      排序类型
# @param  filters                        过滤条件
# @param  sort_params                    排序条件
# @date 20170725
def realtime_recall_sku(query='',
                        offset=0,
                        size=10,
                        sort_type=SERVICE_ORDER_TYPE.DEFAULT,
                        sort_params={},
                        filters={},
                        or_filters=[],
                        fields=[]):
    size = min(size, settings.COUNT_LIMIT)
    # 搜索关键字部分
    # 搜索域
    multi_fields = {
        'short_description': 8,
        'doctor.name': 4,
        'doctor.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  # 搜索机构管理者
    }
    query_fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]
    multi_match = {
        'query': query,
        'type': 'cross_fields',
        'operator': 'and',
        'fields': query_fields,
    }
    sku_query = {
        "nested": {
            "path": "sku_list",
            "query": {
                "multi_match": {
                    "query": query,
                    "fields": ["sku_list.name^2"],
                    'operator': 'and',
                    'type': 'cross_fields'
                }
            }
        }
    }

    # f = process_filters(filters=filters, sort_type=sort_type, query=query)
    #
    # spu_filter = {'bool': {'must': f}}
    sku_filter = SKUFiltersProcesser(filters=filters, sort_type=sort_type, query=query, or_filters=or_filters)()
    interest_tags = sort_params.get('interest_tags')

    functions = []
    if sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME and interest_tags:
        functions.append(
            {
                "filter": {
                    "terms": {
                        "closure_tag_ids": interest_tags
                    }
                },
                "weight": 5
            }
        )

    function_score = {
        "boost_mode": "replace",
        "functions": functions,
        "min_score": 0,
        "score_mode": "max"
    }
    if query != '':
        function_score['query'] = {
            'bool': {
                "should": [
                    {'multi_match': multi_match},
                    sku_query
                ],
                "minimum_should_match": 1
            }
        }

    q = {
        "query": {
            "function_score": function_score
        }
    }
    sort_params['query'] = query
    if len(sku_filter["bool"]["should"]) > 0:
        q["query"]["function_score"]["query"]['bool']['should'].append(sku_filter["bool"]["should"])

    if len(sku_filter["bool"]["must"]) > 0:
        q["query"]["function_score"]["query"]['bool']['must'].append(sku_filter["bool"]["must"])

    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params,
                                append_score=True)

    if fields:
        q["_source"] = {
            "include": fields
        }

    # pretty_json(q)

    res = es_query('service', q, offset, size)
    return res


def realtime_recall_sku_by_new_search_analyze(query='',
                                              offset=0,
                                              size=10,
                                              sort_type=SERVICE_ORDER_TYPE.DEFAULT,
                                              sort_params={},
                                              filters={},
                                              or_filters=[],
                                              fields=[]):
    size = min(size, settings.COUNT_LIMIT)
    # 搜索关键字部分
    # 搜索域
    multi_fields = {
        'short_description': 8,
        'doctor.name': 4,
        'doctor.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  # 搜索机构管理者
    }
    query_fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]
    multi_match = {
        'query': query,
        'type': 'cross_fields',
        'operator': 'or',
        'fields': query_fields,
    }
    sku_query = {
        "nested": {
            "path": "sku_list",
            "query": {
                "multi_match": {
                    "query": query,
                    "fields": ["sku_list.name^2"],
                    'operator': 'or',
                    'type': 'cross_fields'
                }
            }
        }
    }

    # f = process_filters(filters=filters, sort_type=sort_type, query=query)
    #
    # spu_filter = {'bool': {'must': f}}
    sku_filter = SKUFiltersProcesser(filters=filters, sort_type=sort_type, query=query, or_filters=or_filters)()
    interest_tags = sort_params.get('interest_tags')

    functions = []
    if sort_type == SERVICE_ORDER_TYPE.DEFAULT_REALTIME and interest_tags:
        functions.append(
            {
                "filter": {
                    "terms": {
                        "closure_tag_ids": interest_tags
                    }
                },
                "weight": 5
            }
        )

    function_score = {
        "boost_mode": "replace",
        "functions": functions,
        "min_score": 0,
        "score_mode": "max"
    }
    if query != '':
        function_score['query'] = {
            'bool': {
                "should": [
                    {'multi_match': multi_match},
                    sku_query
                ],
                "must": [],
                "minimum_should_match": 1
            }
        }
        sort_params['query'] = query

    q = {
        "query": {
            "function_score": function_score
        }
    }

    # 排序规则部分
    q['sort'] = process_sorting_by_new_search_analyze(sort_type=sort_type, sort_params=sort_params)

    if fields:
        q["_source"] = {
            "include": fields
        }

    # pretty_json(q)

    res = es_query('service', q, offset, size)
    return res


def recall_spu(query='',
               offset=0,
               size=10,
               sort_type=SERVICE_ORDER_TYPE.DEFAULT,
               sort_params={},
               filters={},
               or_filters=[],
               fields=[]):
    # 参数验证
    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)
    spu_filters = SPUFiltersProcesser(query=query, sort_type=sort_type, filters=filters, or_filters=or_filters)()
    q = {
        'query': spu_filters
    }

    # 搜索关键字部分
    # 搜索域
    if query != '':
        multi_fields = {
            'short_description': 8,
            '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
            # 'item_wiki_tags':2,
            # 'detail_description':1,   # 5.3版 去掉搜索域: 项目简介
        }
        query_fields = ['^'.join((k, str(v))) for (k, v) in multi_fields.items()]

        multi_match = {
            'query': query,
            'type': 'cross_fields',
            'operator': 'and',
            'fields': query_fields,
        }
        sort_params['query'] = query
        q['query'] = {'multi_match': multi_match}

    # 排序规则部分
    q['sort'] = process_sorting(sort_type=sort_type, sort_params=sort_params,
                                append_score=True)

    if fields:
        q["_source"] = {
            "include": fields
        }

    pretty_json(q)
    res = es_query('service', q, offset, size)
    logging.info("get qqqq:%s" % q)
    return res


def recommed_service_category_device_id(device_id):
    try:
        '''
        设备品类显示, 是否命中灰度
        '''
        categroy_select_cary = ["0", "1", "2", "3", "4", "a", "b", "c", "e"]
        if not device_id:
            return False

        hd_id = hashlib.md5(str(device_id).encode()).hexdigest()
        is_gray = hd_id[-1] in categroy_select_cary
        if not is_gray:
            gray_devices_key = "gm:gray:devices:key"
            return redis_client.sismember(gray_devices_key, device_id)
        return is_gray
    except:
        logging.error("catch exception,err_msg:%s" % traceback.format_exc())
        return False


def recommed_service_category_device_id_v2(device_id):
    try:
        '''
        设备品类显示, 是否命中灰度
        '''
        categroy_select_cary = ["0", "1", "2", "3", "4", "a", "b", "c", "e"]
        if not device_id:
            return False

        hd_id = hashlib.md5(str(device_id).encode()).hexdigest()
        is_gray = hd_id[-1] not in categroy_select_cary
        if not is_gray:
            gray_devices_key = "gm:gray:devices:key"
            return redis_client.sismember(gray_devices_key, device_id)
        return is_gray
    except:
        logging.error("catch exception,err_msg:%s" % traceback.format_exc())
        return False


def get_tag_size_by_score(user_portrait):
    try:
        all_score = 0
        tag_id_size = dict()

        for item in user_portrait:
            tag_score = item.get("tag_score", 0)
            all_score += tag_score
        for item in user_portrait:
            if round(item.get("tag_score", None) / all_score * 20) > 8:
                tag_id_size[item.get("tag_id", None)] = 8
            else:
                tag_id_size[item.get("tag_id", None)] = round(item.get("tag_score", None) / all_score * 20)

        return tag_id_size
    except:
        logging.error("catch exception,err_msg:%s" % traceback.format_exc())
        return {}
