# coding=utf8

from __future__ import unicode_literals, absolute_import, print_function

import json
import random
import datetime
from collections import defaultdict

from django.conf import settings
from django.db.models import Count, Max, Q, Sum
from django.utils.html import escape

from gm_types.gaia import DIARY_ORDER_TYPE
from gm_types.gaia import DIARY_CONTENT_LEVEL
from gm_types.gaia import TAG_TYPE
from gm_types.gaia import SERVICE_COMMENT_OPTION
from gm_types.error import ERROR as CODES

from gm_rpcd.all import bind

from utils.rpc import gen, rpc_client, logging_exception, assert_uint, get_current_user

from talos.statistic import diary_view_increase_num, topic_view_increase_num
from talos.rpc import bind_context
from talos.decorators import list_interface

from talos.cache.base import hospital_topics_num_cache, diary_base_info_cache
from talos.libs.datetime_utils import get_datetime
from talos.libs.datetime_utils import get_timestamp
from talos.manager.diary import diary_list_manager

from talos.models.diary import Diary, DiaryExtra, MAX_STICK_PRIORITY, DEFAULT_STICK_PRIORITY, DiaryVote
from talos.models.topic import Problem

from talos.services import GoodsService, UserService
from talos.services import TagService
from talos.services import get_location_tag_id_by_city_id
from talos.services import get_user_from_context
from talos.libs.image_utils import get_full_path

def _get_topic_pageview(id):
    return Problem.get_view_amount_of_id(id)


# @bind_context('talos/diaries')
@bind_context('diary/list')
@list_interface(offset_name='start_num', limit_name='count',
                element_model=Diary,
                element_func_list=[Diary.get_diary_info, ])
def get_diary_list(
        ctx, count=10, start_num=0, target_user_id=None,
        service_id=None, hospital_ids=None,
        doctor_id=None, hospital_id=None, province_id='nationwide',
        order_by=None, is_pc=False, filter_tag_ids=None, area_id=None,
        wiki_id=None, body_part_id=None, sub_item_id=None, has_before_cover=False,
        has_after_cover=False, need_service=True,
):
    """话题列表
       count: 每次请求记录条数, 默认为10
       start_num: 起始条数, 默认为0
       service_id: 福利相关日记本筛选
       query: 日记本搜索, 从标题和内容中进行搜索
       doctor_id: 医生筛选
       hospital_id: 机构筛选
       province_id: 省份筛选, 默认为全国

       NOTE: wiki_id=None, body_part_id=None, sub_item_id=None for compatible only

       v 7.6.25 新加参数 bool类型
       has_before_cover 判断封面 是否有 before 图片
       has_after_cover  判断封面 是否有 after 图片

    """

    user = get_user_from_context(ctx)

    if order_by is None:
        if doctor_id or hospital_id:
            order_by = DIARY_ORDER_TYPE.CREATE_TIME
        else:
            order_by = DIARY_ORDER_TYPE.LAST_UPDATE_TIME

    filters = {}

    if province_id and province_id != 'nationwide':
        filters['province_tag_id'] = province_id

    if area_id:
        filters['area_tag_id'] = area_id
        if 'province_tag_id' in filters:
            del filters['province_tag_id']

    if doctor_id:
        filters['doctor_id'] = doctor_id

    if hospital_id:
        filters['hospital_id'] = hospital_id

    if hospital_ids:
        filters['hospital_ids'] = [hospital_ids]

    if target_user_id:
        filters['user_id'] = target_user_id

    if service_id:
        filters['service_id'] = service_id
        filters['service_id'] = assert_uint(filters['service_id'])

    tag_ids = []

    if filter_tag_ids:
        if not isinstance(filter_tag_ids, list):
            filter_tag_ids = [filter_tag_ids]
        tag_ids.extend(filter_tag_ids)

    if tag_ids:
        filters['tag_ids'] = tag_ids

    if has_before_cover:
        filters["has_before_cover"] = True

    if has_after_cover:
        filters["has_after_cover"] = True

    diary_ids = rpc_client['api/diary/filter_diary'](offset=start_num, size=count,
                             sort_type=order_by, filters=filters).unwrap()
    ids = diary_ids["diary_ids"]

    topic_ids = []
    result = diary_list_manager.get_diary_list_by_diary_ids_with_doctor_hospital_name(
        ids=ids, viewer_user_id=user.id, is_online=True, need_service=need_service
    )
    for r in result:
        topic_ids.append(r['latest_topic_id'])

    topic_view_increase_num(topic_ids)
    diary_view_increase_num(ids)

    return result


@bind_context('diary/list_v2')
@list_interface(
    offset_name='start_num',
    limit_name='count',
    element_model=Diary,
    element_func_list=[Diary.get_diary_info, ]
)
def diary_filter_list(ctx, start_num=0, count=10, service_id=None, doctor_id=None,
                      exclude_user=[], sort_type=DIARY_ORDER_TYPE.SERVICE_RELATED):
    """
    获取美购相关的医生的所有日记 当前美购相关的排在前面
    start_num 起始位置
    count 数量
    service_id 相关联美购id(int)
    exclude_user 需要排除的用户
    """
    filters = {}
    user = get_user_from_context(ctx)

    if doctor_id:
        filters['doctor_id'] = doctor_id

    if service_id:
        filters['service_id'] = service_id

    result = rpc_client['api/diary/filter_diary'](
        offset=start_num,
        size=count,
        sort_type=sort_type,
        filters=filters,
        nfilters={
            'user_ids': exclude_user,
        },
        sort_params={
            'service_id': service_id,
        },
        expose_total=True).unwrap()
    ids = result['diary_ids']
    resp = {
        'diaries': [],
        'total': result['total'],
    }

    result = diary_list_manager.get_diary_list_by_diary_ids(ids=ids, viewer_user_id=user.id)
    diary_ids = list(map(lambda x: x['id'], result))
    diary_view_increase_num(diary_ids)
    resp['diaries'] = result
    return resp


@bind_context('diary/service_detail_list')
@list_interface(
    offset_name='start_num',
    limit_name='count',
    element_model=Diary,
    element_func_list=[Diary.get_diary_info, ]
)
def diary_filter4service_detail(ctx, start_num=0, count=10, service_id=None):
    """美购详情页相关日记本
    """
    resp = {
        'diaries': [],
        'total': 0,
    }

    service = GoodsService.get_service_doctor_info_by_id(id=service_id)
    if not service:
        return resp

    elif not (service['doctor_id'] and service['doctor_user_id']):
        return resp

    target_bodypart_id, target_tags_ids = TagService.get_filter_options_for_filter_service_diaries(service_id)
    if target_bodypart_id is None:
        return resp

    # 筛选一级tag相同的
    filters = {
        'tag_ids': [target_bodypart_id],
        'service.doctor_id': service['doctor_id'],
    }

    nfilters = {'user_ids': [service['doctor_user_id']]}

    result = rpc_client['api/diary/filter_diary'](
        offset=start_num,
        size=count,
        sort_type=DIARY_ORDER_TYPE.SERVICE_DETAIL,
        filters=filters,
        nfilters=nfilters,
        sort_params={'same_tag_ids_count': target_tags_ids},
        expose_total=True).unwrap()
    ids = result['diary_ids']
    resp['total'] = result['total']

    if ids:
        diary_view_increase_num(ids)

        user = get_user_from_context(ctx)
        result = diary_list_manager.get_diary_list_by_diary_ids(ids=ids, viewer_user_id=user.id)
        resp['diaries'] = result

    return resp


@bind('diary/list/related')
@list_interface(
    offset_name='start_num',
    limit_name='count',
    element_model=Diary,
    element_func_list=[Diary.get_diary_info, ]
)
def diary_related(diary_id, count=3, start_num=0, exclude_user=[]):
    """
    当前日记本tag，任意选取一个(第一个)，其它日记本有此tag的，即为相关日记
    排除自己的日记
    :param diary_id:
    :param count:
    :param start_num:
    :return: 相关日记本列表
    """
    try:
        diary = Diary.objects.get(id=diary_id)
    except Diary.DoesNotExist:
        return None

    tags = list(filter(lambda x: x.tag_type in [TAG_TYPE.ITEM_WIKI, TAG_TYPE.BODY_PART_SUB_ITEM], diary.tags))
    tag = tags and tags[0]

    if not tag:
        return None

    _es_fetch_count = int(count * 1.6)
    diary_ids = rpc_client['api/diary/filter_diary'](
        offset=start_num,
        size=_es_fetch_count,
        sort_type=DIARY_ORDER_TYPE.YOU_MAY_LIKE,
        filters={
            'tag_ids': [tag.id, ],
            'has_cover': True,
        },
        nfilters={
            'id': diary_id,
            'user_ids': exclude_user,
        }).unwrap()
    ids = diary_ids["diary_ids"]
    result = diary_list_manager.get_diary_list_by_diary_ids(ids=ids)
    result = {
        'diaries': result[:count],
    }

    return result


@bind_context("diary/filter")
@list_interface(offset_name='offset', limit_name='limit', element_model=Diary, element_func_list=[Diary.get_diary_info])
def api_filter_diary(ctx, order_by=DIARY_ORDER_TYPE.LAST_UPDATE_TIME, filters={}, offset=0, limit=10, simple=True,
                     with_count=False, need_service_item_info=False):
    diary_ids = rpc_client['api/diary/filter_diary'](
        offset=offset, size=limit,
        sort_type=order_by, filters=filters,
        expose_total=with_count
        ).unwrap()
    ids = diary_ids["diary_ids"]

    diary_view_increase_num(ids)

    user = get_user_from_context(ctx)
    result = diary_list_manager.get_diary_list_by_diary_ids(
        ids=ids,
        viewer_user_id=user.id,
        need_service_item_info=need_service_item_info
    )

    if not with_count:
        return result

    return {
        'diaries': result,
        'total_count': diary_ids['total'],
    }


@bind_context("diary/index")
@list_interface(offset_name='offset', limit_name='limit', element_model=Diary)
def diary_index(
        ctx,
        offset=0,
        limit=10,
        filters={'is_headline': True},
        sort_type=DIARY_ORDER_TYPE.LAST_UPDATE_TIME,
        current_city_id=None
):
    """get diary list for index page.

    TODO:
        cache this page
    """
    user = get_user_from_context(ctx)

    _, city_tagid = get_location_tag_id_by_city_id(current_city_id)
    if city_tagid is not None:
        sort_params = {'user_city_tag_id': city_tagid}
    else:
        sort_params = None

    result = {}
    result["diaries"] = []

    filters['has_cover'] = True

    diary_ids = rpc_client['api/diary/filter_diary'](
        offset=offset * 2,
        size=limit * 2,
        filters=filters,
        sort_params=sort_params,
        sort_type=sort_type
        ).unwrap()['diary_ids']

    random.shuffle(diary_ids)
    diary_ids = diary_ids[:limit]

    diary_info = diary_list_manager.get_diary_list_by_diary_ids(diary_ids, user.id)
    result['diaries'] = diary_info

    diary_view_increase_num(diary_ids)

    return result


@bind_context("diary/index/mix")
@list_interface(offset_name='offset', limit_name='limit', element_model=Diary)
def diary_index_mix(ctx, offset=0, limit=10, filters={}, current_city_id=None, sort_type=None, **kwargs):
    """get diary list for index page.

    TODO:
        cache this page
    """
    user = get_user_from_context(ctx)
    user_city_tag_id = user.city_tag_id
    page = kwargs.get("page")
    scale = settings.DIARY_RANDOM_SCALE.get(current_city_id, 2)

    if user_city_tag_id:
        user_city_tag_id = assert_uint(user_city_tag_id)

    country_tagid, city_tagid = get_location_tag_id_by_city_id(current_city_id)
    user_city_tag_id = city_tagid and city_tagid or user_city_tag_id
    if user_city_tag_id is not None:
        sort_params = {'user_city_tag_id': user_city_tag_id}
    else:
        sort_params = None

    result = {}
    result["diaries"] = []

    # 先保留原逻辑代码,防止产品改回相似逻辑
    # diary_ids_1 = filter_diary(
    #     offset=offset,
    #     size=limit,
    #     sort_params=sort_params,
    #     sort_type=DIARY_ORDER_TYPE.INDEX
    # )['diary_ids']
    #
    # diary_ids_2 = filter_diary(
    #     offset=offset + 50,
    #     size=60,
    #     sort_params=sort_params,
    #     sort_type=DIARY_ORDER_TYPE.INDEX
    # )['diary_ids']
    # random.shuffle(diary_ids_1)
    # random.shuffle(diary_ids_2)
    #
    # limit_1 = int(0.5 * limit)
    # limit_2 = limit - limit_1
    # diary_ids = diary_ids_1[:limit_1] + diary_ids_2[:limit_2]
    # random.shuffle(diary_ids)
    # diary_ids = diary_ids[:limit]
    ######################

    if page is not None:
        offset = page * 20
        size = 20
    else:
        offset *= scale
        size = limit * scale

    diary_ids = rpc_client['api/diary/filter_diary_index_mix'](
        offset=offset,
        size=size,
        filters={'city_id': current_city_id, 'content_level_is_good': True},
        sort_params=sort_params,
    ).unwrap()
    random.shuffle(diary_ids)
    diary_ids = diary_ids[:limit]

    diary_info = diary_list_manager.get_diary_list_by_diary_ids(diary_ids, user.id)

    result['diaries'] = diary_info
    diary_view_increase_num(diary_ids)

    return result


def get_my_diary_list_common_func(user, start_num, count, topics_is_null):
    """
    获取我的日记本列表公共方法
    :param user:
    :param start_num:
    :param count:
    :param topics_is_null:
    :return:
    """
    query = Q(user_id=user.id, is_online=True)
    if not topics_is_null:
        query &= Q(topics__isnull=False)
    diaries = Diary.objects.filter(query)
    diaries = diaries.distinct()
    diaries = diaries.order_by('-last_modified')
    if not topics_is_null:
        diaries = list(filter(lambda d: d.topics.filter(is_online=True).filter(flag='n').exists(), diaries))
    diaries = diaries[start_num: start_num + count]

    return diaries


@bind("diary/my")
@list_interface(offset_name='start_num', limit_name='count', element_model=Diary)
def my_diary_list(start_num=0, count=10, topics_is_null=False):
    """
    我的日记本列表
    :param ctx:
    :param start_num:
    :param count:
    :param topics_is_null: whether add the empty diaries
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    diaries = get_my_diary_list_common_func(user, start_num, count, topics_is_null)
    result = diary_list_manager.get_diary_list_by_diary_objs(diaries, viewer_user_id=user.id)

    diary_ids = [d.id for d in diaries]
    diary_view_increase_num(diary_ids)

    return {'diaries': result}


@bind("diary/my_v2")
@list_interface(offset_name='start_num', limit_name='count', element_model=Diary)
def my_diary_list_v2(start_num=0, count=10, topics_is_null=False):
    """
    我的日记本列表 新接口，主要是上个接口返回的数据太多了……严重影响性能！！！！
    :param start_num:
    :param count:
    :param topics_is_null:
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    diaries = get_my_diary_list_common_func(user, start_num, count, topics_is_null)

    result = diary_list_manager.get_simple_diary_list_by_diary_objs(diaries)

    diary_ids = [d.id for d in diaries]
    diary_view_increase_num(diary_ids)

    return {'diaries': result}


def _get_topic_image(topic):
    image = topic.post_operation_image
    if not image:
        image = topic.pre_operation_image
    return image


@bind("diary/recent")
@list_interface(offset_name='start_num', limit_name='count')
def diary_recent(start_num=0, count=50, is_page=False):
    """v5.3.0 我的日记本列表
    """

    def handle_cover(diary):
        if diary.post_operation_image:
            return diary.post_operation_image
        newest_cover = diary.newest_cover()
        if newest_cover == settings.DIARY_COVER_DEFAULT:
            return ''
        else:
            return newest_cover

    def handle_doctor_city(diary):  # TODO CR 需要通知产品修改逻辑

        if diary.hospital_id and diary.hospital.city:  # TODO city
            return diary.hospital.city.name  # TODO city
        for tag in diary.tags:
            if tag.tag_type == TAG_TYPE.CITY:
                return tag.name
        return ''

    def handle_titles(diaries):
        service_ids = list(map(lambda x: x.service_id, diaries))
        services = GoodsService.get_service_by_service_ids(service_ids)
        services_dict = {s['id']: s for s in services}
        result = {}
        for d in diaries:
            if d.service_id:
                service = services_dict.get(d.service_id, None)
                result[d.id] = service and service['name']
            else:
                tags = [
                           tag.name
                           for tag in d.tags
                           if tag.tag_type in [TAG_TYPE.ITEM_WIKI, TAG_TYPE.BODY_PART_SUB_ITEM]
                       ][:MAX_TAGS_COUNT]
                result[d.id] = tags and tags[0] or d.title or u'我的更美日记'

        return result

    def handle_subtitles(diaries):
        """
        title: 美购所属地和医院（医生）（优先取医院）, 无美购关联返回空
        """
        service_ids = list(map(lambda x: x.service_id, diaries))
        diary_show_info = GoodsService.get_diary_show_info_by_service_ids(service_ids)
        result = {}
        for diary in diaries:
            if not diary.service_id:
                result[diary.id] = u''
            else:
                info = diary_show_info[diary.service_id]
                city_name = info['city_name']
                hospital_name = info['hospital_name']
                doctor_name = info['doctor_name']
                if city_name and (hospital_name or doctor_name):
                    result[diary.id] = u'{}@{}'.format(city_name, doctor_name or hospital_name)
                else:
                    result[diary.id] = u''
        return result

    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    MAX_TAGS_COUNT = 3

    all_diaries = Diary.objects.filter(user_id=user.id, is_online=True)
    service_diaries = all_diaries.filter(service_id__isnull=False).order_by('-last_modified', '-id')
    normal_diaries = all_diaries.filter(service_id__isnull=True).order_by('-last_modified', '-id')
    service_diaries_num = service_diaries.count()

    if is_page:
        service_diaries = service_diaries[start_num:start_num + count]

        if service_diaries.count() == 0:
            start_num = start_num - service_diaries_num
            normal_diaries = normal_diaries[start_num:start_num + count]

        elif service_diaries.count() < count:
            count = count - service_diaries.count()
            normal_diaries = normal_diaries[0:count]

        else:
            normal_diaries = []

    else:
        # NOTE: return 50 diaries at most
        service_diaries = service_diaries[start_num:start_num + count]

        # NOTE: return 50 diaries at most
        normal_diaries = normal_diaries[start_num:start_num + count]

    diary_set = list(service_diaries) + list(normal_diaries)

    diaries = []
    diary_titles = handle_titles(diary_set)
    diary_subtitles = handle_subtitles(diary_set)
    for diary in diary_set:
        tags = [
                   tag.name
                   for tag in diary.tags
                   if tag.tag_type in [TAG_TYPE.ITEM_WIKI, TAG_TYPE.BODY_PART_SUB_ITEM]
               ][:MAX_TAGS_COUNT]

        diaries.append(
            {
                'is_service_diary': True if diary.service_id else False,
                'id': diary.id,
                'topics_num': diary.topic_num,
                'tags': tags,
                'title': diary_titles[diary.id],
                'subtitle': diary_subtitles[diary.id],  # Add since v6.9.5
                'like_num': diary.like_num,
                'image': handle_cover(diary),
                'created_time': get_timestamp(diary.created_time),
                'last_modified': get_timestamp(diary.last_modified),
                'operation_time': get_timestamp(diary.operation_time),  # 用户选择的治疗时间
                'comment_count': (
                    diary.rate_count
                    if diary.order_id else 0
                ),
            }
        )

    return {'result': diaries}


@bind('diary/list_for_doctor')
@list_interface(offset_name='start_num', limit_name='count', element_model=Diary)
def diary_list(start_num=0, count=10, doctor_id=None):
    """
        医生的日记本列表（案例列表）
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    diary_queryset = Diary.objects.filter(
        doctor_id=doctor_id,
        is_online=True,
        topics__isnull=False,
        topics__flag='n',
        topics__is_online=True
    ).distinct().order_by('-sticky_post', '-id').values_list('id', flat=True)
    diary_ids = list(diary_queryset)
    result = {'total_count': len(diary_ids), 'diaries': []}
    sliced_diary_ids = diary_ids[start_num: start_num + count]
    diary_data = diary_list_manager.get_diary_list_by_diary_ids_with_doctor_hospital_name(
        sliced_diary_ids, viewer_user_id=user.id
    )
    result['diaries'] = diary_data
    return result


@bind('diary/list_for_bz_backend')
def list_for_bz_backend(service_ids, start_num=0,
                        count=10, created_time_gte=None,
                        created_time_lt=None, order_by=None, is_stick_priority=None,
                        diary_or_user_ids=None, is_import=None):

    init_query = query = Q(service_id__in=service_ids) & \
            Q(extra_info__isnull=False)

    if created_time_gte:
        created_time_gte = get_datetime(created_time_gte)
        query &= Q(created_time__gte=created_time_gte)

    if created_time_lt:
        created_time_lt = get_datetime(created_time_lt)
        query &= Q(created_time__lt=created_time_lt)

    if is_stick_priority == 0:
        query &= Q(stick_priority=DEFAULT_STICK_PRIORITY)
    elif is_stick_priority == 1:
        query &= Q(stick_priority__lte=MAX_STICK_PRIORITY)

    if is_import == 0:
        query &= Q(is_import=False)
    elif is_import == 1:
        query &= Q(is_import=True)

    if diary_or_user_ids and isinstance(diary_or_user_ids, list):
        query &= (Q(id__in=diary_or_user_ids) | Q(user_id__in=diary_or_user_ids))

    # 排序
    # diary.topic_num/view_num/vote_num/reply_num
    # extra_info__topic_count/reply_count/vote_count/total_pv
    # 针对统计表中的字段排序
    diary_extra_order = {
        DIARY_ORDER_TYPE.TOPIC_COUNT: ('', "topic_count"),
        DIARY_ORDER_TYPE.TOPIC_COUNT_DESC: ('-', "topic_count"),
        DIARY_ORDER_TYPE.VOTE_COUNT:  ('', "vote_count"),
        DIARY_ORDER_TYPE.VOTE_COUNT_DESC:  ('-', "vote_count"),
        DIARY_ORDER_TYPE.TOTAL_PV:  ('', "total_pv"),
        DIARY_ORDER_TYPE.TOTAL_PV_DESC:  ('-', "total_pv"),
    }

    # 针对diary表中的字段排序
    diary_order = {
        DIARY_ORDER_TYPE.REPLY_COUNT: ('', "reply_num"),
        DIARY_ORDER_TYPE.REPLY_COUNT_DESC: ('-', "reply_num"),
        DIARY_ORDER_TYPE.CONCONTENT_LEVEL: ('', "content_level"),
        DIARY_ORDER_TYPE.CONCONTENT_LEVEL_DESC: ('-', "content_level"),
    }

    order_by = str(order_by) if order_by and isinstance(order_by, int) else None
    order_type, order_field = diary_extra_order.get(order_by, (None, None))
    if order_field:
        diaries = Diary.objects.filter(query). \
                  order_by("%sextra_info__%s" % (order_type, order_field))
    else:
        order_type, order_field = diary_order.get(order_by, (None, None))
        if order_field:
            diaries = Diary.objects.filter(query). \
                      order_by("%s%s" % (order_type, order_field))
        else:
            diaries = Diary.objects.filter(query)

    # 返回置顶信息
    stick_info_list = []
    stick_map = {}
    stick_info = list(Diary.objects.filter(init_query).filter(stick_priority__lte=MAX_STICK_PRIORITY).values('stick_priority', 'service_id').all())
    [stick_map.setdefault(item['service_id'], []).append(item['stick_priority']) for item in stick_info]
    for k,v in stick_map.items():
        stick_info_list.append({
            'service_id':k,
            'stick_priorities': sorted(list(set(v)))
        })

    total = diaries.count()

    diaries = diaries[start_num: start_num + count]
    result = diary_list_manager.get_diary_list_by_diary_objs(diaries)
    return {
        'diaries': result,
        'count': total,
        'stick_info': stick_info_list
    }


@bind('diary/diary_info_by_service_ids')
def diary_info_by_service_ids(service_ids, count_only=False, other_query={}):
    query = Q()
    #Query是针对DiaryCheck的
    if "content_level" in other_query:
        query = Q(content_level__in=other_query.get("content_level", []))

    if count_only:
        diaries = Diary.objects.filter(  # refer api.models.Service.diaries_count
            service_id__in=service_ids,
            is_online=True, topics__isnull=False, topics__flag='n',
            topics__is_online=True
        )  # with topics joined
        result = {
            service_id: {
                'service_id': service_id,
                'diary_count': 0,
                'diaries': []
            } for service_id in service_ids
        }
        q = diaries.values('service_id').annotate(diary_count=Count('id', distinct=True))
        for dcnt in q:
            result[dcnt['service_id']]['diary_count'] = dcnt['diary_count']
        return list(result.values())
    else:
        diaries = Diary.objects.filter(  # refer api.models.Service.diaries_count
            query,
            service_id__in=service_ids,
            is_online=True,
        )
        result, temp_save = [], []
        #每一个日记帖就返回一个数据， temp_save过滤重复数据
        for item in diaries:
            if item.id not in temp_save:
                result.append({
                    "diary_id": item.id,
                    "diary_title": item.title,
                })
                temp_save.append(item.id)

        return result


@bind_context('diary/list_data_get_by_ids')
def talos_diary_list_data_get_by_ids(ctx, ids):
    assert len(ids) <= 100, 'too many diary_ids'
    user = get_user_from_context(ctx)
    result = diary_list_manager.get_diary_list_by_diary_ids(ids, viewer_user_id=user.id)
    diary_view_increase_num(ids)
    return {'diaries': result}


@bind('diary/get_service_case_list')
def talos_service_case(service_id, get_detail=True):
    diaries = Diary.objects.filter(
        service_id=service_id, is_online=True, topics__isnull=False, topics__flag='n', topics__is_online=True
    ).distinct()
    count = diaries.count()
    diaries = []
    if get_detail:
        diaries = Diary.objects.filter(
            service_id=service_id, is_online=True, topics__isnull=False, topics__flag='n', topics__is_online=True
        ).distinct().order_by('-last_topic_add_time')

        ids = []
        for diary in diaries:
            if len(ids) < 10:
                if diary.vote_num > 30:
                    if diary.post_operation_image:
                        ids.append(diary.id)

        diaries = diary_list_manager.get_diary_list_for_service_case(ids)
    return {
        'count': count,
        'diaries': diaries
    }


@bind('diary/get_service_image')
def talos_service_diary_image(service_id, start_num, count):
    ids = Diary.objects.filter(
        service_id=service_id,
        is_online=True
    ).exclude(post_operation_image='').order_by('-last_modified').values_list(
        'id',
        flat=True
    )[start_num:start_num + count]
    result = diary_list_manager.get_diary_image_list_by_diary(ids)
    return result


@bind('diary/special_recommend_list')
@list_interface(offset_name='start_num', limit_name='count')
def get_special_recommend_diaries(special_id, start_num, count):
    try:
        diary_ids = rpc_client['doris/search/get_diaries_by_special_id'](
            special_id=special_id, offset=start_num, size=count
        ).unwrap()
    except Exception as e:
        print(e)
        logging_exception()
        return []

    diary_ids = diary_ids.get('diaries', [])
    result = []
    if diary_ids:
        topic_ids = []
        result = diary_list_manager.get_diary_list_by_diary_ids_with_doctor_hospital_name(
            ids=diary_ids
        )
        for r in result:
            topic_ids.append(r['latest_topic_id'])

        topic_view_increase_num(topic_ids)
        diary_view_increase_num(diary_ids)

    return result


@bind_context('diary/diary_list_for_index')
def talos_diary_list_for_index(ctx, diary_ids, get_last_video=False):
    assert len(diary_ids) <= 100, 'too many diary_ids'
    user = get_user_from_context(ctx)
    diary_info = diary_list_manager.get_diary_list_by_diary_ids(diary_ids, user.id, get_last_video)
    diary_view_increase_num(diary_ids)
    return diary_info


@bind_context('diary/simple_diary_list')
def simple_diary_list(ctx, diary_ids, index=False):
    if not diary_ids:
        diary_ids = []

    user = get_current_user()
    user_id = user and user.id or None
    diaries_info = diary_list_manager.list_diary_by_ids(
        diary_ids,
        user_id,
        index=index
    )
    diary_view_increase_num(diary_ids)
    return diaries_info


@bind('diary/list_diary_info')
def list_diary_info(diary_ids):

    if not diary_ids:
        return []

    cache_result_list = diary_base_info_cache.mget(list(map(lambda i: str(i), diary_ids)))
    result_dict, miss_ids = {}, []

    for index, result_id in enumerate(diary_ids):
        result_string = cache_result_list[index]

        if result_string is not None:
            if type(result_string) == bytes:
                try:
                    result_string = result_string.decode()
                except Exception:
                    pass
            result_dict[result_id] = json.loads(result_string)
        else:
            miss_ids.append(result_id)

    if not miss_ids:
        return list(result_dict.values())

    diary_objs = diary_list_manager._get_diaries_by_ids(miss_ids, is_online=True)
    diaries_info = diary_list_manager.list_diary_by_objs(diary_objs)

    diary_base_info_cache.mset({str(item["id"]): json.dumps(item) for item in diaries_info})

    diaries_info.extend(result_dict)

    diary_view_increase_num(diary_ids)

    return diaries_info


@bind('diary/get_comment_mixed')
def get_diary_and_comment_mix(service_id, comment_option=None, sort_type=DIARY_ORDER_TYPE.SUGGEST,
                              count=10, start_num=0, need_total_count=False):
    '''
    美购日记本和美购评价混合，http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=4440205
    :param service_id:
    :param comment_option:
    :param sort_type:
    :param count:
    :param start_num:
    :param need_total_count:
    :return:
    '''
    order_ids = []
    diary_dict, diary_list = {}, []
    all_topic_count = 0

    filters = {'service_id': service_id}

    if need_total_count:  # for 美购详情页外显日记，必需5星，术前后图存在
        all_diaries = Diary.objects.select_related('extra_info').filter(service_id=service_id, is_online=True).iterator()
        for diary in all_diaries:
            if hasattr(diary, 'extra_info'):
                all_topic_count += diary.extra_info.topic_count

        filters["has_before_cover"] = True
        filters["has_after_cover"] = True
        filters["vote_num_gt"] = 30

    if comment_option == SERVICE_COMMENT_OPTION.WITHIMAGE:
        # 有图，筛选的是有图的日记本, added in 7.6.35
        filters['has_cover'] = True

    if not comment_option or comment_option == SERVICE_COMMENT_OPTION.WITHIMAGE:
        # 1 全部和有图，通过日记本信息获取相应评论信息
        filter_result = rpc_client['api/diary/filter_diary'](
            offset=start_num, size=count, sort_type=sort_type, filters=filters
        ).unwrap()
        if filter_result['diary_ids']:
            diary_list = Diary.objects.filter(id__in=filter_result['diary_ids'])
            index_dict = {k: i for i, k in enumerate(filter_result['diary_ids'])}   # 将数据库查询出来的结果按照原有排序
            diary_list = sorted(diary_list, key=lambda x: index_dict[x.id])
            diary_dict = {item.order_id: item for item in diary_list if item.order_id}
            order_ids = list(diary_dict.keys())
            start_num = 0   # 通过订单去查询评论，start_num  从0，

    try:
        comment_list = rpc_client['api/service/comment_list'](
            comment_option=comment_option,
            service_id=service_id,
            start_num=start_num,
            count=count,
            order_ids=order_ids,
        ).unwrap()
    except:
        logging_exception()
        comment_list = []

    comment_order_id_dict = {comment['order_id']: comment for comment in comment_list}
    query_order_ids = list(comment_order_id_dict.keys())

    if comment_option and comment_list:
        # 除以上1的情况，其他筛选需要通过评论来获取相应日记本信息
        diary_obj = Diary.objects.filter(order_id__in=query_order_ids,
                                         is_online=True,
                                         topics__isnull=False,
                                         topics__flag='n',
                                         topics__is_online=True)
        diary_dict = {item.order_id: item for item in diary_obj if item.order_id}

    result_data = []
    if not comment_option or comment_option == SERVICE_COMMENT_OPTION.WITHIMAGE:
    # 全部展示的是日记，只是关联上评论的评分和星星;
    # 有图，筛选的是有图的日记本, added in 7.6.35
        for diary_obj in diary_list:
            data = {
                'id': '',
                'author': {},
                'comment_type': 1,  # 日记本类型
                'comment_data': {
                    'evaluate_point': diary_obj.rating,  # 评论分值
                    'content': diary_obj.latest_topic and escape(diary_obj.latest_topic.answer) or '',  # 评价内容
                    'post_date': diary_obj.last_modified.strftime("%Y-%m-%d"),
                    'imgs': diary_obj.cover,
                    'tags': diary_obj.tags_new_era,
                    'view_count': diary_obj.view_num,  # 浏览量
                    'reply_count': diary_obj.reply_num,  # 回复量
                    'vote_count': diary_obj.vote_num,  # 点赞量
                    'diary_id': diary_obj.id,
                    'topic_count': diary_obj.extra_info.topic_count if hasattr(diary_obj, 'extra_info') else 0  # 日记帖数量
                }
            }
            comment_item = comment_order_id_dict.get(diary_obj.order_id)
            if comment_item:
                data['id'] = comment_item['id']
                data['author'] = comment_item['user_info']
                data['comment_data']['evaluate_point'] = float('{0:.1f}'.format(comment_item['rating'])) if comment_item['rating'] else 0.0
                data['comment_data']['post_date'] = comment_item['create_time']

                if not comment_item['content_original_none']:
                    # 评价内容/默认好评  > 日记帖内容 > 此用户没有填写评价
                    data['comment_data']['content'] = comment_item['content']
                else:
                    if not data['comment_data']['content']:
                        data['comment_data']['content'] = comment_item['content']

                if not data['comment_data']['imgs'] and comment_item['images']:
                    data['comment_data']['imgs'] = [
                        {
                            'image_half': get_full_path(comment_item['images'][0], '-half'),
                            'desc': 'After',
                        }
                    ]
            else:
                diary_item_info = diary_obj.get_simple_diary()
                data['author'] = {
                    'user_id': diary_item_info['user_id'],
                    'last_name': diary_item_info['user_nickname'],
                    'user_portrait': diary_item_info['user_portrait']
                }

            if comment_option == SERVICE_COMMENT_OPTION.WITHIMAGE:
                if data['comment_data']['imgs']:
                    result_data.append(data)
            else:
                result_data.append(data)

    else:  # 有筛选条件时,筛选的是评论，存在日记本，然后关联上日记本
        for comment in comment_list:
            data = {
            'id': comment['id'],
            'author': comment['user_info'],
            'comment_type': 0,  # 评论类型
            'comment_data': {
                'evaluate_point': float('{0:.1f}'.format(comment['rating'])) if comment['rating'] else 0.0,  # 评论分值
                'content': comment['content'],  # 评价内容
                'post_date': comment['create_time'],
                'imgs': [],
                'tags': [],
                'view_count': 0,  # 浏览量
                'reply_count': 0,  # 回复量
                'vote_count': 0,  # 点赞量
                'diary_id': '',
                'topic_count': 0  # 日记帖数量
                }
            }
            diary_item = diary_dict.get(comment['order_id'])
            if diary_item:
                data['comment_type'] = 1
                data['comment_data']['imgs'] = diary_item.cover
                data['comment_data']['tags'] = diary_item.tags_new_era
                data['comment_data']['view_count'] = diary_item.view_num  # 浏览量
                data['comment_data']['reply_count'] = diary_item.reply_num  # 回复量
                data['comment_data']['vote_count'] = diary_item.vote_num  # 点赞量
                data['comment_data']['diary_id'] = diary_item.id
                data['comment_data']['topic_count'] = diary_item.extra_info.topic_count if hasattr(diary_item, 'extra_info') else 0

            if comment['content_original_none'] and diary_item:  # 无评价内容
                latest_topic = diary_item.latest_topic
                content = latest_topic and escape(latest_topic.answer) or ''
                data['comment_data']['content'] = content

            if not data['comment_data']['imgs'] and comment['images']:
                data['comment_data']['imgs'] = [
                {
                    'image_half': get_full_path(comment['images'][0], '-half'),
                    'desc': 'After',
                }
            ]

            result_data.append(data)

    return {'diary_and_comment': result_data, 'total': all_topic_count}


@bind('diary/diary_filter_info')
def talos_diary_get_filter_info(service_id=None, hospital_id=None, doctor_id=None, has_cover=False,
                                need_topic_count=False):
    """
    v 7.6.35 增加
    目的： 获取美购，医生，医院等 相关的日记本下所有帖子的总数；
    目前支持类型：美购，医院，医生；如有其他项目，请完善此接口
    :param service_id: 美购id
    :param hospital_id: 医院id
    :param doctor_id: 医生id
    :param need_topic_count: 获取所有日记本下的帖子总数
    :return: {} 字典类型 返回 日记本总数，日记本相关的帖子总数
    """
    filters = {}
    topic_count = None
    query = Q()
    result = {
        "diary_count": 0,
        "topic_count": 0,
    }

    if doctor_id:
        filters['doctor_id'] = doctor_id

    if hospital_id:
        filters['hospital_id'] = hospital_id
        topic_count = hospital_topics_num_cache.get(hospital_id)

    if service_id:
        filters['service_id'] = service_id
        query |= Q(service_id=service_id)

    if has_cover:
        filters['has_cover'] = has_cover

    diary_info = rpc_client['api/diary/filter_diary'](filters=filters, expose_total=True,
                                                  sort_type=DIARY_ORDER_TYPE.LAST_UPDATE_TIME).unwrap()

    if need_topic_count:
        query &= Q(is_online=True)
        diary_ids = list(Diary.objects.filter(query).values_list("id", flat=True))
        _counts = DiaryExtra.objects.filter(diary_id__in=diary_ids).aggregate(topic_count=Sum("topic_count"))
        topic_count = _counts.get("topic_count")

    result["diary_count"] = diary_info and diary_info.get("total", 0) or 0
    result["topic_count"] = topic_count and int(topic_count) or 0

    return result


@bind('diary/get_diary_count')
def get_diary_count(service_id=None, doctor_id=None, rating__gt=False,
                    exclude_rating=False, exclude_post_img=False, is_online=False):

    if not service_id and not doctor_id:
        return {'count': 103}

    query = Q()
    if service_id:
        query &= Q(service_id=service_id)
    if is_online:
        query &= Q(is_online=is_online)
    if doctor_id:
        query &= Q(doctor_id=doctor_id)
    if rating__gt:
        query &= Q(rating__gt=0)

    not_query = Q()
    if exclude_rating:
        not_query &= Q(rating=0)
    if exclude_post_img:
        not_query &= Q(post_operation_image='')

    try:
        diary_obj = Diary.objects.filter(query)
        if not_query:
            diary_obj = diary_obj.exclude(not_query)
        re_count = diary_obj.distinct().count()
    except:
        logging_exception()
        re_count = 0

    return {'count': re_count}


@bind_context('diary/diary_list_for_darens')
def diary_list_for_darens(ctx, daren_ids):
    # assert len(diary_ids) <= 100, 'too many diary_ids'
    result = dict.fromkeys(daren_ids, {})

    try:
        diary_info = Diary.objects.filter(user_id__in=daren_ids, is_online=True).values('user_id').annotate(Max('id'))
        diary_ids = [diary['id__max'] for diary in diary_info]
    except:
        logging_exception()
        diary_ids = []

    diaries = diary_list_manager.get_diary_list_for_pc_index_by_diary_ids(diary_ids)

    for diary in diaries:
        if diary['operation_time']:
            days = (datetime.datetime.now() - diary['operation_time']).days
        else:
            days = 0
        result[str(diary['user_id'])] = {
            'id': diary['id'],
            'days': days,
            'images': diary['images'],
            'tag': diary['tags'][0] if diary['tags'] else {},
            'service_name': diary['service_name']
        }

    return result


def _get_topic_count_by_query(query, *fields):
    """
    该函数是接口： diary/get_topic_count 内部的公共方法，其他接口使用请先检查调取逻辑是否一致
    通过query 获取数据，item 代表要查询的哪个字段
    :param query:
    :param item:
    :return:
    """
    query &= Q(is_online=True)
    diary_info = Diary.objects.filter(query).values_list(*fields)

    _data = dict()
    for info in diary_info:
        diary_id, item_id = info

        if item_id not in _data:
            _data[item_id] = []
        _data[item_id].append(diary_id)

    diary_ids = []
    for _, v in _data.items():
        diary_ids.extend(v)

    _counts = DiaryExtra.objects.filter(diary_id__in=diary_ids).values_list("diary_id", "topic_count")
    topic_count_dic = {info[0]: info[1] for info in _counts}

    result = {}
    for k, v in _data.items():
        result[str(k)] = sum(topic_count_dic.get(did, 0) for did in v)

    return result


@bind("diary/get_topic_count")
def get_topic_count(service_ids=None):
    """
    为满足需求:批量计算日记贴数而新增的接口,目前先支持美购，后续需要再增加。

    PS: 完善接口或调用，一定要注意，该接口的设计，仅支持单一条件的查询!!!
    :param service_ids: list
    :return:
    """
    result = {
        "services": {},
    }
    status = dict()  # 数据的状态
    _ids = set()
    data_slice = 5  # 数据切片，先写成5条
    fields = ["id", ]  # 数据获取字段

    if not service_ids:
        return result

    if service_ids:
        _ids = list(set(service_ids))
        status["service"] = True
        fields.append("service_id")

    for i in range(0, len(_ids), data_slice):

        if status.get("service", False):
            service_ids = _ids[i: i + data_slice]
            query = Q(service_id__in=service_ids)
            result["services"].update(_get_topic_count_by_query(query, *fields))

    return result


@bind("diary/get_user_already_related_order_ids")
def get_user_already_related_order_ids(need_filter_ids):
    """
    筛选出已关联日记本的订单id
    :return:
    """
    order_ids = []

    if need_filter_ids:
        filter_order_ids = list(Diary.objects.using(settings.SLAVE_DB_NAME).filter(
            order_id__in=need_filter_ids).values_list("order_id", flat=True))
        order_ids.extend(filter_order_ids)

    return {
        "user_order_ids": order_ids,
    }


@bind_context("diary/get_comment_mixed_v2")
def get_diary_list_for_service_detail(ctx, diary_ids=None, order_ids=None):
    """
    美购详情页日记本卡片新版逻辑展示,仅提供数据接口
    :param ctx:
    :param diary_ids:
    :param order_ids:
    :return:
    """
    query = Q(is_online=True)
    if diary_ids:
        assert len(diary_ids) <= 100, 'too many diary_ids'
        query &= Q(id__in=diary_ids)
    elif order_ids:
        assert len(order_ids) <= 100, 'too many order_ids'
        query &= Q(order_id__in=order_ids, topics__isnull=False, topics__flag='n', topics__is_online=True)
    else:
        return gen(CODES.PARAMS_INCOMPLETE)

    diary_objs = Diary.objects.filter(query).distinct()
    diary_info_list = diary_objs.values("id", "operation_time", "created_time")
    if diary_ids:
        diary_objs = sorted(diary_objs, key=lambda item_obj: diary_ids.index(item_obj.id))
    elif order_ids:
        diary_objs = sorted(diary_objs, key=lambda item_obj: order_ids.index(item_obj.order_id))

    user = get_user_from_context(ctx)
    result = diary_list_manager.get_new_service_case_by_diary_objs(diary_objs, diary_info_list, viewer_user_id=user.id)

    return result


@bind_context("diary/my_page/list")
def get_my_page_diary(ctx, user_id, offset=0, count=10):
    user = get_user_from_context(ctx)

    assert int(count) <= 100, 'too many diary_ids'
    end_num = offset + count
    diary_ids = list(Diary.objects.filter(user_id=user_id).order_by('-id')[offset:end_num].values_list('id', flat=True))

    diary_view_increase_num(diary_ids)

    diary_info = diary_list_manager.get_diary_list_by_diary_ids(diary_ids, viewer_user_id=user.id)
    return diary_info


@bind("diary/list_data_get_by_ids_v2")
def get_diary_list_data_by_ids_v2(diary_ids):
    """
    获取日记本信息
    :param diary_ids:
    :return:
    """
    assert len(diary_ids) <= 100, 'too many diary_ids'

    user = get_current_user()
    _curent_user_id = user and user.id or 0

    result = diary_list_manager.get_diary_list_by_ids_v2(diary_ids, viewer_user_id=_curent_user_id)

    diary_view_increase_num(result.get("valid_diary_ids", []))

    return {
        'diaries': result.get("diary_list", []),
    }


@bind('diary/topic_remark/by_service_ids')
def get_diaries_remark(service_ids):
    """ 获取美购关联日记本日记帖的精选评论 """
    assert len(service_ids) <= 10, 'too many service_ids'

    if not service_ids:
        return {}

    service_map, user_ids = defaultdict(), set()

    valid_levels = [DIARY_CONTENT_LEVEL.FINE, DIARY_CONTENT_LEVEL.EXCELLENT, DIARY_CONTENT_LEVEL.OUTSTANDING]

    topics = Problem.objects.using(settings.SLAVE_DB_NAME).select_related('diary').filter(
        diary__service_id__in=service_ids, diary__content_level__in=valid_levels, diary__is_online=True,
    ).values_list('diary_id', 'user_id', 'diary__service_id', 'remark').order_by('-diary__content_level', '-created_time')

    for diary_id, user_id, service_id, remark in topics.iterator():
        if not remark or service_map.get(str(service_id)): continue
        user_ids.add(user_id)
        service_map[str(service_id)] = {'remark': remark, 'user_id': user_id}

    users = UserService.get_users_by_user_ids(user_ids)
    for service_id, item in service_map.items():
        user = users.get(item['user_id'])
        if not user:
            del service_map[str(service_id)]

        item.update({
            'nickname': user.nickname,
            'portrait': user.portrait,
        })

    return dict(service_map)
