# coding=utf8
from operator import itemgetter

from django.conf import settings

from api.models.types import DIARY_ORDER_TYPE
from api.tool.user_tool import get_user_from_context
from api.tool.geo_tool import get_geo_related_tags
from api.models import DiarySpreadFit, City, Tag

from rpc.decorators import bind_context, bind
from rpc.decorators import list_interface
from rpc.tool.log_tool import search_logger
from rpc.context import get_rpc_remote_invoker

from search.utils.common import get_query_matched_tags
from search.utils.diary import search_diary
from search.utils.diary import filter_diary, filter_diary_index_mix

from services.talos_service import get_diary_info_for_es_query
from statistic.tasks import add_diary_pv
from search.utils.service import service_scatter_by_city


@bind_context("search/diary/esquery")
@list_interface(offset_name='offset', limit_name='size')
def search_es_diary(
        ctx,
        query='',
        offset=0,
        size=5,
        sort_type=DIARY_ORDER_TYPE.DEFAULT,
        filters={},
        current_city_id=None,
        device_id=None
):
    """
    :param query:
    :param offset:
    :param size:
    :return:
        total 匹配结果数量
        id
        pre_operation_image  封面术前图
        post_operation_image  封面术后图
        相关医生列表
        相关美购列表
        相关机构列表
        tags  相关标签列表
    """

    user = get_user_from_context(ctx)
    rpc_client = get_rpc_remote_invoker()
    results = []
    diary_ids = []

    sort_params = {}
    if current_city_id:
        c, p, ci = get_geo_related_tags(city_id=current_city_id)
        if p and ci:
            sort_params['user_city_tag_id'] = ci
            sort_params['user_province_tag_id'] = p
            rs = rpc_client['doris/hera/in_city_whitelist'](city_ids=[current_city_id]).unwrap()
            sort_params['in_whitelist'] = 1 if rs else 0

    search_logger.info(
        u"search args: {} {} {} {} {} {}".format(
            query, offset, size, sort_type, filters, sort_params
        )
    )

    if sort_type == DIARY_ORDER_TYPE.SEARCH_V1 and offset + size <= 80:
        es_data = search_diary(
            query=query,
            offset=0,
            size=80,
            sort_type=sort_type,
            filters=filters,
            sort_params=sort_params,
            device_id=device_id
        )
        rs = []
        for hit in es_data['hits']['hits']:
            item = {
                'diary_id': hit['_source']['id'],
                'merchant_id': hit['_source'].get('service', {}).get('doctor_id', -1),
                'city_tag_id': hit['_source'].get('doctor', {}).get('hospital', {}).get('city_tag_id', -1),
                'nearby_city_tags': list(hit['_source'].get('nearby_city_tags_list')),
            }
            rs.append(item)

        in_whitelist = sort_params.get('in_whitelist', 0)
        user_city_tag_id = sort_params.get('user_city_tag_id', -1)
        rs = service_scatter_by_city(rs, user_city_tag_id, size, merge_nearby=not in_whitelist)
        rs = rs[offset:offset + size]

        # restruct search data order.
        id2val = dict()
        for hit in es_data['hits']['hits']:
            id2val[hit['_source']['id']] = hit

        hits = es_data['hits']['hits'] = []
        for elt in rs:
            diary_id = elt['diary_id']
            hits.append(id2val[diary_id])
    else:
        es_data = search_diary(
            query=query,
            offset=offset,
            size=size,
            sort_type=sort_type,
            filters=filters,
            sort_params=sort_params,
            device_id = device_id
        )

    total = es_data['hits']['total']
    hits = es_data["hits"]["hits"]
    _diary_ids = [h['_id'] for h in hits]
    if user:
        user_id = user.id
    else:
        user_id = None
    diary_infos = get_diary_info_for_es_query(_diary_ids, user_id)
    diary_info_dict = {str(d['id']): d for d in diary_infos}
    for hit in hits:
        source = {}
        id = hit['_id']
        diary_data = diary_info_dict.get(id, None)
        if not diary_data:
            search_logger.info(u"Diary {} not found".format(id))
            continue

        source['id'] = diary_data['id']
        source['pre_operation_image'] = diary_data['pre_operation_image']
        source['post_operation_image'] = diary_data['post_operation_image']
        source['created_time'] = diary_data['created_time']
        source['diary_amount'] = diary_data['diary_amount']
        source['service'] = hit['_source'].get('service')
        source['hospital'] = hit['_source'].get('hospital')
        source['doctor'] = hit['_source'].get('doctor')

        source.update(diary_data)
        source['last_name'] = source['user_nickname']
        source['topic_num'] = diary_data['diary_num']
        source['diary_tags'] = [{'tag_id': str(tag["id"]), 'name': tag["name"]} for tag in source['tags']]
        source['tags'] = hit['_source'].get('tags', '')
        source['score'] = hit.get('_score', 0)
        highlight = hit.get('highlight', None)
        results.append({
            'source': source,
            'highlight': highlight,
            'data': diary_data,
        })
        diary_ids.append(source['id'])

    add_diary_pv.delay(diary_ids)
    ctx.logger.app(search_esquery=dict(type='diary', query=query, offset=offset, recall_count=len(hits)))
    return {
        'matched_tag_ids': [tag.id for tag in get_query_matched_tags(query)],
        'hits': results,
        'total': total
    }

@bind("search/diary/esquery_feed")
@list_interface(offset_name='offset', limit_name='size')
def search_es_diary(ids, offset=0, size=15, sort_type=DIARY_ORDER_TYPE.FEED_FILTER):
    """
    :param ids: diary_ids的候选集
    :param offset:
    :param size:
    :return:
        按照es搜索规则业务逻辑实时过滤
    """
    filters = {'ids': list(ids)}
    result = []
    es_data = search_diary(offset=offset, size=size, sort_type=sort_type, filters=filters)
    if es_data.get('hits', None) and es_data['hits'].get('hits', None):
        hits = es_data['hits']['hits']
        result = [hit['_id'] for hit in hits if reduce(lambda x, y: x * y, hit['sort']) > 0]

    recall = [id_ for id_ in ids if id_ in result]
    return recall


@bind('api/diary/filter_diary')
def api_filter_diary(offset=0, size=10, sort_type=None, device_id=None, source_type=None,
                     filters=None, nfilters=None, sort_params=None, expose_total=False,
                     fields=[], filter_invalid_picture=False, use_fresh_tag=False):
    """
    筛选diary
    :param offset:
    :param size:
    :param sort_type:
    :param filters:
    :return:
    """
    return filter_diary(offset=offset, size=size, sort_type=sort_type, filters=filters,
                        nfilters=nfilters, sort_params=sort_params, device_id=device_id, source_type=source_type,
                        expose_total=expose_total, fields=fields,
                        filter_invalid_picture=filter_invalid_picture,
                        use_fresh_tag=use_fresh_tag)


@bind('api/diary/filter_diary_index_mix')
def api_filter_diary_index_mix(offset, size, sort_params, filters=None, sort_type=None):
    """
    backend 无调用
    :param offset:
    :param size:
    :param sort_params:
    :param filters:
    :return:
    """
    city_id = filters.get('city_id') if filters else None
    spread = None
    if city_id:
        try:
            spread = DiarySpreadFit.objects.get(city_id=city_id)
        except DiarySpreadFit.DoesNotExist:
            pass

    if spread is None:
        diary_ids = filter_diary_index_mix(sort_params=sort_params, filters=filters)
        return diary_ids[offset: offset+size]

    scale = float(spread.commoncity) / spread.bescity
    o1, s1 = int(offset*scale), int(size*scale)
    o2, s2 = int(offset*(1-scale)), int(size*(1-scale))
    nearby_city_tag_ids = get_nearby_city_tag_ids(city_id)

    f1 = {'content_level_is_good': True}
    f2 = {'tag_ids': nearby_city_tag_ids, 'content_level_is_good': True}
    local_diary = filter_diary_index_mix(sort_params=sort_params, filters=f1)[o1: o1+s1]
    nearby_diary = filter_diary_index_mix(sort_params={}, filters=f2)[o2: o2+s2]
    return local_diary + nearby_diary


def get_nearby_city_tag_ids(city_id):
    """

    :param city_id:
    :return:
    """
    try:
        province_id = City.objects.get(id=city_id).province_id
    except City.DoesNotExist:
        return []

    nearby_province_ids = settings.NEARBY_REGION.get(province_id, [])
    nearby_city_names = City.objects.filter(province_id__in=nearby_province_ids).values_list("name", flat=True)
    nearby_city_tag_ids = Tag.objects.filter(name__in=nearby_city_names).values_list('id', flat=True)
    return list(nearby_city_tag_ids)
