# -*- coding: utf8 -*-

""" this module is FOR diary update operation info, cover image, add new topic and delete etc."""

from __future__ import unicode_literals, absolute_import, print_function

import re
import datetime
import time
import json

from django.db import transaction
from django.conf import settings
from django.db.models import Q

from gm_types.gaia import (
    DIARY_OPERATE,
    DIARY_OPERATION,
    FILTER_WORD_TYPE,
    POINTS_TYPE,
    TOPIC_TYPE,
    IMAGE_TYPE,
    TAG_TYPE,
    TOPIC_IMAGE_TYPE,
    PROBLEM_FLAG_CHOICES,
    CONST_STRINGS,
)
from gm_types.mimas import ATTENTION_NOTIFY_TYPE
from gm_types.user_hierarchy import EventType
from gm_upload import batch_move_watermark_pic_to_no_watermark
from gm_rpcd import all
from gm_rpcd.all import bind
from gm_types.error import ERROR as CODES

from talos.manager.diary import set_diary_cover, set_diary_postoperation_cover
from talos.manager.diary import (
    set_pre_operation_images,
    set_pre_operation_images_v1
)
from talos.libs.image_utils import get_full_path, get_short_path

from talos.models.diary import Diary, MAX_STICK_PRIORITY, DEFAULT_STICK_PRIORITY
from talos.models.diary import DiaryTag
from talos.models.diary import DiaryExtra
from talos.models.diary.preoperationimage import PreOperationImage
from talos.models.topic import ProblemTag
from talos.models.topic import Video
from talos.models.topic import Problem
from talos.models.topic import TopicImage
from talos.models.draft import Draft

from talos.services import OrderService
from talos.services import get_doctor_from_context
from talos.services.tag import TagService
from talos.services import get_user_from_context, UserService
from talos.services.other import add_points
from talos.services.goods import GoodsService

from talos.cache.base import diary_pv_cache

from talos.libs.image_utils import get_temp_image_path
from talos.libs.datetime_utils import (
    get_timestamp_or_none,
    get_timestamp_epoch,
)
from talos.libs.diary import (
    common_func_for_create_diary,
    update_diary_operation_info_func,
    create_diary,
)
from talos.libs.diaryrank_utils import DiaryRankTool
from talos.libs.topic import create_topic_for_batch
from talos.cache.takesofacache import take_sofa_cache
from talos.decorators import list_interface, listing

from talos.rpc import bind_context
from utils.rpc import gen, rpc_client, logging_exception, get_current_user
from talos.logger import info_logger, qiniu_logger

from talos.tasks.topic import push_diary_update_to_follow, add_doctor_topic

from talos.tools.filterword_tool import filterword_by_custom
from talos.tasks.follow_msg_create import create_information_db

from user_hierarchy.portal import process_task

from live.tasks import upload_video_picture_to_qiniu_v2, set_water_mark_to_video


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


@bind('diary/view_increase')
def diary_view_increase(id=None, ids=None):
    update = False

    if id:
        diary_pv_cache.incrby(str(id), 1)
        update = True

    elif ids:
        for item in ids:
            diary_pv_cache.incrby(str(item), 1)
        update = True

    return {'updated': update}


@bind('diary/add_preoperation_images')
def diary_add_image(
        diary_id,
        images=[],
):
    """
    日记本术前相册增加照片
    :param ctx:
    :param diary_id:
    :param images:
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        diary = Diary.objects.get(id=diary_id)
        if diary and not diary.is_online:
            return gen(CODES.DIARY_HAS_BEEN_DELETED)

        if diary.user_id != user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    with transaction.atomic():
        for image in images:
            taken_time = datetime.datetime.now()
            diary.pre_operation_images.create(
                image_url=image, taken_time=taken_time
            )

            # 记录操作 UPDATE_INFO
            diary.set_diary_operate(DIARY_OPERATE.UPDATE_INFO)

    diary.last_modified = datetime.datetime.now()
    diary.save()

    return {'added': True}


@bind_context('diary/update')
def diary_update(
        ctx,
        diary_id,
        content='',
        images=[],
        tag_ids=[],
        device_id=None,
        cover=None,
        draft_id=None,
        video_url='',
        operation_date=None,
):
    """
    日记本更新(发新日记帖)
    :param ctx:
    :param diary_id:
    :param content:
    裁剪完成的日记
    :param images: [{"timestamp":123445552, "type":"0", "image": "2015/06/23/d4f38b13ab",
    "cover_image_url": "2015/06/23/d4f38b13ab"}, {}]
    :return:
    """
    user = get_user_from_context(ctx)

    # 日记更新敏感词过滤,以前的代码进行了数字的过滤，本次沿用
    filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_CONTENT, content=content)

    try:
        diary = Diary.objects.get(id=diary_id)
        if diary and not diary.is_online:
            return gen(CODES.DIARY_HAS_BEEN_DELETED)

        if diary.user_id != user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    if images or content:
        topic = Problem()
        topic.diary_id = diary.id
        topic.user_id = user.id
        topic.answer = content
        topic.topic_type = TOPIC_TYPE.SHARE
        if device_id:
            topic.device_id = device_id

        if operation_date:
            topic.operation_date = datetime.date.fromtimestamp(float(operation_date))
        else:
            topic.operation_date = datetime.datetime.now()

        topic.set_review_status(len(images))
        topic.save()

        # 和医生建立关系，医生后台可见
        add_doctor_topic.delay([topic.id], user.id)

        # push signal到mimas
        growth_value, point_value, submit_count = 0, 0, 0  # 默认非首次加载
        if len(content.strip()) >= 30 and len(images) >= 3:
            event_data = process_task(user_id=user.id, event_type=EventType.CREATE_TOPIC, related_item=topic.id)
            growth_value, point_value, submit_count = event_data['growth_value'], event_data['point_value'], event_data['submit_count']

        if video_url != '' and user.can_create_video:
            video_url = Video.cleaned_video_url(video_url)
            video = Video()
            video.topic_id = topic.id
            video.video_url = video_url
            video.video_pic = ''
            video.save()
            upload_video_picture_to_qiniu_v2.delay(video.id)
            set_water_mark_to_video.delay(video.id)

            # 如果有小视频 就给日记本添加视频日记的tag
            diary.add_tags([settings.VIDEO_TAG_ID, ])

        if settings.DIARY_MATCH_TAG_ID in tag_ids:
            diary.add_tags([settings.DIARY_MATCH_TAG_ID, ])

        # 记录操作 ADD_TOPIC
        diary.set_diary_operate(DIARY_OPERATE.ADD_TOPIC, topic.id)

        images = sorted(images, key=lambda x: x['type'], reverse=True)
        for image in images:
            if 'image' not in image:
                # seems like client has bugs when uploading images
                continue

            timestamp = image.get('timestamp', None)

            if timestamp:
                taken_time = datetime.datetime.fromtimestamp(int(timestamp))
            else:
                taken_time = None

            if image['type'] == IMAGE_TYPE.PRE_OPERATION_IMAGE:
                diary.pre_operation_images.create(
                    image_url=image['image'], taken_time=taken_time
                )

                # 记录操作 UPDATE_INFO
                diary.set_diary_operate(DIARY_OPERATE.UPDATE_INFO)

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

            topic.images.create(
                image_url=image['image'], taken_time=taken_time,
                image_type=image['type'], is_cover=is_cover, cover_image_url=image.get('modified_image_url')
            )

        for tag in diary.tags:
            tag_ids.append(tag.id)

        # get tags those cant be added by user
        has_geo_tag = False
        tags = TagService.get_tags_by_tag_ids(list(set(tag_ids)))
        for t in tags:

            if t.tag_type in (TAG_TYPE.COUNTRY, TAG_TYPE.PROVINCE):
                continue

            if t.tag_type == TAG_TYPE.CITY:
                has_geo_tag = True

            try:
                ProblemTag.create_bind(problem_id=topic.id, tag_id=t.id)
            except:
                logging_exception()
                info_logger.info('tag_id: {} not found'.format(t.id))

        if not has_geo_tag and user.city_tag_id:
            try:
                ProblemTag.create_bind(problem_id=topic.id, tag_id=user.city_tag_id)
            except:
                logging_exception()

        if cover:
            diary.post_operation_image = cover

        diary.last_modified = datetime.datetime.now()
        diary.last_topic_add_time = diary.last_modified
        diary.save()

        diary_id = diary.id
        d = DiaryRankTool(diary_id)
        now = datetime.datetime.now()
        d.add_heat(DIARY_OPERATION.CREATE_TOPIC, now)

        if diary.doctor_id:
            topic_info = {
                'id': topic.id,
                'diary_id': diary_id,
                'nickname': user.nickname,
                'person_id': user.person_id,
                'related_service_name': diary.related_service_name,
                'title': diary.title,
                'service_id': diary.service_id,
                'operation_time': diary.operation_time.strftime('%Y-%m-%d') if diary.operation_time else '',
            }
            if diary.topics.count() >= 1:
                rpc_client['doctor/notify/create_topic'](doctor_id=diary.doctor_id, topic=topic_info, count=diary.topics.count()).unwrap()

        if diary.order_id:
            # diary is related to an order create cashback order
            OrderService.create_cashback_order_by_order_id(diary.order_id)

        if diary.topics.count() >= 1 and diary.reply_num == 0:
            # add diary_id to take_sofa diary_id list
            create_time = time.mktime(diary.last_modified.timetuple())
            take_sofa_cache.set_diary_cache_data(create_time, diary.id)

        data = {}
        topic_tag_ids = ProblemTag.get_tag_ids_by_topic_id(problem_id=topic.id)
        if topic_tag_ids:
            first_tag_id = topic_tag_ids[0]
            first_tag = TagService.get_tag_by_tag_id(first_tag_id)
            data = {
                'tag_id': first_tag.id,
                'name': first_tag.name,
            }
        else:
            diary_tags = diary.tags
            if diary_tags:
                first_tag = diary_tags[0]
                data = {
                    'tag_id': first_tag.id,
                    'name': first_tag.name,
                }

        if draft_id:
            try:
                draft_id = int(draft_id)
                draft = diary.drafts.get(pk=draft_id)
                draft.delete()
            except Draft.DoesNotExist:
                pass

        diary_extra = DiaryExtra.objects.filter(diary=diary)
        if not diary_extra:
            diary_extra = DiaryExtra()
            diary_extra.diary = diary
        else:
            diary_extra = diary_extra[0]
        diary_extra.save()

        result = {'first_tag': data,
                  'id': topic.id,
                  'point_value': point_value,
                  'growth_value': growth_value,
                  'submit_count': submit_count
                  }

        push_diary_update_to_follow.delay(diary.id, diary.user_id, diary_title=diary.title)
    else:
        result = {}

    return result


@bind('diary/create_after_paid')
def diary_create_after_paid(
        order_id,
        title,
        user_id,
        service_id,
        doctor_id,
        hospital_id,
        tag_ids=[]
):
    # NOTE: only for internal use, call this func after user paid the bill
    try:
        order_id = str(order_id)
        d = Diary.objects.get(order_id=order_id)
        # created already, ignore this
        return {'created': False, 'id': d.id}

    except Diary.DoesNotExist:
        pass

    # 不存在 则创建
    with transaction.atomic():
        obj = Diary.objects.create(
            order_id=order_id,
            title=title,
            user_id=user_id,
            service_id=service_id,
            doctor_id=doctor_id,
            hospital_id=hospital_id,
            is_online=True
        )

        for tag_id in tag_ids:
            dt = DiaryTag(diary=obj, tag_id=tag_id)
            dt.save()

    return {'created': True, 'id': obj.id}


@bind_context('diary/create')
def diary_create(ctx,
        comment=[],
        order_id=None,
        tag_ids=[],
        operation_timestamp=None,
        doctor_id=None,
        doctor_name=None,
        hospital_id=None,
        hospital_name=None,
        price=0,
        rating=None,
        operation_effect_rating=None,
        doctor_attitude_rating=None,
        hospital_env_rating=None,
        pre_operation_images=[],
        cover=None,
):
    """
    创建日记本
    :param ctx:
    :param comment: 评价列表
    :param order_id: 订单ID
    :param tag_ids: tag ID列表
    :param operation_timestamp: 手术时间戳
    :param doctor_id: 医生ID
    :param doctor_name: 医生姓名
    :param hospital_id: 机构ID
    :param hospital_name: 机构名称
    :param price: 价格
    :param rating: 评价星标
    :return:
    """
    user = get_user_from_context(ctx)
    if not bool(user):
        return gen(CODES.LOGIN_REQUIRED)

    kw = {
        "user": user,
        "tag_ids": tag_ids,
        "operation_timestamp": operation_timestamp,
        "order_id": order_id,
        "doctor_id": doctor_id,
        "doctor_name": doctor_name,
        "hospital_id": hospital_id,
        "hospital_name": hospital_name,
        "pre_operation_images": pre_operation_images,
        "cover": cover,
        "price": price,
        "rating": rating,
        "comment": comment,
        "operation_effect_rating": operation_effect_rating,
        "doctor_attitude_rating": doctor_attitude_rating,
        "hospital_env_rating": hospital_env_rating,
    }
    diary = common_func_for_create_diary(**kw)
    return diary.simple_data()


@bind_context('diary/take_sofa/get')
@list_interface(offset_name='start_num', limit_name='count', element_model=Diary,
                element_func_list=[Diary.get_diary_info])
def api_diary_take_sofa_get(ctx, count=10, start_num=0):
    user = get_user_from_context(ctx)
    if not bool(user):
        return gen(CODES.LOGIN_REQUIRED)

    result = []

    diary_ids = take_sofa_cache.get_diary_cache_data(start_num, start_num + count - 1)
    diaries = Diary.objects.filter(id__in=diary_ids).order_by('-id')

    for diary in diaries:
        if diary.topics.count() == 0:
            continue

        diary_data = diary.get_simple_diary(user)
        result.append(diary_data)

    return result


@bind('diary/cover/update')
def diary_cover_image_update(diary_id, images=[]):  # TODO: merge 680 updates @weijialiang
    """
    编辑日记本封面
    :param ctx:
    :param diary_id:
    :param images: [{
        "type":"0",
        "image": "2015/06/23/d4f38b13ab", 原图
        "cover_image_url":"2016/07/29/8ff4b6a24c",
        "modified_image_url": 裁剪后
    }],
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        diary = Diary.objects.get(id=diary_id)
        if diary and not diary.is_online:
            return gen(CODES.DIARY_HAS_BEEN_DELETED)

        if not diary.user_id == user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    for image in images:
        if image['type'] == IMAGE_TYPE.PRE_OPERATION_IMAGE:
            source_image = diary.pre_operation_images.filter(image_url=image['image']).first()
            if source_image:
                source_image.cover_image_url = image.get('modified_image_url')
                source_image.save()

            diary.pre_operation_image = image['modified_image_url']
            set_diary_cover(image['modified_image_url'], diary)

        elif image['type'] == IMAGE_TYPE.POST_OPERATION_IMAGE:
            if image.get('modified_image_url'):
                diary.post_operation_image = image['modified_image_url']
                topic_ids = Problem.objects.filter(diary_id=diary.id, is_online=True).values_list('id', flat=True)
                images = TopicImage.objects.filter(topic_id__in=topic_ids, image_url=image['image'])
                images.update(cover_image_url=image['modified_image_url'])
            else:
                diary.post_operation_image = image['image']

            set_diary_postoperation_cover(diary.post_operation_image, diary)
        else:
            return gen(CODES.OPERATION_NOT_SUPPORTED)

    diary.last_modified = datetime.datetime.now()
    diary.save()

    return {'updated': True}


@bind('diary/operation_info')
def diary_operation_info(diary_id, display_new_version=False):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    try:
        diary = Diary.objects.get(id=diary_id)
        if user.id != diary.user_id:
            return gen(CODES.OPERATION_NOT_SUPPORTED)

    except (Diary.DoesNotExist, ValueError):
        return gen(CODES.DIARY_NOT_FOUND)

    result = diary.operation_info()
    if display_new_version:
        filter_tag_type = [TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI, TAG_TYPE.FREE, TAG_TYPE.YUNYING]
        result.update({
            "diary_title": diary.title or "",  # v 7.7.25 增加日记本标题
            "operation_items": list(map(lambda x: {'tag_id': x.id, 'name': x.name, 'id': ''},
                                        diary.get_tags_from_tags_by_types(diary.all_tags, filter_tag_type))),  # 项目标签变更
            "diary_cover": diary.cover,
        })

    return result


def diary_operation_info_update_base(
        ctx,
        diary_id,
        doctor_id=None,
        doctor_name=None,
        order_id=None,
        hospital_id=None,
        hospital_name=None,
        operation_timestamp=None,
        tag_ids=[],
        comment=None,
        price=0,
        rating=None,
        operation_effect_rating=None,
        doctor_attitude_rating=None,
        hospital_env_rating=None,
        pre_operation_images=[],
        cover=None,
        need_update_rate=False,
        need_clear_pre_image=False
):
    """
    日记本的手术信息编辑
    :param ctx:
    :param diary_id:
    :param doctor_id:
    :param doctor_name:
    :param order_id:
    :param hospital_id:
    :param hospital_name:
    :param operation_timestamp:
    :param tag_ids:
    :param comment:
    :param price:
    :param rating:
    :return:
    """
    user = get_user_from_context(ctx)
    if not bool(user):
        return gen(CODES.LOGIN_REQUIRED)

    try:
        diary = Diary.objects.get(id=diary_id)
        # if diary and not diary.is_online:
        #     return gen(CODES.DIARY_HAS_BEEN_DELETED)
        # Bug 4211 jiajun's comment

        if not diary.user_id == user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    if price:
        try:
            diary.price = int(price)
        except:
            pass

    point_number = 0
    if not diary.has_commented and (comment or rating):
        point_number = add_points(reason_type=POINTS_TYPE.REPLY_DIARY)

    update_rate = False

    if rating:
        try:
            rating = int(rating)
            if diary.rating != rating and 1 <= rating <= 5:
                update_rate = True
                diary.rating = rating
        except:
            pass

    if doctor_id:
        diary.doctor_id = doctor_id
    else:
        diary.doctor_id = None

    if doctor_name:
        diary.raw_doctor = doctor_name

    if hospital_id:
        diary.hospital_id = hospital_id
    else:
        diary.hospital_id = None

    if hospital_name:
        diary.raw_hospital = hospital_name

    order_info = OrderService.get_order_data_for_update_diary_operation_info(order_id=order_id)
    if order_info and not diary.order_id:
        diary.order_id = order_id

        diary.doctor_id = diary.doctor_id and diary.doctor_id or order_info['doctor_id']
        diary.doctor_name = diary.doctor_name and diary.doctor_name or order_info['doctor_name']

        diary.hospital_id = diary.hospital_id and diary.hospital_id or order_info['hospital_id']
        diary.hospital_name = diary.hospital_name and diary.hospital_name or order_info['hospital_id']

        if not price and not diary.price:
            diary.price = order_info['price']

        if not diary.service_id:
            diary.service_id = order_info['service_id']

        tag_ids.append(order_info["city_tag_id"])

    if operation_timestamp:
        operation_time = datetime.date.fromtimestamp(
            float(operation_timestamp))
        diary.operation_time = operation_time
        diary.is_fake_operation_time = False
    else:
        diary.operation_time = datetime.datetime.now()
        diary.is_fake_operation_time = True

    record_rating = True
    for _rating in (operation_effect_rating, doctor_attitude_rating, hospital_env_rating):
        if _rating < 1 or _rating > 5:
            record_rating = False
            break

    if record_rating:
        # 如果有非法的星级评价 此次作废
        # 如果comment和星级评价都传过来 以星级评价为准
        if diary.operation_effect_rating != operation_effect_rating:
            diary.operation_effect_rating = operation_effect_rating
            update_rate = True

        if diary.doctor_attitude_rating != doctor_attitude_rating:
            diary.doctor_attitude_rating = doctor_attitude_rating
            update_rate = True

        if diary.hospital_env_rating != hospital_env_rating:
            diary.hospital_env_rating = hospital_env_rating
            update_rate = True

    # 有页面需要根据update_rate判断,有的不需要
    if need_update_rate:
        if update_rate and diary.rate_count >= settings.DIARY_RATING_LIMIT:
            return gen(CODES.DIARY_RATE_LIMIT_REACHED)
    else:
        if diary.rate_count >= settings.DIARY_RATING_LIMIT:
            return gen(CODES.DIARY_RATE_LIMIT_REACHED)

    diary.rate_count += 1

    diary_old_tag_ids = [tag.id for tag in diary.tags]

    # 清除旧的数据
    with transaction.atomic():
        tag_ids = list(set(tag_ids))
        tags = TagService.get_tags_by_tag_ids(tag_ids)
        old_tags = list(filter(lambda x: x.tag_type == TAG_TYPE.CITY, diary.all_tags))
        tags += old_tags

        if tag_ids:
            diary.delete_all_tags()

        tag_names = []
        tag_ids = []
        has_city_tag = False
        for tag in tags:

            if tag.tag_type in (TAG_TYPE.COUNTRY, TAG_TYPE.PROVINCE):
                continue

            if tag.tag_type == TAG_TYPE.CITY:
                has_city_tag = True

            tag_ids.append(tag.id)
            tag_names.append(tag.name)

        if not has_city_tag and user.city_tag_id:
            tag_ids.append(user.city_tag_id)

        diary.add_tags(tag_ids=tag_ids)

    # 添加术前图
    if need_clear_pre_image:
        diary.pre_operation_image = cover
        set_pre_operation_images_v1(pre_operation_images, cover, diary)
    else:
        set_pre_operation_images(pre_operation_images, cover, diary)

    set_diary_cover(cover, diary)

    # diary.pre_operation_image = cover

    diary.last_modified = datetime.datetime.now()
    d = DiaryRankTool(diary.id)
    t = datetime.datetime.now()
    d.add_heat(DIARY_OPERATION.MODIFY_DIARY, t)
    diary.save()

    # 计算日记本tag修改后新增和删除的tag
    diary_diff_add_tag_ids = set(tag_ids) - set(diary_old_tag_ids)
    diary_diff_remove_tag_ids = set(diary_old_tag_ids) - set(tag_ids)

    if diary_diff_add_tag_ids:
        for topic in diary.topics.all():
            for tag_id in diary_diff_add_tag_ids:
                ProblemTag(problem=topic, tag_id=tag_id).save()

            if diary_diff_remove_tag_ids:
                topic.problemtag_set.filter(
                    tag_id__in=diary_diff_remove_tag_ids
                ).delete()
    return {'point_number': point_number}


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


@bind('diary/delete')
def delete_diary(diary_id):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    try:
        diary_id = int(diary_id)
        diary = Diary.objects.get(pk=diary_id, user_id=user.id)
    except:
        return gen(CODES.DIARY_NOT_FOUND)

    if diary.topic_num:
        return gen(CODES.DIARY_WONT_BE_DELETED)

    diary.is_online = False
    diary.save()

    diary.drafts.update(is_show=False)

    return {
        'has_deleted': True
    }


@bind_context('diary/sticky_post')
def sticky_post(ctx, diary_id):
    """
        帖子置顶, 取消置顶
    """
    user = get_current_user()
    doctor = get_doctor_from_context(ctx)
    if not user or not doctor:
        return gen(CODES.LOGIN_REQUIRED)
    try:
        diary = Diary.objects.get(id=diary_id)
        if diary and not diary.is_online:
            return gen(CODES.DIARY_HAS_BEEN_DELETED)
    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    if doctor.id != diary.doctor_id:
        return gen(CODES.NO_PERMISSION)
    Diary.objects.filter(doctor_id=doctor.id, sticky_post=True).update(sticky_post=False)

    if not diary.sticky_post:
        diary.sticky_post = True
    else:
        diary.sticky_post = False
    diary.save()
    return {'sticky_post': diary.sticky_post}


@bind_context('diary/stick_priority')
def sticky_priority(ctx, diary_id, stick_priority):
    """
    医生后台日记置顶 && 取消

    :param diary_id:  日记本id
    :params stick_priority:  置顶优先级 1-3 表示置顶; 0表示取消置顶
    """
    if not 0 <= stick_priority <= MAX_STICK_PRIORITY:
        return gen(CODES.DIARY_STICK_PRIORITY_UNSUPPORT)
    user = get_current_user()
    doctor = get_doctor_from_context(ctx)
    if not user or not doctor:
        return gen(CODES.LOGIN_REQUIRED)
    try:
        diary = Diary.objects.get(id=diary_id)
    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    # 权限验证
    allowed_services = ctx.rpc['doctor/get_merchant/service']().unwrap()
    allowed_services = allowed_services or []
    allowed_service_ids = [item['id'] for item in allowed_services]
    if diary.service_id not in allowed_service_ids:
        return gen(CODES.NO_PERMISSION)

    # 重复验证
    if stick_priority:
        same_priority_diarys = Diary.objects.filter(service_id=diary.service_id).\
                                filter(stick_priority=stick_priority).all()
        if same_priority_diarys:
            return gen(CODES.DIARY_STICK_PRIORITY_EXIST)
    diary.stick_priority = stick_priority or DEFAULT_STICK_PRIORITY
    diary.save()
    return gen(CODES.SUCCESS)

@bind('diary/sitemap')
def get_diarylist_sitemap(count=settings.BUILD_SITEMAP_COUNT):
    diarylist = Diary.objects.all().order_by('-last_modified')
    diarylist = diarylist[0:count]

    diary_info = []
    for diary in diarylist:
        diary_info.append({
            'id': diary.id,
            'last_modified': get_timestamp_or_none(diary.last_modified),
        })

    return diary_info


@bind_context('diary/get_all_image')
def get_all_diary_image(ctx, type, diary_id, start=0, count=10):
    user = get_user_from_context(ctx)

    try:
        d = Diary.objects.get(pk=diary_id)
    except:
        return gen(CODES.DIARY_NOT_FOUND)

    if user and d.user_id == user.id:
        need_watermark = False
    else:
        need_watermark = True

    post_cover_image = None
    if type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE:
        topic_ids = Problem.objects.filter(diary_id=diary_id, is_online=True).values_list('id', flat=True)
        images = TopicImage.objects.filter(topic_id__in=topic_ids).order_by(
            '-taken_time'
        ).values('image_url', 'is_cover', 'id')

        image_name = get_short_path(d.post_operation_image)
        post_cover_image = TopicImage.objects.filter(
            Q(cover_image_url=image_name) | Q(image_url=image_name), topic_id__in=topic_ids).values(
            'image_url', 'is_cover', 'id').first()
        if post_cover_image:
            post_cover_image['image_url'] = image_name
    else:
        images = PreOperationImage.objects.filter(
            diary_id=diary_id
        ).values('image_url', 'is_cover', 'id')

    images = images[start:start + count]

    temp = []
    for image in images:
        image['image_url'] = get_short_path(image['image_url'])
        temp.append(image)
    if not start:
        temp.insert(0, post_cover_image)

    new_temp = []
    id_temp = []
    for item in temp:
        if not item:
            continue
        if item['id'] in id_temp:
            continue
        new_temp.append(item)
        id_temp.append(item['id'])
    temp = new_temp

    new_images = []
    if need_watermark:
        images = temp
    else:
        _images = []
        for t in temp:
            _t = [t['image_url']]
            i = batch_move_watermark_pic_to_no_watermark(_t, 'wanmeizhensuo', 'wanmeizhensuo-tmp')
            _images += [{'image_url': i[0], 'is_cover': t['is_cover'], 'id': t['id']}]
        images = _images

    for image in images:
        if image and image['image_url']:
            if not need_watermark:
                new_image = get_temp_image_path(image['image_url'])
            else:
                new_image = get_full_path(image['image_url'], '-w')
            new_images.append({'url': new_image, 'image_name': image['image_url'], 'is_cover': image['is_cover'],
                               'id': image['id']})

    if type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE and d.post_operation_image:
        for i in range(len(new_images)):
            if post_cover_image and new_images[i]['id'] == post_cover_image['id']:
                new_images[i]['is_cover'] = True
            else:
                new_images[i]['is_cover'] = False

    has_cover = False
    for i in range(len(new_images)):
        if new_images[i]['is_cover']:
            if has_cover:
                new_images[i]['is_cover'] = False
            else:
                has_cover = True

    return new_images


@bind_context('diary/update_operation_info/v1')
def diary_operation_info_update_v1(
        ctx,
        diary_id,
        doctor_id=None,
        doctor_name=None,
        order_id=None,
        hospital_id=None,
        hospital_name=None,
        operation_timestamp=None,
        tag_ids=[],
        comment=None,
        price=0,
        rating=None,
        operation_effect_rating=None,
        doctor_attitude_rating=None,
        hospital_env_rating=None,
        pre_operation_images=[],
        cover=None,
        need_update_rate=False,
):

    return diary_operation_info_update_base(ctx,
                                            diary_id,
                                            doctor_id,
                                            doctor_name,
                                            order_id,
                                            hospital_id,
                                            hospital_name,
                                            operation_timestamp,
                                            tag_ids,
                                            comment,
                                            price,
                                            rating,
                                            operation_effect_rating,
                                            doctor_attitude_rating,
                                            hospital_env_rating,
                                            pre_operation_images,
                                            cover,
                                            need_update_rate,
                                            need_clear_pre_image=True)


@bind("diary/edit_city_tag")
def edit_diary_city_tag(diary_ids):
    """
    仅供hera后台使用
    批量修改日记本关联的地域标签
    :param diary_ids: 被修改的日记本id列表
    :return:
    """
    if diary_ids:
        try:
            diaries = Diary.objects.filter(id__in=diary_ids).iterator()
        except Diary.DoesNotExist:
            return gen(CODES.DIARY_NOT_FOUND)

        for diary in diaries:
            new_city_tag = _get_city_tag(diary)
            old_city_tag = [tag.id for tag in diary.all_tags if tag.tag_type == TAG_TYPE.CITY]
            _tag_logic_ratio(item=diary, new=new_city_tag, old=old_city_tag)

            if diary.topics.count() > 0:
                for topic in diary.topics.all():
                    topic_tag_ids = [tag.id for tag in topic.tags if tag.tag_type == TAG_TYPE.CITY]
                    _tag_logic_ratio(item=topic, new=new_city_tag, old=topic_tag_ids)


def _get_city_tag(diary):
    order = OrderService.get_order_info_by_id(diary.order_id)

    if order and order.get("city_tag_id"):
        return order.get("city_tag_id")

    service = GoodsService.get_diary_show_info_by_service_ids([diary.service_id, ])
    if service and service[diary.service_id]["city_tag_id"]:
        return service[diary.service_id]["city_tag_id"]

    user = UserService.get_user_by_user_id(diary.user_id)
    if user:
        return user.city_tag_id


def _tag_logic_ratio(item, new, old):
    try:
        if new:
            if old:
                if new in old and len(old) == 1:
                    pass
                else:
                    item.del_tags(list(set(old)))
                    item.add_tags([new, ])
            else:
                item.add_tags([new, ])
        else:
            if old:
                item.del_tags(list(set(old)))
    except Exception:
        logging_exception()


@bind("diary/get_diary_operation_album")
def get_diary_operation_album(diary_id, start_num=0, count=10, display_in_new_version=False):
    """
    v 7.6.45 新加 获取日记本的手术相册。
    :param diary_id: 日记本id
    :param start_num: 起始数
    :param count:  个数
    :param display_in_new_version: 是否是新版本展示逻辑
    :return: result {} 字典类型
    """
    result = {
        "pre_operation_album": [],  # 术前图
        "post_operation_album": [],  # 术后图
        "pre_operation_img_num": 0,  # 术前图片数量
    }
    try:
        diary = Diary.objects.get(pk=diary_id, is_online=True)
        # topic_count = diary.extra_info.topic_count if hasattr(diary, "extra_info") else 0  # TODO diary_extra 数据不一致，导致逻辑错误
        topic_count = diary.topic_num
    except Diary.DoesNotExist:
        return result
    need_watermark = True
    user = get_current_user()
    if user and diary.user_id == user.id:
        need_watermark = False
    result["need_watermark"] = need_watermark

    base_query = Q(diary_id=diary.id, flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True)

    diary_operation_time = diary.operation_time or diary.created_time  # 日记本的手术信息
    query = base_query & Q(operation_date__lte=diary_operation_time)
    pre_topic_ids = list(Problem.objects.filter(query).values_list("id", flat=True))

    if not start_num:  # 仅首次获取时，返回术前图。
        #  术前图
        pre_operation_images = list(PreOperationImage.objects.filter(diary_id=diary.id).values("image_url", "is_cover", "id"))
        if display_in_new_version:
            pre_topic_images = list(TopicImage.objects.filter(topic_id__in=pre_topic_ids).order_by("-id").values("image_url", "is_cover", "id"))
        else:
            pre_topic_images = []

        pre_image_list = []
        pre_image_list.extend(pre_operation_images)
        pre_image_list.extend(pre_topic_images)

        if pre_image_list and not start_num:
            # 修改术前图封面设置逻辑
            diary_pre_image_cover = diary.pre_operation_image
            for image_item in pre_image_list:
                image_item["is_cover"] = True if image_item.get("image_url", "") == diary_pre_image_cover else False

            result["pre_operation_img_num"] = len(pre_image_list)
            result["pre_operation_album"] = pre_image_list
            count -= 1

    #  术后图
    if topic_count:
        diary_operation_time = diary_operation_time.replace(hour=0, minute=0, second=0)
        topics = Problem.objects.filter(base_query).order_by("-operation_date", "-id").only("id", "operation_date")

        topic_ids = list(topics.values_list("id", flat=True))  # 用于检索术后图及生成第几篇日记
        post_operation_images = TopicImage.objects.filter(
            topic_id__in=topic_ids).order_by("-is_cover", "-id").values("id", "topic_id", "image_url", "is_cover")

        post_operation_images_dic = {}  # 图片预处理, {topic_id:[{},{}]}
        diary_post_image_cover = diary.post_operation_image

        for image in post_operation_images:
            topic_id = image.pop("topic_id", 0)
            if topic_id and topic_id not in post_operation_images_dic:
                post_operation_images_dic[topic_id] = []
            image["is_cover"] = True if image.get("image_url") == diary_post_image_cover else False

            post_operation_images_dic[topic_id].append(image)

        #  处理帖子
        pre_image_exists = PreOperationImage.objects.filter(diary_id=diary.id).exists() or \
                           TopicImage.objects.filter(topic_id__in=pre_topic_ids).exists()

        start_num = (start_num and pre_image_exists) and start_num - 1 or start_num
        for topic in topics[start_num: start_num + count]:
            post_operation_time = topic.operation_date
            day_num = (diary_operation_time and post_operation_time) and \
                      (post_operation_time - diary_operation_time).days or 0
            operation_after_days = 0 if day_num <= 0 else day_num  # 术后多少天
            diary_num = topic_count - topic_ids.index(topic.id)  # 第几篇日记
            default = {
                "operation_after_days": operation_after_days,
                "diary_num": diary_num,
                "images": sorted(post_operation_images_dic.get(topic.id, []), key=lambda x: x["id"]),  # 排序
            }
            result["post_operation_album"].append(default)

    return result


@bind_context("diary/create_v2")
def diary_create_v2(
        ctx,
        diary_id=0,
        title="",
        order_id=None,
        tag_ids=[],
        operation_timestamp=None,
        doctor_id=None,
        doctor_name=None,
        hospital_id=None,
        hospital_name=None,
):
    """
    创建日记本重构，新版逻辑。
    承接两个功能：1.创建日记本，2.对从草稿箱创建的日记本进行更新操作
    :param ctx:
    :param diary_id:
    :param title:
    :param order_id:
    :param tag_ids:
    :param operation_timestamp:
    :param doctor_id:
    :param doctor_name:
    :param hospital_id:
    :param hospital_name:
    :param service_id:
    :return:
    """
    user = get_user_from_context(ctx)
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    kw = {
        "user": user,
        "title": title,
        "tag_ids": tag_ids,
        "operation_timestamp": operation_timestamp,
        "order_id": order_id,
        "doctor_id": doctor_id,
        "doctor_name": doctor_name,
        "hospital_id": hospital_id,
        "hospital_name": hospital_name,
    }

    if diary_id:  # 如果有日记本ID 则走更新操作
        try:
            diary = Diary.objects.get(pk=diary_id, user_id=user.id)
            kw.update({
                "diary": diary,
            })
            diary = update_diary_operation_info_func(**kw)
        except Diary.DoesNotExist:
            return gen(CODES.DIARY_NOT_FOUND)
    else:
        kw.update({
            "new_version_create": True,
        })
        diary = common_func_for_create_diary(**kw)
    # 粉丝消息页面通知数据保存到db
    result = create_information_db.delay(
        user_id=user.id, source_type=ATTENTION_NOTIFY_TYPE.DIARY, business_id=diary.id,
    )
    info_logger.info('开始保存给粉丝的消息:{},日记本id:{}'.format(result.task_id, diary.id))

    result = {
        "diary_id": diary.id,
    }

    return result


@bind_context("diary/update_v2")
def diary_update_v2(
        ctx,
        diary_id,
        tag_ids=[],
        topic_list=[],
        device_id=None
):
    """
    日记本更新 --> 批量发帖
    :param ctx:
    :param diary_id:
    :param tag_ids:
    :param topic_list: 要发布的帖子列表数据
    :param device_id:
    :return:
    """
    user = get_user_from_context(ctx)
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        diary = Diary.objects.get(id=diary_id)

        if diary.user_id != user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    # 日记本的零点变身时间
    diary_operation_date = (lambda date_time: date_time.date())(diary.operation_time or diary.created_time)

    # 通过变身时间，划分出 术前帖 和 术后贴。主要是用于封面图的更新逻辑！！！
    for item in topic_list:
        item.update({
            "operation_timestamp": float(item.pop("operation_timestamp", 0)),  #时间戳转成 int 类型，防止下边比较报错!
            "pre_cover": "",  # 术前封面
            "post_cover": "",  # 术后封面
        })

    _topic_list = list(filter(lambda item: item.get("images", []), sorted(topic_list, key=lambda t: t.get("operation_timestamp", 0))))
    # 术前封面处理
    for pre_item in list(
            filter(lambda pre: datetime.date.fromtimestamp(pre.get("operation_timestamp", 0)) <= diary_operation_date,
                   _topic_list)):
        pre_index = topic_list.index(pre_item)  # 先获取原始索引
        pre_item["pre_cover"] = pre_item["images"][0].get("image", "")  # 给封面图字段赋值
        topic_list[pre_index] = pre_item
        break

    # 术后封面图处理
    for post_item in list(
            filter(lambda post: datetime.date.fromtimestamp(post.get("operation_timestamp", 0)) > diary_operation_date,
                   _topic_list))[::-1]:
        post_index = topic_list.index(post_item)  # 先获取原始索引
        post_item["post_cover"] = post_item["images"][0].get("image", "")  # 给封面图字段赋值
        topic_list[post_index] = post_item
        break

    result = create_topic_for_batch(
        user=user,
        diary=diary,
        topic_list=topic_list,
        tag_ids=tag_ids,
        device_id=device_id
    )

    return result


@bind_context('diary/update_operation_info/v2')
def diary_operation_info_update_v2(
        ctx,
        diary_id,
        title='',
        tag_ids=[],
        doctor_id=None,
        doctor_name=None,
        order_id=None,
        hospital_id=None,
        hospital_name=None,
        operation_timestamp=None,
        pre_cover='',
        post_cover='',
        only_update_order_info=False
):
    """
    新版本，编辑日记本信息接口。主要是去掉了术前图，增加了封面图的变更
    :param ctx:
    :param diary_id:
    :param title:
    :param tag_ids:
    :param doctor_id:
    :param doctor_name:
    :param order_id:
    :param hospital_id:
    :param hospital_name:
    :param operation_timestamp:
    :param pre_cover:
    :param post_cover:
    :param only_update_order_info:仅更新绑定订单的数据
    :return:
    """
    user = get_user_from_context(ctx)
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        diary = Diary.objects.get(id=diary_id)
        if diary and not diary.is_online:
            return gen(CODES.DIARY_HAS_BEEN_DELETED)

        if not diary.user_id == user.id:
            return gen(CODES.NO_PERMISSION)

    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)
    diary = update_diary_operation_info_func(
        user=user,
        diary=diary,
        title=title,
        tag_ids=tag_ids,
        doctor_id=doctor_id,
        doctor_name=doctor_name,
        order_id=order_id,
        hospital_id=hospital_id,
        hospital_name=hospital_name,
        operation_timestamp=operation_timestamp,
        pre_cover=pre_cover,
        post_cover=post_cover,
        only_update_order_info=only_update_order_info
    )
    return {"diary_id": diary.id}


def _check_image_url(image_dict):
    """
    图片地址处理
    :param image_dict:
    :return:
    """
    image_dict["image_url"] = get_short_path(image_dict.get("image_url", ""))
    return image_dict


def _get_images_list(image_list, offset=0, size=10):
    """
    获取过滤掉封面图的图片列表
    :param image_list:
    :param offset:
    :param size:
    :return:
    """
    def _convert_image(image):
        _ = image.pop("topic_id", 0)
        return image
    image_list = list(map(_check_image_url, map(_convert_image, image_list)))  # 处理图片字段
    return image_list[offset: offset + size]


def _get_topic_images(q, operation_time, image_type, cover, start_num=0, count=10):
    """
    获取帖子图片
    :param q: 筛选项
    :param operation_time: 日记本设置的治疗时间
    :param image_type: 图片类型
    :param cover: 封面 需要过滤
    :param start_num:
    :param count:
    :return:
    """
    query = q
    _operation_date, _id = "-topic__operation_date", "-id"

    if image_type == TOPIC_IMAGE_TYPE.PRE_OPERATION_IMAGE:  # 术前相册
        query &= Q(operation_date__lte=operation_time)
        _operation_date, _id = "topic__operation_date", "id"
    elif image_type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE:  # 术后相册
        query &= Q(operation_date__gt=operation_time)

    topic_ids = list(Problem.objects.filter(query).values_list("id", flat=True))

    topic_images = TopicImage.objects.filter(topic_id__in=topic_ids).exclude(image_url=cover).order_by(
        _operation_date, _id).values("topic_id", "image_url", "is_cover", "id")[start_num: start_num + count]

    return list(topic_images)


@bind("diary/get_diary_albums")
@listing()
def get_diary_pre_or_post_album(diary_id, image_type, start_num=0, count=10):
    """
    v 7.7.25 新增 获取日记本术前或术后相册 新逻辑

    ps: 之前版本 客户端需要图片id进行去除，这是因为有明确的图库返回数据。新版本的 术前图是由 术前图库 + 术前贴组成的。
        所以id丢回已然没用！但客户端需要这个 id 以保证对方逻辑的完整性。

        同时产品定义 术前 or 术后 相册 封面图置顶。但通过对应的图片地址查找其对应的库中数据，图片地址字段是没有索引的，故是个全文检索
        未避免这种情况，针对封面图字段，后端以 日记本ID 作为封面图的id 以满足客户端的的逻辑。王金龙已知晓。
    :param diary_id:
    :param image_type: 图片类型，术前 or 术后
    :param start_num:
    :param count:
    :return:
    """
    result = {
        "operation_image_list": [],
    }

    try:
        diary = Diary.objects.get(pk=diary_id, is_online=True)
    except Diary.DoesNotExist:
        return gen(CODES.DIARY_NOT_FOUND)

    diary_operation_time = diary.operation_time or diary.created_time  # 日记本的手术时间

    pre_operation_image_cover = get_short_path(diary.pre_operation_image or "")  # 术前封面图
    post_operation_image_cover = get_short_path(diary.post_operation_image or "")  # 术后封面图

    base_query = Q(diary_id=diary.id, is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL)
    operation_image_list = []
    raw_count = count

    # start_num and count reset
    if (image_type == TOPIC_IMAGE_TYPE.PRE_OPERATION_IMAGE and pre_operation_image_cover) or (
                    image_type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE and post_operation_image_cover):

        if not start_num:
            count -= 1  # 封面图置顶，所以会写入一条数据，所以补齐策略所取数量减一
        else:
            start_num -= 1  # 封面图置顶，会先写入一条数据，需要调整拉取数据的起始位置

    if image_type == TOPIC_IMAGE_TYPE.PRE_OPERATION_IMAGE:  # 术前相册

        if not start_num and pre_operation_image_cover:
            operation_image_list.append({"image_url": pre_operation_image_cover, "is_cover": True, "id": diary_id})

        pre_images = PreOperationImage.objects.filter(diary_id=diary.id).exclude(
            image_url=pre_operation_image_cover).values("image_url", "is_cover", "id")

        #术前图列表处理， 数据补齐 术前帖 补充术前图列表
        if pre_images.exists() and len(pre_images) > start_num:
            pre_images = pre_images[start_num: start_num + count]  # 多取一个用于封面图过滤
            operation_image_list.extend(_get_images_list(pre_images, size=count))

        # 术前图数据补齐策略
        if len(operation_image_list) < raw_count:
            if start_num < len(pre_images):
                _start_num = 0
            else:
                _start_num = start_num - len(pre_images)

            new_count = raw_count - len(operation_image_list)

            pre_topic_images = _get_topic_images(
                base_query,
                diary_operation_time,
                image_type,
                pre_operation_image_cover,
                start_num=_start_num,
                count=new_count
            )
            operation_image_list.extend(_get_images_list(pre_topic_images, size=new_count))

    elif image_type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE:  # 术后相册

        if not start_num and post_operation_image_cover:
            operation_image_list.append({"image_url": post_operation_image_cover, "is_cover": True, "id": diary_id})

        # 术后封面图补齐策略
        post_topic_images = _get_topic_images(
            base_query,
            diary_operation_time,
            image_type,
            post_operation_image_cover,
            start_num=start_num,
            count=count
        )
        operation_image_list.extend(_get_images_list(post_topic_images, size=count))

    # 封面图处理 封面置顶逻辑（没封面那都不是封面了……），之后不再会出现封面图！！！
    has_cover = False if not start_num else True
    for image in operation_image_list:
        if (image_type == TOPIC_IMAGE_TYPE.PRE_OPERATION_IMAGE and not pre_operation_image_cover) \
                or (image_type == TOPIC_IMAGE_TYPE.POST_OPERATION_IMAGE and not post_operation_image_cover) \
                or has_cover:
            image["is_cover"] = False

        if not start_num and image["is_cover"]:
            has_cover = True

    result["operation_image_list"] = operation_image_list

    return result


@bind_context("diary/batch_create_inner")
def diary_create_inner(ctx, data, services_tags):
    """
    批量创建日记
    :param ctx:
    :param data:
    :param services_tags:
    :return:
    """
    diary_list = []
    result = []
    error_result = []
    for index, obj in enumerate(data):
        content = obj.get('content')
        service_id = obj.get('service_id')
        if content and service_id:
            diary_list.append(obj)
            result.append(index)
        else:
            error_result.append(index)
    create_diary(diary_list, services_tags)

    return result, error_result
