# coding=utf8

from __future__ import unicode_literals, absolute_import, print_function

import datetime
import json
from collections import defaultdict
from operator import itemgetter

from django.conf import settings
from django.db import transaction
from django.db.models import Q, Count
from django.db.models.query import QuerySet
from django.utils.html import escape
from gm_types.gaia import (
    AUTHOR_TYPE,
    DIARY_OPERATE,
    TAG_TYPE,
    PROBLEM_FLAG_CHOICES,
)
from gm_types.gaia import CONST_STRINGS
from ..models.diary import PreOperationImage
from ..models.topic import Problem, TopicImage, Video

# TODO: CR
from social.models import SocialInfo
from talos.cache.service import diary_index_data_cache
from talos.libs.datetime_utils import (
    get_timestamp_epoch,
    get_timestamp_or_none,
    get_humanize_datetime,
)
from talos.cache.viewrecord import ViewRecord
from talos.libs.image_utils import get_full_path
from ..models.diary import DiaryVote, Diary, DiaryFavor, DiaryTagV3, DiaryTag
from talos.rpc import get_objects_from_queryset_and_pk_list
from talos.services.doctor import DoctorService
from talos.services.hospital import HospitalService
from talos.services.user import UserService
from talos.services.goods import GoodsService
from talos.services import UserConvertService, TagV3Service, TagService
from utils.gevent_jobs import Tasks


def get_diary_voted_info_by_user_ids(diary, user_ids):
    user_ids = list(set(user_ids))
    if not user_ids:
        return {}

    dvs = DiaryVote.objects.filter(diary=diary, user_id__in=user_ids)

    result = {}
    for dv in dvs:
        result[dv.user_id] = True

    return result


def get_diary_voted_info_for_user(diaries, user_id):
    if not diaries:
        return {}

    dvs = DiaryVote.objects.filter(diary__in=diaries, user_id=user_id)
    if not dvs:
        return {}

    result = {}
    for dv in dvs:
        result[dv.diary_id] = True

    return result


def get_diary_favored_info_for_user(diaries, user_id):
    """
    获取日记本的收藏信息
    :param diaries:
    :param user_id:
    :return:
    """
    result = {}
    if not diaries:
        return result

    dfs = DiaryFavor.objects.filter(
        diary__in=diaries, user_id=user_id, is_deleted=False
    ).values_list("diary_id", flat=True)

    for diary_id in dfs:
        result[diary_id] = True

    return result


def get_diary_favor_nums_by_diaries(diaries):
    """
    获取日记本的收藏数
    :param diaries:
    :return:
    """
    result = {}
    if not diaries:
        return result

    diary_favor_nums = DiaryFavor.objects.filter(
        diary__in=diaries, is_deleted=False
    ).values("diary_id").annotate(favor_count=Count("diary_id")).values("diary_id", "favor_count")

    for item in diary_favor_nums:
        result.setdefault(item["diary_id"], item["favor_count"])

    return result


def get_diary_tags_info_by_ids(diary_tags):
    tag_infos = TagService.get_tags_dict_by_tag_ids(set(item.tag_id for item in diary_tags))
    tags_info_dict = defaultdict(list)
    for item in diary_tags:
        tag_obj = tag_infos.get(item.tag_id, None)
        if not tag_obj:
            continue
        tags_info_dict[item.diary_id].append(TagService.format_tag(tag_obj))
    return tags_info_dict


def get_diary_tags_v3_info_by_ids(diary_tags_v3):
    tags_v3 = TagV3Service.get_tags_by_tag_v3_ids(set(item.tag_v3_id for item in diary_tags_v3))
    tag_v3_dict = defaultdict(list)
    for item in diary_tags_v3:
        tag_v3_info = tags_v3.get(item.tag_v3_id, None)
        if not tag_v3_info:
            continue
        tag_v3_dict[item.diary_id].append(TagV3Service.format_tag_v3(tag_v3_info))
    return tag_v3_dict


def get_diary_tagv3_info_by_diary_ids(diary_ids):
    """
    获取日记本关联的tag_v3的信息
    :param diary_ids:
    :return:
    """
    result = {}

    if not diary_ids:
        return result

    diary_tag_v3_ids = DiaryTagV3.objects.filter(
        diary_id__in=diary_ids
    ).values_list("diary_id", "tag_v3_id")
    tag_v3_infos = TagV3Service.get_tags_by_tag_v3_ids(set(item[1] for item in diary_tag_v3_ids))

    for diary_id, tag_v3_id in diary_tag_v3_ids:
        if diary_id not in result:
            result[diary_id] = []

        tag_v3_info = tag_v3_infos.get(tag_v3_id, None)
        if tag_v3_info:
            result[diary_id].append(TagV3Service.format_tag_v3(tag_v3_info))

    return result


def get_diary_tag_info_by_diary_ids(diary_ids):
    """
    获取日记本关联的tag的信息
    :param diary_ids:
    :return:
    """
    result = {}

    if not diary_ids:
        return result

    diary_tag_ids = DiaryTag.objects.filter(
        diary_id__in=diary_ids
    ).values_list("diary_id", "tag_id")
    tag_infos = TagService.get_tags_dict_by_tag_ids(set(item[1] for item in diary_tag_ids))

    for diary_id, tag_id in diary_tag_ids:
        if diary_id not in result:
            result[diary_id] = []

        tag_obj = tag_infos.get(tag_id, None)
        if not tag_obj:
            continue

        result[diary_id].append(TagService.format_tag(tag_obj))

    return result


def tags_sort_for_special(tags):
    result = get_tags_from_tags_by_types(
        tags, [
            TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI
        ]
    )

    result += get_tags_from_tags_by_types(
        tags, [
            TAG_TYPE.CITY, TAG_TYPE.PROVINCE, TAG_TYPE.COUNTRY
        ]
    )
    result += get_tags_from_tags_by_types(tags, [TAG_TYPE.FREE])

    return result


def get_tags_from_tags_by_types(tags, types):
    result = []
    for tag in tags:
        if tag["name"] in settings.UNSHOW_TAGS:
            continue
        if tag["tag_type"] in types:
            result.append(tag)
    return result


def filter_diary_tags(tags):
    result = []

    for tag in tags:
        # 添加热门tag后 跟日记本相关的显示部分 热门tag作为3级tag来显示
        d = {'name': tag["name"], 'id': tag["id"], 'type': tag["tag_type"], 'tag_id': tag["id"]}
        result.append(d)

    return result[0:10]


class DiaryListManager(object):

    def __init__(self, redis_client):
        self._redis = redis_client

    def _get_cache_key(self, diary_id):
        return 'talos:diary-v2:lm:%s' % diary_id

    def _get_simple_cache_key(self, diary_id):
        return 'talos:diary-v5:lm:%s' % diary_id

    def get_city_by_tag_objs(self, tags):
        for tag in tags:
            if tag.tag_type in [TAG_TYPE.CITY, TAG_TYPE.PROVINCE, TAG_TYPE.PROVINCE]:
                return tag.name

    def get_city_by_tags(self, tags):
        for tag in tags:
            if tag["tag_type"] in [TAG_TYPE.CITY, TAG_TYPE.PROVINCE, TAG_TYPE.PROVINCE]:
                return tag["name"]

    def list_cached_diary_data_by_ids(self, ids):

        keys = [self._get_simple_cache_key(_id) for _id in ids]
        cache_result_list = self._redis.mget(keys)

        result, missing_ids = {}, []
        for index, _id in enumerate(ids):
            v = cache_result_list[index]
            if v is not None:
                if isinstance(v, bytes):
                    v = v.decode()
                result[_id] = json.loads(v)
            else:
                missing_ids.append(_id)

        return result, missing_ids

    def set_cache(self, diary_id, data, timeout=60*10):
        key = self._get_simple_cache_key(diary_id)
        self._redis.set(key, json.dumps(data), timeout)

    def _diary_base_info(self, diary):

        latest_topic = diary.latest_topic
        title = latest_topic and latest_topic.get_title() or diary.title
        content = latest_topic and escape(latest_topic.answer) or ''

        diary_data = {
            'id': diary.id,

            'diary_id': diary.id,
            'title': title,
            'content': content,
            'tags': [],
            'tags_v3': [],
            'images': diary.enduring_cover,
            'user_id': diary.user_id,
            'latest_topic_id': latest_topic and latest_topic.id or 0,
            'lasest_topic_created_time': (latest_topic and get_timestamp_or_none(latest_topic.created_time)) or None,
            'date': get_humanize_datetime(diary.last_modified),
            'last_modified': get_timestamp_or_none(diary.last_modified),
            'created_time': get_timestamp_or_none(diary.created_time),

            'city': '中国',
            'title_style_type': diary.title_style_type,
            'author_type': AUTHOR_TYPE.USER,

            'is_identification': diary.is_identification,  # 兼容

            'content_level': diary.content_level,
            'service_id': diary.service_id,
            'diary_title': diary.title or None,
            'import_type': diary.is_import,
            'sticky_post': diary.sticky_post,
            'doctor_id': diary.doctor_id,
            'hospital_id': diary.hospital_id,
            'last_topic_time': get_timestamp_or_none(diary.last_topic_add_time),

            'sell_point': diary.sell_point,
            'is_online': diary.is_online,
            'is_headline': diary.is_headline,
            "operation_time": get_timestamp_or_none(diary.operation_time) - 8 * 3600 if diary.operation_time else None,  # 治疗时间
            'stick_priority': diary.stick_priority,
            'is_sink': diary.is_sink,

            'reply_num': diary.reply_num,
            'topic_num': diary.topic_num,
        }

        return diary_data

    def _get_or_init_cached_data(self, diary):
        k = self._get_cache_key(diary.id)
        _cached = self._redis.get(k)
        if _cached:
            return json.loads(_cached)

        latest_topic = diary.latest_topic
        title = latest_topic and latest_topic.get_title() or diary.title
        content = latest_topic and escape(latest_topic.answer) or ''

        diary_data = {
            'id': diary.id,
            'diary_id': diary.id,
            'title': title,
            'content': content,
            'tags': diary.tags_new_era,
            'images': diary.cover,
            'diary_num': diary.topic_num,
            'user_id': diary.user_id,
            'latest_topic_id': latest_topic and latest_topic.id or 0,
            'lasest_topic_created_time': (latest_topic and get_timestamp_or_none(latest_topic.created_time)) or None,
            'date': get_humanize_datetime(diary.last_modified),
            'last_modified': get_timestamp_or_none(diary.last_modified),
            'created_time': get_timestamp_or_none(diary.created_time),

            'city': self.get_city_by_tag_objs(diary.tags) or u'中国',
            'title_style_type': diary.title_style_type,
            'author_type': AUTHOR_TYPE.USER,

            'view_num': diary.view_num,
            'reply_num': diary.reply_num,
            'vote_num': diary.vote_num,

            'is_identification': diary.is_identification,  # 兼容
        }
        diary_data.update(diary.video_cover())

        self._redis.setex(k, 3 * 60, json.dumps(diary_data))
        return diary_data

    @staticmethod
    def _get_users_info(user_ids):
        users = UserService.get_users_by_user_ids(user_ids)
        return users

    @staticmethod
    def get_users_base_info(user_ids):
        users = UserService.get_users_base_info_by_user_ids(user_ids)
        return users

    @staticmethod
    def _get_users_info_v2(user_ids, simple=True):
        result = {}
        users = UserConvertService.get_user_info_by_user_ids(user_ids, simple=simple)
        for user_id, user_info in users.items():
            if not simple:
                user_info.update({
                    "city": user_info.pop("city_name", ""),
                    'topic_num_posted': user_info.pop("topic_count", 0),
                    'vote_num_gained': user_info.pop("vote_count", 0),
                })
            result[user_id] = user_info

        return result

    @staticmethod
    def _get_follow_info(viewer_user_id, diary_author_ids):
        follow_rels = {}
        if viewer_user_id:
            social_info = SocialInfo(uid=viewer_user_id)
            follow_rels = social_info.is_following_users(uids=diary_author_ids)

        return follow_rels

    def _get_hospitals_info(self, hospital_ids):
        if not hospital_ids:
            return {}

        hospital_ids = list(set(hospital_ids))
        hospitals = HospitalService.get_hospital_from_hospital_ids(hospital_ids)
        hospitals = {h.id: h for h in hospitals}
        return hospitals

    def _get_doctors_info(self, doctor_ids):
        if not doctor_ids:
            return {}

        doctor_ids = list(set(doctor_ids))
        doctors = DoctorService.get_doctor_from_doctor_ids(doctor_ids)
        doctors = {doctor.id: doctor for doctor in doctors}
        return doctors

    def _get_doctors_info_by_user_ids(self, user_ids):
        if not user_ids:
            return {}

        doctors = DoctorService.list_doctors_by_user_ids(user_ids)
        return doctors

    def _get_diary_show_info_by_service_ids(self, service_ids):
        """
         v 7.6.95新增，通过美购id列表 返回基本的展示信息
        :param service_ids:
        :return:
        """
        if not service_ids:
            return {}
        service_ids = list(set(service_ids))
        return GoodsService.get_diary_show_info_by_service_ids(service_ids)

    def _get_service_item_info_by_order_ids(self, order_ids):
        """
        v 7.7.30 新增，通过订单id获取sku信息
        :param order_ids:
        :return:
        """
        if not order_ids:
            return {}
        order_ids = list(set(order_ids))
        return GoodsService.get_service_item_info_by_order_ids(order_ids)

    @staticmethod
    def _filter_pre_and_post_topic_list(topic_list, reference_time):
        """
        通过 参照时间，把帖子分成术前帖和术后帖
        :param topic_list:
        :param reference_time:
        :return:
        """
        pre_topic_list, post_topic_list = [], []

        for topic_info in topic_list:
            operation_date, created_time = topic_info.pop("operation_date"), topic_info.pop("created_time")
            topic_operation_time = operation_date or created_time
            topic_info["topic_operation_time"] = get_timestamp_or_none(topic_operation_time) - 8 * 3600
            topic_info["topic_id"] = topic_info.pop("id")

            _topic_image = topic_info.get("images", [])
            _topic_video = topic_info.get("video_url", '')

            if _topic_image and topic_operation_time <= reference_time:
                pre_topic_list.append(topic_info)
            elif topic_operation_time > reference_time and (_topic_image or _topic_video):
                post_topic_list.append(topic_info)
            else:
                continue

        return pre_topic_list, post_topic_list

    @staticmethod
    def get_new_images_and_content_by_diary_info(diary_info_list):
        """
        日记本卡片展示新的逻辑，目前应用于美购列表页卡片展示
        取第一篇有图术前帖及最新一篇有图 or 有视频的术后帖，before 展示术前帖图片 after展示术后帖图片，若术后帖有视频则展示视频
        :param diary_info_list:[{"id": 1, "operation_time": '2017-12-10(datetime)', "created_time": '2015-12-10(datetime)'}]
        :return:
        """
        diary_info_dic = {diary["id"]: diary for diary in diary_info_list}
        diary_ids = list(diary_info_dic.keys())
        # 拿到所有的帖子数据
        _query = Q(diary_id__in=diary_ids, is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL)
        topic_info_list = Problem.objects.filter(_query).values(
            "id", "diary_id", "answer", "operation_date", "created_time")

        # 查图，查视频
        _need_filter_images_or_video_topic_ids = [topic["id"] for topic in topic_info_list]
        # 图片
        topic_images_dic = defaultdict(list)
        topic_image_list = TopicImage.objects.filter(
            topic_id__in=_need_filter_images_or_video_topic_ids).values_list("topic_id", "image_url")
        for topic_id, img_url in topic_image_list:
            topic_images_dic[topic_id].append(img_url)

        # 视频
        topic_videos_dic = defaultdict(dict)
        topic_videos = Video.objects.filter(topic_id__in=_need_filter_images_or_video_topic_ids)
        for video_item in topic_videos:
            topic_id = video_item.topic_id
            _data = video_item.get_video_info()
            _data["topic_id"] = topic_id
            topic_videos_dic[topic_id] = _data

        # 通过日记本id分类
        diary_topic_info_dic = defaultdict(list)
        for topic_info in topic_info_list:
            _topic_id = topic_info.get("id", 0)
            _images_list = topic_images_dic.get(_topic_id, [])
            _video_dic = topic_videos_dic.get(_topic_id, {})

            if not _images_list and not _video_dic:  # 去除无图无视频的帖子
                continue

            # 将图片、视频数据归入即将筛选的帖子数据，可能存在 有视频无图的情况
            topic_info = dict(topic_info)
            topic_info["images"] = _images_list
            topic_info.update(_video_dic)
            diary_topic_info_dic[topic_info["diary_id"]].append(topic_info)

        # 查下日记本的术前图库
        diary_pre_images_dic = defaultdict(list)
        diary_pre_images_list = PreOperationImage.objects.filter(
            diary_id__in=diary_ids).values_list("diary_id", "image_url")
        for diary_id, diary_pre_image_url in diary_pre_images_list:
            diary_pre_images_dic[diary_id].append(diary_pre_image_url)

        # 数据重组,取出满足要求的术前术后帖
        for diary_id in diary_ids:
            diary_info = diary_info_dic.pop(diary_id, {})
            if not diary_info:
                continue

            topic_list = diary_topic_info_dic.get(diary_id, [])
            diary_operation_time = diary_info.get("operation_time") or diary_info.get("created_time")
            pre_topic_list, post_topic_list = DiaryListManager._filter_pre_and_post_topic_list(
                topic_list, diary_operation_time)
            # 术前帖，术后帖数据
            pre_topic_info = pre_topic_list and min(pre_topic_list, key=itemgetter("topic_operation_time", "topic_id")) or {}
            post_topic_info = post_topic_list and max(post_topic_list, key=itemgetter("topic_operation_time", "topic_id")) or {}

            diary_pre_images_list = diary_pre_images_dic.get(diary_id, [])
            if pre_topic_info:
                _ = pre_topic_info.pop("answer", "")

            if post_topic_info:
                post_topic_info["content"] = escape(post_topic_info.pop('answer', ""))

            diary_info_dic[diary_id] = {
                "pre": pre_topic_info,
                "post": post_topic_info,
                "diary_pre_images": diary_pre_images_list,
            }

        return diary_info_dic

    @staticmethod
    def _force_queryset_to_list(queryset):
        if isinstance(queryset, QuerySet):
            queryset = list(queryset)
        return queryset

    def get_diary_list_by_diary_ids_with_doctor_hospital_name(
            self, ids, viewer_user_id=None, is_online=None, need_service=True
    ):
        diary_objs = self._get_diaries_by_ids(ids, is_online=is_online)
        return self.get_diary_list_by_diary_objs_with_doctor_hospital_name(
            diary_objs, viewer_user_id, need_service=need_service
        )

    def get_diary_list_by_diary_objs_with_doctor_hospital_name(
            self, diary_objs, viewer_user_id=None, need_service=True):
        result = self.get_diary_list_by_diary_objs(
            diary_objs, viewer_user_id=viewer_user_id, need_service=need_service
        )
        if not result:
            return result

        doctor_ids = [d.doctor_id for d in diary_objs]
        doctors = self._get_doctors_info(doctor_ids=doctor_ids)

        hospital_ids = [d.hospital_id for d in diary_objs]
        hospitals = self._get_hospitals_info(hospital_ids=hospital_ids)

        for diary in result:
            doctor = doctors.get(diary['doctor_id'])
            doctor_name = ''
            if doctor:
                doctor_name = doctor.name

            hospital = hospitals.get(diary['hospital_id'])
            hospital_name = ''
            if hospital:
                hospital_name = hospital.name

            diary.update({
                'doctor_name': doctor_name,
                'hospital_name': hospital_name,
            })

        return result

    def get_service_case_by_diary_objs(self, diary_objs):
        if not diary_objs:
            return []
        user_ids = [d.user_id for d in diary_objs]
        users = self._get_users_info(user_ids=user_ids)

        result = []
        for diary in diary_objs:
            user = users.get(diary.user_id)
            if not user:
                continue
            diary_data = {
                "user_id": user.id,
                "user_portrait": user.portrait,
                "diary_id": diary.id,
                "vote_num": diary.vote_num,
                "user": user.nickname,
                "membership_level": user.membership_level
            }
            image = diary.post_operation_image
            if image:
                diary_data['image'] = get_full_path(image, '-half')
            else:
                continue
            result.append(diary_data)
        return result

    def get_diary_image_info_by_diary_objs(self, diary_objs):
        result = []

        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return []

        user_ids = [d.user_id for d in diary_objs]
        users = self._get_users_info(user_ids=user_ids)
        for diary in diary_objs:
            user = users.get(diary.user_id)
            if not user:
                continue

            post_operation_image = diary.post_operation_image
            result.append({
                'pic': get_full_path(post_operation_image, '-half'),
                'pic_w': get_full_path(post_operation_image, '-w'),
                'name': user.nickname
            })
        return result

    def get_diary_list_by_diary_objs(self, diary_objs, viewer_user_id=None, get_last_video=False, need_service=True,
                                     need_service_item_info=False):
        """更具日记本对象列表获取全部的日记本信息。

        :param diary_objs: 日记本对象列表
        :param viewer_user_id: 请求用户
        :param get_last_video: 是否获取日记本的最后一个视频信息
        :param need_service: 是否需要美购
        :param need_service_item_info: 是否展示日记本所属美购的sku
        :return:
        """
        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return []

        diary_ids, user_ids, service_ids, order_ids = [], [], [], []
        for d in diary_objs:
            user_ids.append(d.user_id)
            diary_ids.append(d.id)
            if d.service_id:
                service_ids.append(d.service_id)
            if d.order_id:
                order_ids.append(d.order_id)

        follow_rels = self._get_follow_info(viewer_user_id=viewer_user_id, diary_author_ids=user_ids)

        vote_info = get_diary_voted_info_for_user(diaries=diary_objs, user_id=viewer_user_id)

        users = self._get_users_info(user_ids=user_ids)
        services = self._get_diary_show_info_by_service_ids(service_ids) if need_service else {}
        service_items_dic = self._get_service_item_info_by_order_ids(order_ids) if need_service_item_info else {}
        tag_v3_infos = get_diary_tagv3_info_by_diary_ids(diary_ids)

        result = []
        for diary in diary_objs:
            user = users.get(diary.user_id)
            if not user:
                continue

            diary_data = self._get_or_init_cached_data(diary)

            latest_topic_video = {}
            latest_topic = diary.latest_topic
            service_info = services.get(diary.service_id, {})
            relation_service_sku = service_items_dic.get(diary.order_id, {})
            _ = relation_service_sku.pop("order_id", "")  # 将订单id 从数据中剔除

            if latest_topic:
                latest_topic_video = latest_topic.get_video_info()

            bj_lasest_topic_operation_date = None
            if latest_topic and get_timestamp_or_none(latest_topic.operation_date):
                bj_lasest_topic_operation_date = get_timestamp_or_none(latest_topic.operation_date)-8*3600

            diary_data.update({
                'diary_amount': diary_data['diary_num'],

                # for doctor backend, 20171009
                'content_level': diary.content_level,
                'service_id': diary.service_id,
                'diary_title': diary.title or None,
                'service_info': service_info,
                'import_type': diary.is_import,  # 区分是导入数据还是非导入数据
                'relation_service_sku': relation_service_sku,
                'tags_new_era': diary_data['tags'],
                "tags_v3": tag_v3_infos.get(diary.id, []),
                'sticky_post': diary.sticky_post,
                'doctor_id': diary.doctor_id,
                'hospital_id': diary.hospital_id,
                'last_topic_time': get_timestamp_or_none(diary.last_topic_add_time),

                'is_following': follow_rels.get(diary.user_id, False),
                'is_liked': vote_info.get(diary.id, False),
                'is_voted': vote_info.get(diary.id, False),
                'sell_point': diary.sell_point,
                'user': {
                    'user_id': user.id,
                    'user_name': user.nickname,
                    'city': user.city_name,
                    'portrait': user.portrait,
                    'membership_level': user.membership_level,
                    'topic_num_posted': user.topic_count,
                    'vote_num_gained': user.vote_count,
                },
                'user_level': {
                    'level_icon': user.level_icon,
                    'membership_icon': user.membership_icon,
                    'constellation_icon': user.constellation_icon
                },

                # FOR COMPATIBLE  start
                'topic_num_posted': user.topic_count,
                'vote_num_gained': user.vote_count,
                'user_portrait': user.portrait,
                'user_nickname': user.nickname,
                'membership_level': user.membership_level,
                'author': {
                    'vote_num_gained': user.vote_count,
                    'topic_num_posted': user.topic_count,
                },
                # FOR COMPATIBLE  end
                # ONLY FOR PCWEB
                'is_online': diary.is_online,
                'is_headline': diary.is_headline,
                'video_url': latest_topic_video['video_url'] if latest_topic_video else "",
                'video_pic': latest_topic_video['video_pic'] if latest_topic_video else "",
                'short_video_url': latest_topic_video.get("short_video_url", ""),
                "operation_time": get_timestamp_or_none(diary.operation_time) - 8 * 3600 if diary.operation_time else None,  # 治疗时间
                'lasest_topic_operation_date': bj_lasest_topic_operation_date,
                "service_city_name": service_info.get("city_name", ""),  # 美购对应的城市名字
                'stick_priority': diary.stick_priority,
                'is_sink': diary.is_sink,
                'vote_num': diary.vote_num,
            })

            # 需要获取最后一个视频、并且最后一个日记贴中没有视频
            if get_last_video and (not latest_topic_video or not latest_topic_video["video_url"]):
                topic = Problem.objects.filter(Q(diary=diary) & Q(is_online=True) &
                                               Q(video__isnull=False)).prefetch_related("video"). \
                                order_by('-created_time').first()
                if not topic: continue
                video_info = topic.get_video_info()
                diary_data['video_url'] = video_info['video_url']
                diary_data['video_pic'] = video_info['video_pic']
                diary_data['short_video_url'] = video_info.get('short_video_url', "")

            result.append(diary_data)

        return result

    def list_diary_vote_num_by_ids(self, diary_ids):

        topics = Problem.objects.using(settings.SLAVE_DB_NAME).filter(diary_id__in=diary_ids).only("id", "diary_id")
        topic_ids = [i.id for i in topics]
        topic_diary = {i.id: i.diary_id for i in topics}
        topic_vote_num_list = ViewRecord(CONST_STRINGS.TOPIC_VOTE_V1).view_hmget(topic_ids)

        topic_belong_diary = {}
        for topic in topics:
            topic_belong_diary[topic.id] = topic.diary_id

        diary_vote_num_dict = defaultdict(int)
        for index, topic_id in enumerate(topic_ids):

            vote_num = topic_vote_num_list[index]
            if vote_num is None:
                continue

            diary_id = topic_diary[topic_id]
            diary_vote_num_dict[diary_id] += int(vote_num)

        return diary_vote_num_dict

    def list_diary_video_cover_by_objs(self, diary_objs):

        if not settings.SHOW_VIDEO_COVER:
            return {}

        last_topics = {diary.latest_topic.id: diary.id  for diary in diary_objs if diary.latest_topic}
        topic_ids = [topic.id for topic, _ in last_topics.items() if topic]
        if not topic_ids:
            return {}

        diary_videos = {}
        for video in Video.objects.filter(topic_id__in=topic_ids):
            info = video.get_video_info()
            info["video_cover"] = info['video_pic']  # 为客户端添加：｀video_cover｀ 字段
            diary_videos[last_topics[video.topic_id]] = info

        return diary_videos

    def list_simple_diaries_info_by_diary_objs(self, diary_objs, viewer_user_id, index=False):

        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return []

        tasks = Tasks()
        user_ids = [d.user_id for d in diary_objs]
        diary_ids = [diary.id for diary in diary_objs]
        diary_v3_tags = list(DiaryTagV3.objects.filter(diary_id__in=diary_ids))
        diary_tags = list(DiaryTag.objects.filter(diary_id__in=diary_ids))
        tasks.add("tag_v3_infos", get_diary_tags_v3_info_by_ids, diary_v3_tags)
        tasks.add("tags_info", get_diary_tags_info_by_ids, diary_tags)
        tasks.add("user_dic", self.get_users_base_info, user_ids)

        vote_info = get_diary_voted_info_for_user(diaries=diary_objs, user_id=viewer_user_id)
        vote_num_dict = self.list_diary_vote_num_by_ids(diary_ids)
        diaries_dict, missing_ids = self.list_cached_diary_data_by_ids(diary_ids)
        missing_objs = [obj for obj in diary_objs if obj in missing_ids]
        missing_video_cover_dict = self.list_diary_video_cover_by_objs(missing_objs)
        # missing_cover_dict = self.list_diary_cover_by_objs(missing_objs)

        tasks.joinall()
        _data = tasks.jobs
        users = _data['user_dic'] or {}
        tags_info = _data['tags_info'] or {}
        tag_v3_infos = _data['tag_v3_infos'] or {}

        result = []

        for diary in diary_objs:
            user = users.get(diary.user_id)
            if not user:
                continue

            tags = tags_sort_for_special(tags_info.get(diary.id, []))
            tags_new_era = filter_diary_tags(tags)

            if diary.id in diaries_dict:
                diary_data = diaries_dict[diary.id]
            else:
                diary_data = self._diary_base_info(diary)
                diary_data.update(missing_video_cover_dict.get(diary.id, {}))
                self.set_cache(diary.id, diary_data)

            diary_data.update({

                'is_voted': vote_info.get(diary.id, False),
                'user': {
                    'doctor': {
                        "user_id": user.id,
                        "doctor_id": user.doctor_id,
                        "nickname": user.doctor_name,
                        "portrait": user.doctor_portrait,
                    },
                    'user_id': user.id,
                    'user_name': user.nickname,
                    'city': '',
                    'portrait': user.portrait,
                    'membership_level': user.membership_level,
                },
                'user_level': {
                    'level_icon': user.level_icon,
                    'membership_icon': user.membership_icon,
                    'constellation_icon': user.constellation_icon
                },

                "tags_v3": tag_v3_infos.get(diary.id, []),
                'tags': tags_new_era,
                'city': self.get_city_by_tags(tags),
                'vote_num': vote_num_dict.get(diary.id, 0) + diary.like_num,
            })

            result.append(diary_data)

        return result

    def get_simple_diary_list_by_diary_objs(self, diary_objs):
        """
        精简接口，通过日记本对象，获取简单信息
        :param diary_objs:
        :return:
        """
        result = []
        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return result

        tag_v3_infos = get_diary_tagv3_info_by_diary_ids(set(diary.id for diary in diary_objs))
        for diary in diary_objs:
            _data = {
                'id': diary.id,
                'diary_id': diary.id,
                'title': diary.title or "",
                'view_num': diary.view_num,
                "topics_num": diary.topic_num,
                'order_id': diary.order_id,
                "hospital_id": diary.hospital_id,
                'hospital_name': diary.raw_hospital,
                'doctor_id': diary.doctor_id,
                'doctor_name': diary.raw_doctor,
                'operation_time': get_timestamp_or_none(diary.operation_time),
                'tags': diary.tags_new_era,
                "tags_v3": tag_v3_infos.get(diary.id, []),
                'cover': diary.cover,
                "is_online": diary.is_online,
            }
            result.append(_data)

        return result

    def get_diary_image_list_by_diary(self, ids):
        if not ids:
            return []
        diaries = self._get_diaries_by_ids(ids)
        return self.get_diary_image_info_by_diary_objs(diaries)

    def get_diary_list_by_diary_ids(self, ids, viewer_user_id=None, get_last_video=False, need_service_item_info=False):
        """
        :param ids:
        :param viewer_user_id:
        :param get_last_video:
        :param need_service_item_info:
        :return:
        """
        if not ids:
            return []

        diaries = self._get_diaries_by_ids(ids, is_online=True)
        return self.get_diary_list_by_diary_objs(
            diaries,
            viewer_user_id=viewer_user_id,
            get_last_video=get_last_video,
            need_service_item_info=need_service_item_info
        )

    def list_diary_by_ids(self, ids, viewer_user_id=None, get_last_video=False, index=False):
        """获取简单的数据"""

        diary_ids = map(int, list(filter(lambda x: x is not None, ids)))
        if not diary_ids:
            return []

        diaries = get_objects_from_queryset_and_pk_list(
            Diary.objects.filter(is_online=True),
            list(diary_ids),
        )
        return self.list_simple_diaries_info_by_diary_objs(
            diaries,
            viewer_user_id=viewer_user_id,
            index=index
        )

    def list_diary_by_objs(self, diary_objs):
        """获取简单的日记本数据"""

        if not diary_objs:
            return []

        tag_v3_infos = get_diary_tagv3_info_by_diary_ids(set(diary.id for diary in diary_objs))

        result = []
        for diary in diary_objs:

            latest_topic = diary.latest_topic
            title = latest_topic and latest_topic.get_title() or diary.title
            content = latest_topic and escape(latest_topic.answer) or ''

            diary_data = {
                'id': diary.id,
                'diary_id': diary.id,
                'title': title,
                'content': content,
                'images': diary.cover,
                'tags': diary.tags_new_era,
                "tags_v3": tag_v3_infos.get(diary.id, []),
                'user_id': diary.user_id,
                'latest_topic_id': latest_topic and latest_topic.id or 0,
                'lasest_topic_created_time': (latest_topic and get_timestamp_or_none(latest_topic.created_time)) or None,
                'date': get_humanize_datetime(diary.last_modified),
                'last_modified': get_timestamp_or_none(diary.last_modified),
                'created_time': get_timestamp_or_none(diary.created_time),

                'city': self.get_city_by_tag_objs(diary.tags) or u'中国',
                'title_style_type': diary.title_style_type,
                'author_type': AUTHOR_TYPE.USER,

                'is_identification': diary.is_identification,  # 兼容
                'content_level': diary.content_level,
                'service_id': diary.service_id,
                'diary_title': diary.title or None,
                'import_type': diary.is_import,
                'sticky_post': diary.sticky_post,
                'doctor_id': diary.doctor_id,
                'hospital_id': diary.hospital_id,
                'last_topic_time': get_timestamp_or_none(diary.last_topic_add_time),
                'sell_point': diary.sell_point,
                'is_online': diary.is_online,
                'is_headline': diary.is_headline,
                "operation_time": get_timestamp_or_none(diary.operation_time) - 8 * 3600 if diary.operation_time else None,  # 治疗时间
                'stick_priority': diary.stick_priority,
                'is_sink': diary.is_sink,
                'images': diary.cover,
                'reply_num': diary.reply_num,
            }
            diary_data.update(diary.video_cover())

            result.append(diary_data)

        return result

    def get_diary_list_by_diary_ids_for_journey(self, ids):
        if not ids:
            return []

        diaries = self._get_diaries_by_ids(ids)
        return self.get_diary_list_by_diary_objs_for_journey(diaries)

    def get_diary_list_by_diary_objs_for_journey(self, diary_objs):
        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return []

        user_ids = [d.user_id for d in diary_objs]

        users = self._get_users_info(user_ids=user_ids)

        result = []

        for diary in diary_objs:
            user = users.get(diary.user_id)
            if not user:
                continue
            cover = diary.cover
            if len(diary.cover) > 1:
                img_url = cover[1]["image"]
            elif len(diary.cover) == 1:
                img_url = cover[0]["image"]
            else:
                img_url = ''

            diary_data = {
                "city": user.city_name,
                "pic": get_full_path(img_url, '-half'),
                "id": diary.id,
                "num": diary.vote_num,
                "user_portrait": user.portrait,
                "nickname": user.nickname,
                "service_id": diary.service_id
            }
            result.append(diary_data)
        return result

    def get_diary_list_for_service_case(self, ids):
        if not ids:
            return []
        diaries = self._get_diaries_by_ids(ids)
        return self.get_service_case_by_diary_objs(diaries)

    def list_diaries_by_ids(self, diary_ids):

        return {
            item.id: item
            for item in self._get_diaries_by_ids(diary_ids, is_online=True)
        }

    @staticmethod
    def _get_diaries_by_ids(diary_ids, is_online=None):
        filtered_ids = filter(lambda x: x is not None, diary_ids)
        diary_ids = map(int, list(filtered_ids))
        if is_online is None:
            q = Q()
        else:
            q = Q(is_online=is_online)
        diaries = get_objects_from_queryset_and_pk_list(
            Diary.objects.filter(q).prefetch_related("topics"),
            list(diary_ids),
        )
        return diaries

    def get_diary_list_for_esquery_by_diary_ids(self, ids, viewer_user_id=None):
        if not ids:
            return []

        diaries = self._get_diaries_by_ids(ids)
        diary_dict = {d.id: d for d in diaries}
        result = self.get_diary_list_by_diary_objs_with_doctor_hospital_name(diaries, viewer_user_id)
        for r in result:
            # 兼容字段
            r['tags_info'] = []
            r['is_identification'] = True
            r['membership_level'] = r['user']['membership_level']
            r['user_id'] = r['user']['user_id']
            r['city'] = r['user']['city']

            diary = diary_dict[r['id']]
            r['pre_operation_image'] = diary.pre_operation_image
            r['post_operation_image'] = diary.post_operation_image
        return result

    def get_diary_list_for_index_by_diary_ids(self, ids, viewer_user_id=None):
        if not ids:
            return []

        result = []
        diaries = self._get_diaries_by_ids(ids, is_online=True)

        user_ids = [d.user_id for d in diaries]

        # follow_rels = self._get_follow_info(viewer_user_id=viewer_user_id, diary_author_ids=user_ids)

        vote_info = get_diary_voted_info_for_user(diaries=diaries, user_id=viewer_user_id)

        users = self._get_users_info(user_ids=user_ids)

        for diary in diaries:
            user = users.get(diary.user_id)
            if not user:
                continue

            diary_data = self._get_or_init_cached_data(diary)
            diary_data.update({
                'is_voted': vote_info.get(diary.id, False),
                # 'is_following': follow_rels.get(diary.user_id, False),
                'is_following': True,

                'user_portrait': user.portrait,
                'user_nickname': user.nickname,

                'membership_level': user.membership_level,
                'author': {
                    'vote_num_gained': user.vote_count,
                    'topic_num_posted': user.topic_count,
                },
                "user_level": {
                    'membership_icon': user.membership_icon,
                    'level_icon': user.level_icon,
                    'constellation_icon': user.constellation_icon,
                },
            })
            result.append(diary_data)

        return result

    def get_diary_list_for_pc_index_by_diary_ids(self, ids):
        if not ids:
            return []

        result = []
        diaries = self._get_diaries_by_ids(ids, is_online=True)

        for diary in diaries:
            diary_data = self._get_or_init_cached_data(diary)
            diary_data.update({
                'operation_time': diary.operation_time,
                'service_name': diary.related_service_name,
            })
            result.append(diary_data)

        return result

    @staticmethod
    def get_new_diary_card_display_content_by_diary_id(diary_id):
        content_list = list(Problem.objects.filter(
            diary_id=diary_id,
            is_online=True,
            flag=PROBLEM_FLAG_CHOICES.NORMAL,
            answer__isnull=False
        ).order_by("-operation_date", "-id").exclude(answer='').values_list("answer", flat=True))
        # 过滤掉内容为换行、空格的情况
        content_list = list(filter(lambda item: item.strip(), content_list))
        return content_list and content_list[0] or ''

    def get_new_service_case_by_diary_objs(self, diary_objs, diary_info_list, viewer_user_id=None):
        """
        获取新版的美购案例卡片展示逻辑
        :param diary_objs:
        :param diary_info_list:
        :param viewer_user_id:
        :return:
        """
        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return []

        result, user_ids, order_ids = [], [], []
        for d in diary_objs:
            user_ids.append(d.user_id)
            if d.order_id:
                order_ids.append(d.order_id)

        vote_info = get_diary_voted_info_for_user(diaries=diary_objs, user_id=viewer_user_id)
        users = self._get_users_info(user_ids=user_ids)
        service_items_dic = self._get_service_item_info_by_order_ids(order_ids)
        new_images_display_logic_dic = self.get_new_images_and_content_by_diary_info(diary_info_list)

        for diary in diary_objs:
            new_image_display_logic_dic = new_images_display_logic_dic.get(diary.id, {})

            user = users.get(diary.user_id)
            if not user:
                continue

            diary_data = self._get_or_init_cached_data(diary)

            relation_service_sku = service_items_dic.get(diary.order_id, {})
            _ = relation_service_sku.pop("order_id", "")  # 将订单id 从数据中剔除

            lastest_post_topic = new_image_display_logic_dic.get("post", {})

            diary_data.update({
                'content_level': diary.content_level,
                'service_id': diary.service_id,
                'diary_title': diary.title or None,
                'relation_service_sku': relation_service_sku,

                'is_liked': vote_info.get(diary.id, False),
                'is_voted': vote_info.get(diary.id, False),

                'user': {
                    'user_id': user.id,
                    'user_name': user.nickname,
                    'city': user.city_name,
                    'portrait': user.portrait,
                    'membership_level': user.membership_level,
                    'topic_num_posted': user.topic_count,
                    'vote_num_gained': user.vote_count,
                },
                'user_level': {
                    'level_icon': user.level_icon,
                    'membership_icon': user.membership_icon,
                    'constellation_icon': user.constellation_icon
                },

                'membership_level': user.membership_level,
                'is_online': diary.is_online,
                "operation_time": get_timestamp_or_none(diary.operation_time or diary.created_time) - 8 * 3600,  # 治疗时间
                'lasest_topic_operation_date': lastest_post_topic.get("topic_operation_time", None) or None,
                'video_pic': lastest_post_topic.get("video_pic", ""),
                'video_url': lastest_post_topic.get("video_url", ""),
                'short_video_url': lastest_post_topic.get("short_video_url", ""),
            })
            diary_data.update(new_image_display_logic_dic)

            if not diary_data.get("content", ""):  # 如果日记本最新一篇日记帖没内容，则找最新有内容的一篇的日记帖
                diary_data["content"] = escape(self.get_new_diary_card_display_content_by_diary_id(diary.id) or '')

            result.append(diary_data)

        return result

    def get_diary_list_by_diary_objs_v2(self, diary_objs, viewer_user_id=None):
        """
        日记本列表整合，后期需要啥再往上加
        :param diary_objs:
        :param viewer_user_id:
        :return:
        """
        result = {
            "diary_list": [],
            "valid_diary_ids": [],
        }
        diary_objs = self._force_queryset_to_list(diary_objs)
        if not diary_objs:
            return result

        diary_ids, user_ids, service_ids = [], [], []
        for d in diary_objs:
            user_ids.append(d.user_id)
            service_ids.append(d.service_id)
            diary_ids.append(d.id)

        # 点赞
        voted_dic = get_diary_voted_info_for_user(diaries=diary_objs, user_id=viewer_user_id)
        # 收藏
        favor_nums_dic = get_diary_favor_nums_by_diaries(diaries=diary_objs)
        favored_dic = get_diary_favored_info_for_user(diaries=diary_objs, user_id=viewer_user_id)
        # 关注状态
        follow_rels = self._get_follow_info(viewer_user_id=viewer_user_id, diary_author_ids=user_ids)
        # 用户信息
        users_info = self._get_users_info_v2(user_ids=user_ids, simple=False)
        # 美购信息
        service_dic = GoodsService.get_diary_show_info_by_service_ids(list(filter(None, set(service_ids))))

        # 标签3.0
        tag_v3_infos = get_diary_tagv3_info_by_diary_ids(diary_ids=diary_ids)

        for diary_obj in diary_objs:
            _user_id = diary_obj.user_id
            _id = diary_obj.id
            user_info = users_info.get(_user_id, {})
            service_info = service_dic.get(diary_obj.service_id, {})
            if not user_info:
                continue

            latest_topic = diary_obj.latest_topic
            video_info = latest_topic and latest_topic.get_video_info() or {}
            _data = {
                "diary_id": diary_obj.id,
                'diary_title': diary_obj.title or "",
                "user": user_info,
                'tags': diary_obj.tags_new_era,
                'tags_v3': tag_v3_infos.get(diary_obj.id, []),
                'cover': diary_obj.cover,
                'city': self.get_city_by_tag_objs(diary_obj.tags) or u'中国',
                'service_city_name': service_info.get("city_name", "") or users_info.get("city", "") or "",
                'content_level': diary_obj.content_level,
                'create_timestamp': get_timestamp_epoch(diary_obj.created_time),
                "operation_timestamp": get_timestamp_epoch(diary_obj.operation_time),  # 治疗时间
                'topic_num': diary_obj.topic_num,
                "view_num": diary_obj.view_num,
                'vote_num': diary_obj.vote_num,
                'favor_num': favor_nums_dic.get(_id, 0),
                'is_following': follow_rels.get(_user_id, False),
                'is_voted': voted_dic.get(_id, False),
                'is_favored': favored_dic.get(_id, False),

                # 以下是最新一个日记帖的数据
                'content': latest_topic and escape(latest_topic.answer) or '',
                'video_url': video_info.get("video_url", ""),
                'video_pic': video_info.get("video_pic", ""),
                "short_video_url": video_info.get("short_video_url", ""),
                'lastest_topic_id': latest_topic and latest_topic.id or 0,
                'lastest_topic_reply_num': latest_topic and latest_topic.reply_num or 0,
                'lastest_topic_create_timestamp': latest_topic and get_timestamp_epoch(latest_topic.created_time) or 0,
                # 发帖选择的治疗时间
                'lastest_topic_operation_timestamp': latest_topic and get_timestamp_epoch(latest_topic.operation_date) or 0,
            }

            result["diary_list"].append(_data)
            result["valid_diary_ids"].append(_id)

        return result

    def get_diary_list_by_ids_v2(self, diary_ids, viewer_user_id=None):
        """
        获取日记本信息列表
        :param diary_ids:
        :param viewer_user_id:
        :return:
        """
        diaries = self._get_diaries_by_ids(diary_ids, is_online=True)
        return self.get_diary_list_by_diary_objs_v2(
            diary_objs=diaries,
            viewer_user_id=viewer_user_id
        )


def clean_video_url(video_url):
    return video_url.replace(settings.VIDEO_HOST, '')


def set_diary_cover(cover, diary):
    if cover:
        with transaction.atomic():
            cover = get_full_path(cover)
            pre_images = diary.pre_operation_images.all()
            for image in pre_images:
                if image.image_url == cover or image.cover_image_url == cover:
                    image.is_cover = True
                else:
                    image.is_cover = False
                image.save()


def set_diary_postoperation_cover(cover, diary):
    if cover:
        topic_ids = Problem.objects.filter(diary_id=diary.id, is_online=True).values_list('id', flat=True)
        topic_ids = list(topic_ids)
        images = TopicImage.objects.filter(topic_id__in=topic_ids)

        with transaction.atomic():
            images.update(is_cover=False)
            image = images.filter(Q(image_url=cover) | Q(cover_image_url=cover))
            image.update(is_cover=True)


def set_pre_operation_images(pre_operation_images, cover, diary):
    if pre_operation_images:
        taken_time = datetime.datetime.now()
        has_cover = False
        for image in pre_operation_images:
            if not image.get('image', ''):
                break

            if has_cover:
                is_cover = False
            else:
                is_cover = False if image['image'] != cover else True

            if is_cover:
                has_cover = True

            diary.pre_operation_images.create(
                image_url=image['image'], taken_time=taken_time,
                is_cover=is_cover, cover_image_url=image.get('modified_image_url'))
            # 记录操作 UPDATE_INFO
            diary.set_diary_operate(DIARY_OPERATE.UPDATE_INFO)


def set_pre_operation_images_v1(pre_operation_images, cover, diary):
    if pre_operation_images:
        diary.pre_operation_images.clear()
        set_pre_operation_images(pre_operation_images, cover, diary)


diary_list_manager = DiaryListManager(diary_index_data_cache)
