# coding=utf-8
from django.conf import settings
from rpc.decorators import cache_page

from gm_types.gaia import TAG_TYPE, DIARY_CONTENT_LEVEL

from rpc.tool.log_tool import logging_exception
from rpc.context import get_rpc_remote_invoker

from .es import get_es, get_highlight, es_index_adapt
from .common import area_tag_id_filter
from api.models.types import DIARY_ORDER_TYPE
from api.models import Tag
from search.utils.common import variousness
from search.interpose.diary_interpose import search_interpose
from search.interpose.diary_interpose import filter_interpose
from api.manager.cache_manager import CacheServer
from utils.gray_control import search_diary_gray_by_device

def process_filters(filters, nfilters):
    # 过滤器部分
    f = [
        {'term': {'is_online': True}},  # 只返回上线的日记本
        {'range': {'normal_topic_count': {'gt': 0}}},  # 正常的关联帖子数>0
    ]
    nf = []

    for k, v in filters.items():
        if k == 'province_tag_id':
            f.append({
                'term': {'user.city_province_tag_id': v}
            })
        elif k == 'area_tag_id':
            f.append(area_tag_id_filter(['user.', 'hospital.', 'doctor.hospital.'], v))
        elif k == 'area_tag_id_v1':
            f.append({
                "bool": {
                    "should": [
                        {"term": {"doctor.hospital.city_province_country_tag_id": v}},
                        {"term": {"doctor.hospital.city_tag_id": v}}
                    ]
                }
            })
        elif k=='bodypart_tag_id':
            # compatibility
            if isinstance(v, list) and v:
                f.append({
                    'terms': {'closure_tag_ids':v}
                })
            elif isinstance(v, (int, long)):
                f.append({
                    'term': {'closure_tag_ids':v}
                })
        elif k=='bodypart_subitem_tag_id':
            f.append({
                'term': {'closure_tag_ids':v}
            })
        elif k=='doctor_id':
            f.append({
                'term': {'doctor.id':v}
            })
        elif k == 'doctor_ids':
            f.append({
                'terms': {'doctor.id': v}
            })
        elif k=='hospital_id':
            f.append({
                'term': {'hospital.id':v}
            })
        elif k == 'hospital_ids':
            f.append({
                'terms': {"doctor.hospital.id": v}
            })
        elif k=='service_id':
            f.append({
                'term': {'service.id':str(v)}  # convert to str because of weird mapping setting
            })
        elif k=='tag_ids' and isinstance(v, list) and v:
            f.append({
                'terms': {'closure_tag_ids':v}
            })
        elif k=='user_id':
            if isinstance(v, list):
                f.append({
                    'terms': {'user.id':v}
                })
            else:
                f.append({
                    'term': {'user.id':v}
                })
        elif k == 'has_cover':
            f.append({
                'term': {'has_cover': v}
            })
        elif k == 'is_headline':
            f.append({
                'term': {'is_headline': v}
            })
        elif k == 'service.doctor_id':
            f.append({
                'term': {'service.doctor_id': v}
            })
        elif k == 'content_level':
            if isinstance(v, list):
                vals = v
            else:
                vals = [v]
            f.append({
                'terms': {'content_level': vals}
            })
        elif k == 'id':
            f.append({
                'term': {'id': v}
            })
        elif k == 'has_before_cover':
            f.append({
                'term': {'has_before_cover': v}
            })
        elif k == 'has_after_cover':
            f.append({
                'term': {'has_after_cover': v}
            })
        elif k == 'ids':
            f.append({
                'terms': {'id': v}
            })
        elif k == 'is_rating_5':
            f.append({
                'term': {'is_rating_5': v}
            })
        elif k == 'content_level_is_good':
            f.append({
                'term': {'content_level_is_good': v}
            })
        elif k == 'is_sink':
            f.append({
                'term': {'is_sink': v}
            })
        elif k == 'vote_num_gt':
            f.append({
                'range': {'vote_num': {'gt': v}}
            })

    for k, v in nfilters.items():
        if k == 'id':
            nf.append({
                'term': {'id': v}
            })
        elif k == 'user_ids' and isinstance(v, list) and v:
            for user_id in v:
                nf.append({
                    'term': {'user.id': user_id}
                })

    return f, nf


def search_diary(
        query='',
        offset=0,
        size=5,
        sort_type=DIARY_ORDER_TYPE.DEFAULT,
        filters=None,
        sort_params=None,
        device_id=None,
):
    """
    @param query: 搜索词
    @param offset: 偏移量
    @param size: 返回个数
    @param sort_type: 排序方式[POPULARITY<热度>, LAST_UPDATE_TIME<最近更新>]
    @param filters: 筛选器{"province_tag_id":<省份tag id>, "bodypart_tag_id":<一级tag id>, "bodypart_subitem_tag_id":<二级tag id>}
    @params sort_params: 筛选器排序参数

    日记本搜索
    搜索域:[
        1.医生名字
        2.项目(三级tag)
        ]
    默认排序:[
        本地在前
        匹配度
        展示顺序(ordering)，小的在前
        最后上架时间，新的在前
        ]
    其它排序:[
        销量，从高到低
        价格，更美价从低到高
        最新上架，上架时间从新到旧
        ]
    """

    #
    # 2018.10.15
    # 搜索逻辑已经迁移到 doris ，方面doris的统一管理和策略调整
    #

    tags = Tag.objects.filter(name=query).filter(tag_type__in=[TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM,
                                                               TAG_TYPE.ITEM_WIKI])
    sort_diary_tag_first = True if tags else False

    if search_diary_gray_by_device(device_id):
        remote_rpc = "doris/search/query_diary_alpha"
    else:
        remote_rpc = "doris/search/query_diary"

    res = get_rpc_remote_invoker()[remote_rpc](
        query=query,
        offset=offset,
        size=size,
        sort_type=sort_type,
        filters=filters,
        sort_params=sort_params,
        sort_diary_tag_first=sort_diary_tag_first,
        device_id=device_id
    ).unwrap()

    return res


def filter_diary(
        offset=0,
        size=5,
        sort_type=DIARY_ORDER_TYPE.LAST_UPDATE_TIME,
        filters=None,
        nfilters=None,
        sort_params=None,
        expose_total=False,
        interal_call=False,
        fields=[],
        filter_invalid_picture=False,
        use_fresh_tag=False,
        device_id=None,
        source_type=None,
):

    #
    # 2018.10.15
    # 搜索逻辑已经迁移到 doris ，方面doris的统一管理和策略调整
    #
    result = get_rpc_remote_invoker()["doris/search/query_filter_diary"](
        offset=offset,
        size=size,
        sort_type=sort_type,
        filters=filters,
        nfilters=nfilters,
        sort_params=sort_params,
        expose_total=expose_total,
        interal_call=interal_call,
        fields=fields,
        filter_invalid_picture=filter_invalid_picture,
        use_fresh_tag=use_fresh_tag,
        device_id=device_id,
        source_type=source_type,
    ).unwrap()

    return result


def scatter(data, size, fields):

    if "doctor" not in fields:
        return data

    for item in data:
        item['id'] = item['_id']
        item['group'] = item.get('_source', {}).get('doctor', {}).get("id", None)

    data = variousness(data, size)
    return data[:size]


def _get_penalty_score(doctor_times, hospital_times, original_score):
    penalty_score = (doctor_times - 1) * settings.PENALTY_FACTOR + (hospital_times - 1) * settings.PENALTY_FACTOR
    penalty_score = 30.0 if penalty_score > 30 else penalty_score
    penalty_score = original_score if penalty_score > original_score else penalty_score
    return penalty_score



def caculate_diarys_with_penlaty(hits):
    doctor_penalty_times = {}
    hospital_penalty_times = {}
    diarys_with_penlaty = []
    for hit in hits:
        source = hit['_source']
        missing_service_penalty_score = 60 if not source['has_service'] else 0
        if source['doctor']['is_authenticated']:
            doctor_id = source['doctor']['id']
            if doctor_id in doctor_penalty_times:
                doctor_penalty_times[doctor_id]['times'] = doctor_penalty_times[doctor_id]['times'] + 1
            else:
                doctor_penalty_times[doctor_id] = {'times':1}

            if 'hospital' in source['doctor']:
                try:
                    hospital_id = source['doctor']['hospital']['id']
                except KeyError:
                    hospital_id = ''
            else:
                hospital_id = ''  # fake hospital ID, shouldn't be in the first 1000 diaries

            if hospital_id in hospital_penalty_times:
                hospital_penalty_times[hospital_id]['times'] = hospital_penalty_times[hospital_id]['times'] + 1
            else:
                hospital_penalty_times[hospital_id] = {'times': 1}

            original_score = hit['sort'][1]
            duplication_penalty_score = _get_penalty_score(doctor_penalty_times[doctor_id]['times'], hospital_penalty_times[hospital_id]['times'], original_score)
            # if 'user_city_tag_id' in sort_params:
            #    user_city_tag =  sort_params['user_city_tag_id']
            final_score = original_score - duplication_penalty_score - missing_service_penalty_score
            diarys_with_penlaty.append({
                'id':source['id'],
                'original_score':original_score,
                'duplication_penalty_score': duplication_penalty_score,
                'missing_service_penalty_score': missing_service_penalty_score,
                'final_score': final_score,
            })
        else:
            diarys_with_penlaty.append({
                'id':source['id'],
                'original_score':hit['sort'][1],
                'duplication_penalty_score': 0,
                'missing_service_penalty_score': missing_service_penalty_score,
                'final_score': hit['sort'][1],
            })
    return diarys_with_penlaty


def get_es_diary_data(sort_params, filters):
    hits = filter_diary(
            offset=0,
            size=1000,
            filters=filters,
            sort_params=sort_params,
            sort_type=DIARY_ORDER_TYPE.INDEX,
            interal_call=True
    )['hits']
    return hits


#@cache_page(300)
def filter_diary_index_mix(sort_params, filters):
    """
    根据查询参数&排序参数进行es查询
    注: 函数内部做缓存, 根据传入的过滤参数及排序参数精细定制, 使用需谨慎!!!

    :param filters: {'city_id': current_city_id, 'content_level_is_good': True, 'tag_ids': []}     # 其中city_id传入的情况只为None, 并且后续未处理, 不作为缓存key使用
    :param sort_params: {'user_city_tag_id': user_city_tag_id} or None
    :return:
    """
    cache_params = {
        'content_level_is_good': filters.get('content_level_is_good'),
        'tag_ids': sorted(filters['tag_ids']) if filters.get('tag_ids') is not None else None,
        'user_city_tag_id': sort_params and sort_params.get('user_city_tag_id') or None
    }
    cache_index = 'func_filter_diary_index_mix'
    cache_server = CacheServer(cache_index, cache_params)
    _existing, cache_result = cache_server.get_result_from_cache()
    if _existing:
        return cache_result

    hits = get_es_diary_data(sort_params, filters)
    diarys_with_penlaty = caculate_diarys_with_penlaty(hits)
    diarys_with_penlaty.sort(key=lambda x:x['final_score'], reverse=True)
    diary_id_list = [diary['id'] for diary in diarys_with_penlaty]
    cache_server.set_result_to_cache(diary_id_list)
    return diary_id_list
