# coding=utf8

from __future__ import unicode_literals, absolute_import, print_function

import datetime
import json
from django.conf import settings
from django.db import models
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.html import escape
from gm_types.gaia import AUTHOR_TYPE
from gm_types.gaia import CASH_BACK_STATUS
from gm_types.gaia import DIARY_AUDIT_STATUS
from gm_types.gaia import DIARY_CONTENT_LEVEL
from gm_types.gaia import DIARY_OPERATE
from gm_types.gaia import DOCTOR_TYPE
from gm_types.gaia import IMAGE_TYPE
from gm_types.gaia import PROBLEM_FLAG_CHOICES
from gm_types.gaia import PROBLEM_REVIEW_STATUS_CHOICES
from gm_types.gaia import TAG_TYPE
from gm_types.gaia import VOTEOBJECT
from gm_types.gaia import GROUPBUY_STATUS, CONST_STRINGS
from gm_upload import IMG_TYPE, ImgUrlField, batch_move_watermark_pic_to_no_watermark

from social.utils import get_social_info
from talos.cache.base import page_cache
from talos.cache.base import diary_pv_cache
from talos.cache.viewrecord import ViewRecord
from talos.libs.datetime_utils import get_timestamp
from talos.libs.datetime_utils import get_timestamp_or_none
from talos.libs.image_utils import get_full_path
from talos.libs.image_utils import get_short_path, get_temp_image_path
from talos.rpc import logging_exception
from talos.services.doctor import DoctorService
from talos.services.goods import GoodsService
from talos.services.hospital import HospitalService
from talos.services.order import OrderService
from talos.services.tag import TagService
from talos.services.user import UserService
from talos.services.tag_v3 import TagV3Service
from utils.common import convert_image
from utils.cache import common_cache_wrapper

# 置顶优先级, 医生后台指定日记在其美购详情页跳转的日记列表展示
MAX_STICK_PRIORITY = 3
DEFAULT_STICK_PRIORITY = 255


class Diary(models.Model):
    """
        日记本
    """

    class Meta:
        verbose_name = u'日记本'
        verbose_name_plural = u'日记本'
        app_label = 'talos'
        db_table = 'api_diary'

    title = models.CharField(verbose_name=u'标题', max_length=128, default='')
    user_id = models.IntegerField()

    order_id = models.CharField(null=True, blank=True, verbose_name=u'关联的订单外键id', max_length=12)
    service_id = models.IntegerField(null=True, blank=True, verbose_name=u'关联的美购外键id')
    doctor_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'关联的医生外键id')
    hospital_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'关联的医院外键id')
    raw_doctor = models.CharField(verbose_name=u'医生(名称)', blank=True, default='', max_length=200)
    raw_hospital = models.CharField(verbose_name=u'医院(名称)', blank=True, default='', max_length=200)
    rating = models.FloatField(verbose_name=u'评价', blank=True, default=0)
    operation_effect_rating = models.FloatField(verbose_name=u'术后效果评分',
                                                blank=True, default=0)
    doctor_attitude_rating = models.FloatField(verbose_name=u'医生态度评分',
                                               blank=True, default=0)
    hospital_env_rating = models.FloatField(verbose_name=u'医院环境评分',
                                            blank=True, default=0)
    comment = models.TextField(verbose_name=u'评价文字', blank=True, default='')
    price = models.IntegerField(verbose_name=u'价格', blank=True, default=0)
    created_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now)
    last_modified = models.DateTimeField(verbose_name=u'最后修改时间', default=timezone.now)
    last_topic_add_time = models.DateTimeField(verbose_name=u'最后发帖时间', blank=True, null=True, default=None)
    operation_time = models.DateTimeField(verbose_name=u'手术时间', blank=True, null=True, default=None)
    is_fake_operation_time = models.BooleanField(verbose_name=u'手术时间为假', default=False,
                                                 help_text=u'当用户选择忘记了手术日期的时候,此字段为真')
    pre_operation_image = ImgUrlField(img_type=IMG_TYPE.DIARY, verbose_name=u'术前图', max_length=128, default='',
                                      blank=True, null=True)
    post_operation_image = ImgUrlField(img_type=IMG_TYPE.DIARY, verbose_name=u'术后图', max_length=128, default='',
                                       blank=True, null=True)
    is_headline = models.BooleanField(verbose_name=u"首页推荐", default=False, db_index=True)
    headline_time = models.DateTimeField(
        verbose_name=u'设为首页推荐的时间', null=True, default=None,
        help_text='如果取消首页推荐, 该字段设为None',
    )
    sell_point = models.CharField(verbose_name=u'卖点', max_length=32, default='')
    is_online = models.BooleanField(verbose_name=u'是否上线', default=True, db_index=True)
    is_real_case = models.BooleanField(verbose_name=u'真人案例', default=False)
    is_sink = models.BooleanField(verbose_name=u'是否下沉', default=False, db_index=True)
    is_spam = models.BooleanField(verbose_name=u'是否为疑似广告', default=False, db_index=True)
    note = models.TextField(verbose_name=u'注释', max_length=200, default='', blank=True)
    reply_num = models.IntegerField(verbose_name=u'回复数量', default=0)
    share_num = models.IntegerField(verbose_name=u'分享数量', default=0)
    is_identification = models.BooleanField(verbose_name=u"是否认证", default=False, db_index=True)
    is_essence = models.BooleanField(default=False, help_text=u"是否精华帖", verbose_name=u"精华帖")
    # TODO Deprecated since 1.6.0
    pre_operation_image_for_doctor = ImgUrlField(
        img_type=IMG_TYPE.DIARY,
        verbose_name=u'医生术前图',
        max_length=128,
        default=None,
        blank=True,
        null=True
    )
    # TODO Deprecated since 1.6.0
    post_operation_image_for_doctor = ImgUrlField(
        img_type=IMG_TYPE.DIARY,
        verbose_name=u'医生术后图',
        max_length=128,
        default=None,
        blank=True,
        null=True
    )
    # 医生版1.9.1添加,每个医生的帖子列表中只能有一个置顶的 2016-02-23
    sticky_post = models.BooleanField(default=False, help_text=u"是否置顶", verbose_name=u"置顶帖")

    # added at 5.8
    like_num = models.IntegerField(default=0, verbose_name=u"赞的数目")

    # added at 5.9.3
    # http://wiki.gengmei.cc/pages/viewpage.action?pageId=1842494
    rate_count = models.IntegerField(default=0, verbose_name=u'评价次数', blank=True, null=False)

    is_choice = models.BooleanField(verbose_name=u'是否是精选日记本', default=False)
    is_choice_content = models.TextField(verbose_name=u'精选日记本描述', default='', blank=True)
    audit_status = models.CharField(verbose_name=u'审核状态', max_length=1, choices=DIARY_AUDIT_STATUS,
                                    default=DIARY_AUDIT_STATUS.UNAUDITED)
    audit_time = models.DateTimeField(
        verbose_name=u'最终审核时间', null=True, default=None, help_text='',
    )
    content_level = models.CharField(verbose_name=u'内容等级', max_length=3, choices=DIARY_CONTENT_LEVEL,
                                     default=DIARY_CONTENT_LEVEL.UNAUDITED)
    stick_priority = models.PositiveIntegerField(verbose_name=u'置顶优先级', default=DEFAULT_STICK_PRIORITY)
    is_import = models.BooleanField(verbose_name=u'是否是导入数据', default=False, blank=True)

    low_quality = models.IntegerField(default=0, verbose_name=u'低质量反馈数量', blank=True, null=False)
    low_quality_deal = models.BooleanField(default=False, verbose_name=u'低质量反馈是否处理', blank=True)

    @classmethod
    @common_cache_wrapper('diary:{diary_id}')
    def get_by_id(cls, diary_id):
        diary = Diary.objects.get(pk=diary_id)
        return diary

    @classmethod
    def get_topic_ids(cls, diary_id):
        from talos.models.topic import Problem
        if not diary_id:
            return []
        topic_ids = Problem.objects.filter(
            diary_id=diary_id, flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True,
        ).order_by('-operation_date', '-id').values_list('id', flat=True)
        return topic_ids

    @classmethod
    def get_topic_query(cls, diary_id):
        from talos.models.topic import Problem
        query = Problem.objects.filter(
            diary_id=diary_id, flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True,
        )
        return query

    def get_index_rank(self):
        """获取首页排序评分(Tuple3)
        """
        # 热度分
        try:
            heat_score = self.diary_rank.heat_score
            # calculate the loss caused by time
            from talos.libs.diaryrank_utils import DiaryRankTool
            loss = DiaryRankTool.get_loss_score_by_time(self.id, heat_score)
            heat_score_now = heat_score - loss
            heat_score = max(0, min(heat_score_now, settings.DIARY_HEAT_RANK['max_score']))

        except Exception:
            heat_score = 0.0

        # 质量分
        if self.diary_check.exists():
            audit = self.diary_check.all().order_by('id').last()
            audit_score_map = {
                DIARY_CONTENT_LEVEL.UNAUDITED: settings.DIARY_CONTENT_LEVEL_SCORE['UNAUDITED'],
                DIARY_CONTENT_LEVEL.ILLEGAL: settings.DIARY_CONTENT_LEVEL_SCORE['ILLEGAL'],
                DIARY_CONTENT_LEVEL.BAD: settings.DIARY_CONTENT_LEVEL_SCORE['BAD'],
                DIARY_CONTENT_LEVEL.GENERAL: settings.DIARY_CONTENT_LEVEL_SCORE['GENERAL'],
                DIARY_CONTENT_LEVEL.FINE: settings.DIARY_CONTENT_LEVEL_SCORE['FINE'],
                DIARY_CONTENT_LEVEL.EXCELLENT: settings.DIARY_CONTENT_LEVEL_SCORE['EXCELLENT'],
                DIARY_CONTENT_LEVEL.BETTER: settings.DIARY_CONTENT_LEVEL_SCORE['BETTER'],
            }
            audit_score = audit_score_map[audit.content_level]
        else:
            audit_score = 0.0

        # 医生抽成分
        choucheng_score = DoctorService.get_discount_score_for_diary_index(self.doctor_id)

        return heat_score, audit_score, choucheng_score

    @property
    def comment_count(self):
        return self.rate_count

    @property
    def has_commented(self):
        """日记本评价"""
        return bool(self.rating or self.comment)

    @property
    def title_style_type(self):
        return ''

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

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

        return result

    def get_tags_from_tags_by_types(self, 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

    @cached_property
    @common_cache_wrapper('diary:tags_new_era:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def tags_new_era(self):
        result = []
        for tag in self.tags:
            # 添加热门tag后 跟日记本相关的显示部分 热门tag作为3级tag来显示
            d = {'name': tag.name, 'id': tag.id, 'type': tag.tag_type, 'tag_id': tag.id}

            if hasattr(tag, 'itemwiki'):
                d['type'] = TAG_TYPE.ITEM_WIKI
                d['wiki_id'] = tag.itemwiki

            result.append(d)
        result = result[0:10]
        return result

    @cached_property
    def wiki_info(self):
        result = []

        for tag in self.tags_new_era:
            if 'wiki_id' not in tag:
                continue

            obj = {
                'id': tag['wiki_id'],
                'name': tag['name'],
                'tag_id': tag['id'],
            }
            result.append(obj)

        return result

    @cached_property
    def operation_tags(self):
        # get all operation tags
        tags = filter(lambda x: x.tag_type in (TAG_TYPE.ITEM_WIKI, TAG_TYPE.BODY_PART_SUB_ITEM), self.tags)
        return tags

    @classmethod
    def get_redis_diary_topic_vote_key(cls):
        topic_vote = "diary_topic_vote"
        return topic_vote

    @cached_property
    @common_cache_wrapper('diary:vote_num:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def vote_num(self):
        # TODO: 为了性能转成预处理
        # 医生版2.1.0 把评论的赞数也统计上了 2016-04-20
        # 日记贴里面把获取点赞数逻辑放入了redis,日记本取的是原生,是否考虑将
        # 日记本的点赞逻辑也放入redis

        # 6.5.0 用户感知去掉
        # comment_votes = sum(int(reply.like_num) for reply in self.topicreply_set.all())

        topic_ids = list(self.topics.all().values_list("id", flat=True))
        topic_vote_num_list = list(filter(None, ViewRecord(CONST_STRINGS.TOPIC_VOTE_V1).view_hmget(topic_ids)))
        num2 = sum(map(int, topic_vote_num_list)) if topic_vote_num_list else 0

        return num2 + self.like_num

    @property
    def cover_key(self):
        return "diary:enduring_cover:{diary_id}".format(diary_id=self.id)

    @cached_property
    def enduring_cover(self):
        data = page_cache.get(self.cover_key)
        if data is None:
            cover = self.cache_cover()
            return cover

        return json.loads(data)

    def cache_cover(self, timeout=60*60*24*1):
        """缓存封面图"""
        cover_key = self.cover_key
        cover = self.cal_cover()
        page_cache.setex(cover_key, timeout, json.dumps(cover))
        return cover

    @cached_property
    @common_cache_wrapper('diary:cover:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def cover(self):
        return self.cal_cover()

    def cal_cover(self):
        """
        封面
        v5.4.0 重新整理
        case1:日记本的所有贴子里术前术后图都至少有一张
          case1:日记本术前图术后图都已经设置了,正常显示
          case2:日记本术前图or术后图未设置,从所有贴子里取最新的术前或术后图补上,作为对比图封面
        case2:整个日记本缺少术前图
          case1:已经设置了日记本术后图,显示这张图片作为封面
          case2:未设置日记本术后图,显示最新的术后图作为封面
        case3:整个日记本缺少术后图
          case1:已经设置了日记本术前图,显示这张图片作为封面
          case2:未设置日记本术前图,显示最新的术前图作为封面
        case4:日记本无图,列表里不显示图片
        """

        # 日记本封面图新逻辑,数据全部按照新版逻辑获取,潜在问题浩楠已知晓
        from talos.models.topic.topicimage import TopicImage
        base_query = Q(topic__diary=self, topic__is_online=True, topic__flag=PROBLEM_FLAG_CHOICES.NORMAL)
        diary_operation_time = self.operation_time or self.created_time

        # 获取日记本术前封面图
        if self.pre_operation_image:
            pre_image = self.pre_operation_image
        else:
            pre_operation_image = self.pre_operation_images.all().order_by("-is_cover", "-id").first()
            if pre_operation_image:
                pre_image = pre_operation_image.image_url
            else:
                query = base_query & Q(topic__operation_date__lte=diary_operation_time)
                pre_topic_image = TopicImage.objects.filter(query).order_by("topic__operation_date", "id").first()
                pre_image = pre_topic_image.image_url if pre_topic_image else ""

        # 获取日记本术后封面图
        if self.post_operation_image:
            post_image = self.post_operation_image
        else:
            query = base_query & Q(topic__operation_date__gt=diary_operation_time)
            post_topic_image = TopicImage.objects.filter(query).order_by("-topic__operation_date", "-id").first()
            post_image = post_topic_image.image_url if post_topic_image else ""

        diary_cover = []
        if pre_image:
            pre_operation_image_data = {
                'image': get_full_path(pre_image),
                'desc': 'Before'
            }
            pre_operation_image_data.update(self._format_pic_dict(pre_image))
            diary_cover.append(pre_operation_image_data)

        if post_image:
            post_operation_image_data = {
                'image': get_full_path(post_image),
                'desc': 'After',
            }
            post_operation_image_data.update(self._format_pic_dict(post_image))
            diary_cover.append(post_operation_image_data)

        return diary_cover

    @staticmethod
    def _format_pic_dict(image_url):

        return convert_image(image_url, watermark=True)

    def has_cover(self):
        return bool(self.cover)

    @common_cache_wrapper('diary:video_cover:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def video_cover(self):
        data = {'video_pic': '', 'video_url': '', 'short_video_url': ''}

        if not settings.SHOW_VIDEO_COVER:
            data.update({'video_cover': ''})
            return data

        if self.latest_topic and self.latest_topic.can_video_cover:
            data = self.latest_topic.get_video_info()

        # 为客户端添加:`video_cover` 字段
        data.update({'video_cover': data['video_pic']})
        return data

    @property
    def operator_is_hospital_officer(self):
        return self.doctor and self.doctor.doctor_type == DOCTOR_TYPE.OFFICER or False

    @cached_property
    def doctor(self):
        if not self.doctor_id:
            return

        doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id)
        if not doctor:
            return

        return doctor

    def tag_count(self):
        return len(self.tags)

    @cached_property
    def related_service_name(self):
        if not self.order_id:
            return None

        order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id)
        if order:
            return order['service_name']
        else:
            return None

    def get_operation_info_for_topic_detail(self):
        """get diary operaiton info for topic detail.

        TODO:
            refactor topic detail page(pc, m, backend, doctor), get diary info from diary api
            instead of "api/topic"
        """
        result = {
            'operation_items': list(map(
                lambda x: {'tag_id': x.id, 'name': x.name, 'id': ''}, self.operation_tags
            )),
            'rating': self.rating or 0,
            'price': self.price,
            'operation_timestamp': None,
            'comment': self.comments,

            'service_id': '',
            'service_name': '',

            'hospital_name': self.raw_hospital or u'',

            'doctor_id': '',
            'doctor_name': self.raw_doctor or u'',
        }

        if self.operation_time:
            result['operation_timestamp'] = get_timestamp(self.operation_time)

        if self.order_id:
            # if diary related to an order, get doctor/service info from order
            order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id)
            if order:
                result['service_id'] = order['service_id']
                result['service_name'] = order['service_name']
                result['hospital_name'] = order['hospital_name']
                result['doctor_id'] = order['doctor_id']
                result['doctor_name'] = order['doctor_name']

        else:
            # get service doctor hospital by related id(diary.doctor_id, diary.hospital_id)
            if self.doctor:
                result['doctor_id'] = self.doctor.id
                result['doctor_name'] = self.doctor.name

            if self.hospital_id:
                hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id)
                if hospital:
                    result['hospital_name'] = hospital.name

        return result

    def _set_service_info(self, info, service):
        if service is not None:
            info['service_id'] = service.id
            info['service_name'] = service.name
            info['service_short_description'] = service.short_description
            info['service_image'] = service.image_header

    def _set_doctor_info(self, info, service):
        doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id)
        if doctor is not None:
            info['doctor_id'] = doctor.id
            info['doctor_name'] = doctor.name
            info['doctor_is_online'] = doctor.is_online
            info['doctor_portrait'] = doctor.portrait
            info['doctor_title'] = doctor.title  # 新加医生职称字段
        else:
            info['doctor_id'] = ''
            info['doctor_name'] = self.raw_doctor or u''
            info['doctor_portrait'] = ''
            info['doctor_title'] = ''

        if info['doctor_portrait'] == '' and service is not None:
            d_id = service.doctor.id
            d = DoctorService.get_doctor_from_doctor_id(d_id)
            if d is not None:
                info['doctor_portrait'] = d.portrait
            else:
                info['doctor_portrait'] = ''

    def _set_hospital_info(self, info):
        hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id)
        if hospital is not None:
            info['hospital_id'] = hospital.id
            info['hospital_name'] = hospital.name

        else:
            info['hospital_id'] = ''
            info['hospital_name'] = self.raw_hospital or u''

    def _set_order_info(self, info, s):
        order = OrderService.get_order_from_order_id(self.order_id)
        if order is not None:
            if s:
                info['order_info'] = {
                    'title': s.name,
                    'image': s.image_header,
                    'order_id': order.id,
                    'validate_time': get_timestamp_or_none(order.validate_time),
                    'groupbuy_status': order.groupbuy_status,
                }
            else:
                info['order_info'] = {}

            info['order_id'] = order.id
            if not info['service_id']:
                ss = json.loads(order.service_snapshot)
                if ss is None:
                    logging_exception()

                info['service_id'] = ss.get('id', '')
                info['service_name'] = ss.get('name', '')
                info['service_short_description'] = ss.get('short_description', '')
                info['service_image'] = ss.get('image_header', '')
                doctor = DoctorService.get_doctor_from_doctor_id(ss.get('doctor'))
                if doctor is None:
                    info['operator_is_hospital_officer'] = False
                else:
                    info['operator_is_hospital_officer'] = True if doctor.doctor_type == DOCTOR_TYPE.OFFICER else False
        else:
            info['order_id'] = ''
            info['order_info'] = {}

    def _get_all_pre_operation_images(self):
        pre_operation_images = self.pre_operation_images.all()
        images = []
        temp_image = []
        for image in pre_operation_images:
            is_cover = False
            if self.pre_operation_image:
                if image.image_url == self.pre_operation_image or image.cover_image_url == self.pre_operation_image:
                    is_cover = True
            elif image.is_cover:
                is_cover = True

            image_info = image.get_image_info()
            image_info['is_cover'] = is_cover
            temp_image.append(get_short_path(image.image_url))
            images.append(image_info)
        if temp_image:
            new_images = batch_move_watermark_pic_to_no_watermark(temp_image, 'wanmeizhensuo', 'wanmeizhensuo-tmp')
        i = 0
        for image_info in images:
            if image_info['image_name'] in new_images:
                image_info['source_image'] = get_temp_image_path(image_info['image_name'])
            else:
                image_info['source_image'] = image_info['image']
            i += 1
        return images

    def _set_operation_image_info(self, info):
        info['pre_operation_images'] = self._get_all_pre_operation_images()

        amount = len(info['pre_operation_images'])
        info['pre_operation_image_amount'] = u'{} 张'.format(amount) if amount else u''

        if self.pre_operation_image:
            info['pre_operation_image'] = {
                'image': get_full_path(self.pre_operation_image, '-w'),
                'image_thumb': get_full_path(self.pre_operation_image, '-thumb'),
                'image_half': get_full_path(self.pre_operation_image, '-half'),
                'image_wide': get_full_path(self.pre_operation_image, '-wide'),
                'desc': ''
            }
        else:
            info['pre_operation_image'] = {}

        if self.post_operation_image:
            info['post_operation_image'] = {
                'image': get_full_path(self.post_operation_image, '-w'),
                'image_thumb': get_full_path(self.post_operation_image, '-thumb'),
                'image_half': get_full_path(self.post_operation_image, '-half'),
                'image_wide': get_full_path(self.post_operation_image, '-wide'),
                'desc': ''
            }
        else:
            info['post_operation_image'] = {}

    def _set_operation_info(self, info):
        if self.operation_time:
            info['operation_timestamp'] = get_timestamp(self.operation_time)

        if self.operation_time:
            interval = datetime.datetime.now() - self.operation_time
            if interval.days >= 0:
                info['interval'] = u'{}'.format(interval.days + 1)
            else:
                info['interval'] = u''
        else:
            info['interval'] = u''

    def operation_info(self):
        operation_item_amount = self.tag_count()
        info = {
            'operation_item_amount': u'{} 项'.format(operation_item_amount) if operation_item_amount else u'',
            # TODO: delete id:'' @594, this is only for compatible
            'operation_items': list(map(
                lambda x: {'tag_id': x.id, 'name': x.name, 'id': ''}, self.operation_tags
            )),
            'rating': self.rating or 0,
            'operation_effect_rating': self.operation_effect_rating or 0,
            'doctor_attitude_rating': self.doctor_attitude_rating or 0,
            'hospital_env_rating': self.hospital_env_rating or 0,
            'price': self.price,
            'operation_timestamp': None,
            'operator_is_hospital_officer': self.operator_is_hospital_officer,
            'comment_count': self.comment_count,
        }

        info['service_id'] = ''
        info['service_name'] = ''
        info['service_short_description'] = ''
        info['service_image'] = ''

        service = GoodsService.get_service_by_service_id(self.service_id)
        self._set_service_info(info, service)
        self._set_doctor_info(info, service)
        self._set_order_info(info, service)

        self._set_hospital_info(info)
        self._set_operation_image_info(info)
        self._set_operation_info(info)

        info['comment'] = self.comments
        info['has_topics'] = self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).count() > 0

        info['operation_effect_rating'] = self.operation_effect_rating
        info['doctor_attitude_rating'] = self.doctor_attitude_rating
        info['hospital_env_rating'] = self.hospital_env_rating
        info['show_comment'] = self.has_commented
        info['comment_count'] = self.comment_count

        return info

    @property
    def comments(self):
        # 可供选择的评论
        comments_choice = {
            'good': [
                {'name': u'效果非常好', 'select': 0},
                {'name': u'医生技术好', 'select': 0},
                {'name': u'医生态度好', 'select': 0},
                {'name': u'医院环境好', 'select': 0},
                {'name': u'价格超便宜', 'select': 0},
                {'name': u'性价比很高', 'select': 0},
                {'name': u'会推荐朋友', 'select': 0},
            ],
            'bad': [
                {'name': u'还挺一般的', 'select': 0},
                {'name': u'医院老推销', 'select': 0},
                {'name': u'效果不理想', 'select': 0},
            ]
        }

        if self.comment:
            return json.loads(self.comment)
        else:
            return comments_choice

    @property
    def pre_operation_images_info(self):
        return [get_full_path(i.image_url) for i in self.pre_operation_images.all()]

    def is_voted_by(self, user):
        from .diaryvote import DiaryVote
        if not user:
            return False

        try:
            DiaryVote.objects.get(user_id=user.id, diary=self)
            is_voted = True

        except DiaryVote.DoesNotExist:
            is_voted = False

        return is_voted

    @cached_property
    def latest_topic(self):
        latest_topic = self.topics.filter(
            is_online=True,
            flag=PROBLEM_FLAG_CHOICES.NORMAL
        ).select_related('video').order_by('-operation_date', '-id').first()
        # v 7.6.25 变更 日记帖取设置的术后日期离今天最近的一篇,时间相同取id最大
        return latest_topic

    @property
    def author_type(self):
        return AUTHOR_TYPE.USER

    def get_simple_diary(self, user=None):
        social_info = get_social_info(user)
        is_following = social_info and social_info.is_following_user(uid=self.user_id) or False

        latest_topic = self.latest_topic
        latest_topic_id = latest_topic and latest_topic.id or 0
        content = latest_topic and escape(latest_topic.answer) or ''

        hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id)
        if hospital is not None:
            hospital_name = hospital.name
        else:
            hospital_name = self.raw_hospital or u''

        doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id)

        diary_author = UserService.get_user_by_user_id(self.user_id)

        # TO DO:
        # diary_num 在IOS客户端全局无使用, 安卓客户端某个不确定版本已废弃
        # 当前最新线上版本v6.4.0,期望在某个版本迭代后将此字段删除
        data = {
            'title_style_type': self.title_style_type,
            'reply_num': self.reply_num,
            'is_following': is_following,
            'city': diary_author.city_name,
            'user_id': self.user_id,
            'title': self.title if self.title else '',
            'user_portrait': diary_author.portrait,
            'latest_topic_id': latest_topic_id,
            'diary_id': self.id,
            'vote_num': self.vote_num,
            'content': content,
            'user_nickname': diary_author.nickname,
            'author_type': AUTHOR_TYPE.USER,
            'tags': self.tags_new_era,
            'images': self.cover,
            'sticky_post': self.sticky_post,
            'membership_level': diary_author.membership_level,
            'view_num': self.view_num,
            'diary_amount': self.extra_info.topic_count if hasattr(self, 'extra_info') else 0,
            'is_voted': self.is_voted_by(user),
            'vote_num_gained': diary_author.vote_count,
            'topic_num_posted': diary_author.topic_count,
            'diary_num': self.topic_num,
            'doctor_id': self.doctor_id,
            'doctor_name': doctor and doctor.name,
            'hospital_name': hospital_name,
            'created_time': get_timestamp_or_none(self.created_time),
            # TO DO:
            # 以下字段为本次上线需要兼容backend做的冗余,下次迭代需删除
            'is_identification': self.is_identification,
            'tags_new_era': self.tags_new_era,
            'date': get_timestamp_or_none(self.last_modified),
            'user': {
                'vote_num_gained': diary_author.vote_count,
                'topic_num_posted': diary_author.topic_count,
                'portrait': diary_author.portrait,
                'user_name': diary_author.nickname,
                'membership_level': diary_author.membership_level,
                'city': diary_author.city_name,
                'user_id': self.user_id,
            },
            'user_level': {
                'membership_icon': user and user.membership_icon or '',
                'level_icon': user and user.level_icon or '',
                'constellation_icon': user and user.constellation_icon or '',
            },
            # 以下为兼容医生版的字段, 下次迭代需跟医生版上来商量删除
            'tags_info': [],
            'rating': self.rating,  # 给美购的评分
        }
        return data

    def get_diary_info(self, simple=False, user=None):
        # NOTE: if simple is True, operation info wont be return
        #       all diary list should call this method with simple=True
        social_info = None
        if user:
            from social.models import SocialInfo
            social_info = SocialInfo(uid=user.id)

        doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id)

        diary_author = UserService.get_user_by_user_id(user_id=self.user_id)
        diary_data = {
            'user': {
                'user_id': self.user_id,
                'user_name': diary_author.nickname,
                'portrait': diary_author.portrait,
                'city': diary_author.city_name,
                'membership_level': diary_author.membership_level,
                'doctor_id': '',
                'hospital_id': '',
                'vote_num_gained': diary_author.vote_count,
                'topic_num_posted': diary_author.topic_count,
            }
        }

        user_bz = UserService.get_doctor_hospital_id_by_user_id(self.user_id)
        if user_bz:
            diary_data['user_id'] = user_bz['user_id']
            diary_data['doctor_id'] = user_bz['doctor_id']
            diary_data['hospital_id'] = user_bz['hospital_id']

        diary_data['raw_doctor'] = self.raw_doctor if self.raw_doctor else ''
        diary_data['raw_hospital'] = self.raw_hospital if self.raw_hospital else ''
        diary_data['diary_id'] = self.id
        diary_data['diary_amount'] = self.topics.filter(is_online=True).count()
        diary_data['reply_num'] = self.reply_num
        diary_data['reply_num_for_diary'] = self.topicreply_set.filter(is_online=True).count()
        diary_data['vote_num'] = self.vote_num
        diary_data['view_num'] = self.view_num

        # get doctor info
        hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id)

        diary_data['doctor_id'] = self.doctor_id or ''
        diary_data['doctor_name'] = doctor and doctor.name or ''
        diary_data['hospital_name'] = hospital and hospital.name or ''
        diary_data['hospital_id'] = self.hospital_id or ''

        latest_topic = self.topics.filter(
            is_online=True,
            flag=PROBLEM_FLAG_CHOICES.NORMAL
        ).last()

        diary_data.update(self.video_cover())

        order = OrderService.get_order_from_order_id(self.order_id)
        if order is not None and order.cash_back_status == CASH_BACK_STATUS.SUCCESS:
            is_cash_back = True
        else:
            is_cash_back = False

        diary_data.update({
            'images': self.cover,
            'title': self.title if self.title else '',
            'last_modified': get_timestamp_or_none(self.last_modified),
            'created_time': get_timestamp_or_none(self.created_time),
            'is_identification': self.is_identification,
            'is_headline': self.is_headline,
            'is_essence': self.is_essence,
            'is_online': self.is_online,
            'is_sink': self.is_sink,
            'is_cash_back': is_cash_back,
            'is_favored': self.favor_diary_diary.filter(user_id=user.id, is_deleted=False).exists() if user else False,
            'is_following': social_info and social_info.is_following_user(uid=self.user_id) or False,
            'latest_topic_id': latest_topic and latest_topic.id or 0,

            'tags_new_era': self.tags_new_era,
            'tags': self.wiki_info,
            'zones': [],
            'tags_info': [],

            'is_voted': self.is_voted_by(user),
            'sticky_post': self.sticky_post,
        })

        author_type = self.author_type

        diary_data.update({
            'title_style_type': self.title_style_type,
            'author_type': author_type,
            'content': latest_topic and escape(latest_topic.answer) or '',
            'date': get_timestamp_or_none(self.last_modified),
        })

        if simple:
            return diary_data

        diary_data['operation_info'] = self.operation_info()
        return diary_data

    @property
    def extra_diary_images(self):
        return [get_full_path(topic.image_url) for topic in self.topics.last().images.all()]

    def newest_cover(self):
        """ v5.3.0 获取日记本最新的帖子的图片 作为封面
        规则:
        1.最新的帖子
        2.术后图
        3.术前图
        """
        topics = self.topics.all().order_by('-created_time')
        image = settings.DIARY_COVER_DEFAULT
        for topic in topics:
            images = topic.images.all()
            if images:
                image = images[0].image_url
                break
        return image

    def get_simple_tags(self):
        tags = self.tags
        return tags[0:4]

    def simple_data(self):
        """ v5.3.0
        """
        is_service_diary = False
        tags = [t.name for t in self.get_simple_tags()]

        service = GoodsService.get_service_by_service_id(self.service_id)
        if service:
            tags = [service.name]
            is_service_diary = True

        return {
            'is_service_diary': is_service_diary,
            'id': self.id,
            'topics_num': self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).count(),
            'tags': tags,
            'title': self.title,
            'image': self.newest_cover(),
            'created_time': get_timestamp(self.created_time),
            'last_modified': get_timestamp(self.last_modified),
            'review_count': self.review_count,
            'vote_count': self.vote_num,
            'comment_count': self.comment_count if self.order_id is not None else 0,
            'type': VOTEOBJECT.DIARY,
        }

    def topics_data(self):
        topics = []
        for topic in self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL):
            topics.append(
                {
                    "id": topic.id,
                    "title": topic.get_title(),
                    "review_status": topic.review_status,
                    "review_reason": PROBLEM_REVIEW_STATUS_CHOICES.getDesc(topic.review_status)
                }
            )
        return topics

    @property
    def cashback_topic_num_limit(self):
        service = GoodsService.get_service_by_service_id(self.service_id)
        if service:
            # self.service 不是NoneType时
            return service.cashback_limit
        else:
            return 0

    @property
    def review_count(self):
        return self.topics.filter(review_status=PROBLEM_REVIEW_STATUS_CHOICES.OK).count()

    @property
    def diary_first_topic(self):
        return self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).order_by('created_time').first()

    @property
    def view_num(self):
        num1 = diary_pv_cache.get(str(self.id))
        topic_ids = list(self.topics.all().values_list("id", flat=True))
        topic_view_num_list = list(filter(None, ViewRecord(CONST_STRINGS.TOPIC_VIEW).view_hmget(topic_ids)))
        num2 = sum(map(int, topic_view_num_list)) if topic_view_num_list else 0

        if num1 is None:
            num1 = 0
        if num2 is None:
            num2 = 0
        return str(int(num1) + int(num2))

    @cached_property
    def topic_num(self):
        num = self.topics.filter(flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True).count()
        return num

    @property
    def get_diary_operate(self):
        from .diaryoperate import DiaryOperate
        '''
        :return: 用户的更新操作
        '''
        if self.audit_time is not None:
            audit_time = self.audit_time
        else:
            audit_time = datetime.datetime.fromtimestamp(0)

        update_info_list = DiaryOperate.objects.filter(
            diary__id=self.id,
            operate_time__gte=audit_time,
            operation=DIARY_OPERATE.UPDATE_INFO,
        ).values('topic').annotate(Count('topic'))

        update_topic_list = DiaryOperate.objects.filter(
            diary__id=self.id,
            operate_time__gte=audit_time,
            operation=DIARY_OPERATE.UPDATE_TOPIC,
        ).values('topic').annotate(Count('topic'))

        delete_topic_list = DiaryOperate.objects.filter(
            diary__id=self.id,
            operate_time__gte=audit_time,
            operation=DIARY_OPERATE.DELETE_TOPIC,
        ).values('topic').annotate(Count('topic'))

        add_topic_list = DiaryOperate.objects.filter(
            diary__id=self.id,
            operate_time__gte=audit_time,
            operation=DIARY_OPERATE.ADD_TOPIC,
        ).values('topic').annotate(Count('topic'))

        read = u''

        if len(update_info_list) != 0:
            read += u'更新术前资料\n'
        if len(update_topic_list) != 0:
            read += u'修改%s篇帖子\n' % str(len(update_topic_list))
        if len(delete_topic_list) != 0:
            read += u'删除%s篇帖子\n' % str(len(delete_topic_list))
        if len(add_topic_list) != 0:
            read += u'新增%s篇帖子\n' % str(len(add_topic_list))

        return read[:-1]

    def set_diary_operate(self, operation, topic=''):
        from .diaryoperate import DiaryOperate
        if operation in DIARY_OPERATE:
            diary_operate = DiaryOperate()
            diary_operate.diary_id = self.id
            diary_operate.operate_time = datetime.datetime.now()
            diary_operate.operation = operation
            diary_operate.topic = topic
            diary_operate.save()
            if self.audit_status != DIARY_AUDIT_STATUS.UNAUDITED:
                self.audit_status = DIARY_AUDIT_STATUS.REAUDIT
                self.save()

    @cached_property
    @common_cache_wrapper('diary:tags:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def tags(self):
        # 日记本关联的tag数量有限的话 可以这么做
        from .diarytag import DiaryTag
        tag_ids = DiaryTag.objects.filter(diary_id=self.id).values_list('tag_id', flat=True)
        tag_ids = list(tag_ids)
        tags = TagService.get_tags_by_tag_ids(tag_ids)
        tags = self.tags_sort_for_special(tags)
        return tags or []

    @cached_property
    @common_cache_wrapper('diary:tag_v3:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)])
    def tags_v3(self):
        # tag v3
        from .diarytag import DiaryTagV3
        tag_ids = list(DiaryTagV3.objects.filter(diary_id=self.id).values_list('tag_v3_id', flat=True))
        tags = TagV3Service.get_tags_by_tag_v3_ids(tag_ids)
        tags_info = [
            TagV3Service.format_tag_v3(tag)
            for tag in tags.values()
        ]
        return tags_info

    @cached_property
    def all_tags(self):
        # 日记本关联的tag数量有限的话 可以这么做
        from .diarytag import DiaryTag
        tag_ids = DiaryTag.objects.filter(diary_id=self.id).values_list('tag_id', flat=True)
        tag_ids = list(tag_ids)
        tags = TagService.get_tags_by_tag_ids(tag_ids)
        return tags or []

    def add_tags(self, tag_ids):
        from .diarytag import DiaryTag
        for tag_id in tag_ids:
            DiaryTag.objects.get_or_create(diary_id=self.id, tag_id=tag_id)

    def upd_tags(self, tag_ids):
        new_tags = set([int(tag_id) for tag_id in tag_ids if str(tag_id).isdigit()])
        old_tags = set([tag.id for tag in self.all_tags])
        self.add_tags(list(new_tags - old_tags))
        self.del_tags(list(old_tags - new_tags))

    def del_tags(self, tag_ids):
        from .diarytag import DiaryTag
        DiaryTag.objects.filter(diary_id=self.id, tag_id__in=tag_ids).delete()

    def delete_all_tags(self):
        from .diarytag import DiaryTag
        DiaryTag.objects.filter(diary_id=self.id).delete()

    @property
    def is_operation(self):
        if not self.order_id:
            return False

        order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id)
        if order:
            return order['is_operation']
        else:
            return False

    @classmethod
    def load_by_id(cls, diary_id):
        try:
            return cls.get(pk=diary_id)
        except cls.DoesNotExist:
            return None

    @classmethod
    def list_by_ids(cls, diary_ids):
        return cls.objects.filter(pk__in=diary_ids)


class DiaryRecord(models.Model):
    """
        日记本新增、修改记录
    """

    class Meta:
        verbose_name = u'日记本新增修改记录'
        verbose_name_plural = u'日记本新增修改记录'
        app_label = 'talos'
        db_table = 'api_diary_record'

    diary_id = models.IntegerField(verbose_name="日记id")