# coding=utf8
# 只做老接口兼容
from __future__ import absolute_import, unicode_literals

from gm_types.whisper import MESSAGE_SOURCE
from six import string_types

import json
import time

from datetime import datetime, timedelta
import dateutil.parser as parser
from rpc.tool.log_tool import info_logger
from django.conf import settings
from django.db.models import Q
from gm_types.msg import CONVERSATION_TYPE, CONVERSATION_ORDER
from gm_types.gaia import MESSAGE_TYPE

from api.tool.image_utils import get_full_path, get_thumb_path
from rpc.decorators import bind_context, bind
from rpc.tool.error_code import gen, CODES
from rpc.exceptions import RPCPermanentError, RPCLoginRequiredException, RPCNotFoundException

from api.models.message import SPECIAL_MSG_TYPE
from hippo.models import Hospital
from api.models.message import ConversationUserStatus, Conversation, Message, MessageBlackUser
from api.tool.user_tool import get_user_from_context, filter_user_nick_name, get_portrait_by_user
from api.tool.user_tool import get_doctor_by_user_id
from api.tool.user_tool import get_user_by_id
from api.models.types import CONVERSATION_STATUS, MESSAGE_ORDER_TYPE, DOCTOR_TYPE, USER_TYPE
from api.models.doctor import Doctor
from api.models.user import User
from api.models.person import Person

import message.signals as message_signals
from message.utils.db import read_msg_from_es, read_conversation_extra_from_es
from message.utils.message import send_one_message
from message.signal_handlers import sync_conversation
from message.utils.common import get_jim_login_info, register_jim
from rpc.tool.log_tool import doctor_unread_logger, logging_exception, current_trace_id
from rpc.context import get_rpc_remote_invoker
from search.utils.conversation import search_conversation_es_compatable, search_conversation_from_es
from services.unread.noti_unread import noti_operation
from talos.models.topic import Activity

from utils.decorators import keyword_argument_only
from utils.text import fmath_all
from services.unread.stat import DoctorUnread
from services.unread.stat import UserUnread


@bind_context('message/conversation/list')
def conversation_list(ctx, offset=0, size=10, order_by=MESSAGE_ORDER_TYPE.LAST_REPLY_TIME, read_status=None):
    # NOTE @pengfeix
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    conversation_status = ConversationUserStatus.objects.filter(is_empty=False, user_id=user.id)
    if read_status is not None:
        conversation_status = conversation_status.filter(read_status=read_status)

    conversation_status = conversation_status.select_related('conversation')
    if order_by == MESSAGE_ORDER_TYPE.LAST_REPLY_TIME:
        conversation_status = conversation_status.order_by('-last_reply_time')
    elif order_by == MESSAGE_ORDER_TYPE.READ_STATUS:
        conversation_status = conversation_status.order_by('-read_status', '-last_reply_time')

    total_count = conversation_status.count()
    conversation_status = conversation_status[offset:offset + size]
    # conversation_info in database
    conversation_list = [cs.conversation.conversation_info(user, cs) for cs in conversation_status]
    # conversation_extra in es
    conversation_extra = read_conversation_extra_from_es([cs.conversation_id for cs in conversation_status])
    for c in conversation_list:
        if c['id'] in conversation_extra:
            c.update(conversation_extra[c['id']])
        # 初始化未读数, 忽略客服的
        default = 1 if c['is_new'] else 0
        c['unread_num'] = default
        if c.get('conversation_type') == CONVERSATION_TYPE.MESSAGE:
            c['unread_num'] = UserUnread(user.id).get_conversation_unread(c['id'], default=default)

    # filter out items with empty user_ids
    conversation_list = [c for c in conversation_list if len(c['user_ids']) > 1]

    result = {
        'conversation_list': conversation_list,
        'total_count': total_count,
    }

    return result


def _conversation_msgs(conversation, send_user,
                       set_read_status=True, offset_msg_id=None, last_msg_id=None, is_slice=False,
                       msg_id_only=False):
    # 设置为已读
    # 医生后台在获取私信详情的时候不需要设置为已读
    if set_read_status:
        assert send_user is not None
        conversation.user_status_set.filter(user_id=send_user.id).update(
            status=CONVERSATION_STATUS.OLD,
            read_status=False
        )
        message_signals.post_touch_conversation(send_user, conversation)

    # 获取消息id列表
    # IMPORTANT: here we assume the primary key of messages is a AutoIncrement field
    msg_ids = conversation.message_set.order_by('-pk')
    if offset_msg_id is not None:
        msg_ids = msg_ids.filter(pk__lt=offset_msg_id)
    if last_msg_id is not None:
        msg_ids = msg_ids.filter(pk__gt=last_msg_id)

    # offset和last都传时is_slice无效
    if (offset_msg_id is None or last_msg_id is None) and is_slice:
        msg_ids = msg_ids[:Message.MSG_PER_PAGE]
    else:
        msg_ids = msg_ids.all()

    if msg_id_only:
        result = {
            'msgs': [{'id': m.id, 'send_user_id': m.user_id} for m in msg_ids],
        }
    else:
        msg_ids = msg_ids.values_list('id', flat=True)
        msgs = _mget_msgs(msg_ids, conversation.user_ids(), resort=True)
        result = {
            'msgs': msgs,
        }

    result['conversation_id'] = conversation.id
    result['conversation_info'] = {
        'user_ids': conversation.user_ids(),
    }
    return result


@bind_context('message/conversation/delta_msgs')
def conversation_delta_msgs(ctx, person_ids=None, conversation_id=None, last_msg_id=None):
    """
    :param person_ids: tuples of person_id
    :param conversation_id:
    :param last_msg_id: 从这条会话id开始获取
    """
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    if conversation_id is not None:
        conversation = Conversation.objects.get(id=conversation_id)
    elif person_ids is not None:
        if len(person_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        try:
            uid1 = Person.objects.get(pk=person_ids[0]).user_id
            uid2 = Person.objects.get(pk=person_ids[1]).user_id
            conversation = Conversation.objects.get_conversation(uid1, uid2)
        except Person.DoesNotExist:
            gen(CODES.MESSAGE_UNKNOWN_USER)
    else:
        raise RPCPermanentError('invalid arguments')

    if not ConversationUserStatus.objects.filter(
        conversation_id=conversation.id,
        user_id=user.id
    ).exists():
        gen(CODES.MESSAGE_NO_PERMISSION)
    return _conversation_msgs(conversation=conversation, send_user=user,
                              last_msg_id=last_msg_id, is_slice=True)


@bind_context('message/conversation/msgs')
def conversation_msgs(ctx, person_ids=None, user_ids=None, conversation_id=None,
                      ascle=False, offset_msg_id=None, last_msg_id=None, is_slice=False,
                      from_user_refer=None, business_id=None):
    """
    :param person_ids: tuples of person_id
    :param user_ids: tuples of user_id
    :param offset_msg_id: 该对话从这条消息(不包含)往更早一页的消息, None表示从最新一条开始
    :param is_slice: 是否分页, 新版统一使用分页

    :param from_user_refer from where from_user_id requests these messages, added at 7.4.5
    :param business_id service/doctor/hospital id etc. added at 7.4.5
    """

    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    if conversation_id is not None:
        conversation = Conversation.objects.get(id=conversation_id)

    elif person_ids is not None:
        if len(person_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        try:
            uid1 = Person.objects.get(pk=person_ids[0]).user_id
            uid2 = Person.objects.get(pk=person_ids[1]).user_id
            conversation = Conversation.objects.get_conversation(uid1, uid2)
        except Person.DoesNotExist:
            gen(CODES.MESSAGE_UNKNOWN_USER)
    elif user_ids is not None:
        if len(user_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        conversation = Conversation.objects.get_conversation(*user_ids)
    else:
        raise RPCPermanentError('invalid arguments')

    if not ConversationUserStatus.objects.filter(
        conversation_id=conversation.id,
        user_id=user.id
    ).exists():
        gen(CODES.MESSAGE_NO_PERMISSION)

    # update refer
    if from_user_refer:
        from_user_status = conversation.user_status_set.get(user_id=user.id)
        if from_user_status.refer != from_user_refer or from_user_status.business_id != business_id:
            from_user_status.refer = from_user_refer
            from_user_status.business_id = business_id
            from_user_status.save()

    # add 医生版实时小红点数量
    # 只要医生有未读的就发出通知 read_status True的时候是未读......
    conversation_num = UserUnread(user.id).get_conversation_unread(conversation.id)
    if conversation_num and int(conversation_num) > 0:
        from services.notify import notify
        uids_1, uids_2 = conversation.user_ids()
        notify("conversation/clear", user_id=user.id, data={
            'conversation_id': conversation.id,
            'send_uid': user.id,
            'send_nickname': user.last_name,
            'target_uid': uids_2 if uids_1 == user.id else uids_1,
        })

    return _conversation_msgs(conversation=conversation, send_user=user, set_read_status=not ascle,
                              offset_msg_id=offset_msg_id, last_msg_id=last_msg_id, is_slice=is_slice)


@bind_context('message/conversation/msgs_v2')
def conversation_msgs_v2(
    ctx, uid, person_ids=None, user_ids=None, conversation_id=None,
    ascle=False, offset_msg_id=None, last_msg_id=None, is_slice=False,
    from_user_refer=None, business_id=None
):
    """
    :param person_ids: tuples of person_id
    :param user_ids: tuples of user_id
    :param offset_msg_id: 该对话从这条消息(不包含)往更早一页的消息, None表示从最新一条开始
    :param is_slice: 是否分页, 新版统一使用分页

    :param from_user_refer from where from_user_id requests these messages, added at 7.4.5
    :param business_id service/doctor/hospital id etc. added at 7.4.5
    """

    try:
        user = User.objects.get(pk=uid)
    except User.DoesNotExist:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    if conversation_id is not None:
        conversation = Conversation.objects.get(id=conversation_id)

    elif person_ids is not None:
        if len(person_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        try:
            uid1 = Person.objects.get(pk=person_ids[0]).user_id
            uid2 = Person.objects.get(pk=person_ids[1]).user_id
            conversation = Conversation.objects.get_conversation(uid1, uid2)
        except Person.DoesNotExist:
            gen(CODES.MESSAGE_UNKNOWN_USER)
    elif user_ids is not None:
        if len(user_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        conversation = Conversation.objects.get_conversation(*user_ids)
    else:
        raise RPCPermanentError('invalid arguments')

    if not ConversationUserStatus.objects.filter(
            conversation_id=conversation.id,
            user_id=user.id
    ).exists():
        gen(CODES.MESSAGE_NO_PERMISSION)

    # update refer
    if from_user_refer:
        from_user_status = conversation.user_status_set.get(user_id=user.id)
        if from_user_status.refer != from_user_refer or from_user_status.business_id != business_id:
            from_user_status.refer = from_user_refer
            from_user_status.business_id = business_id
            from_user_status.save()

    # add 医生版实时小红点数量
    # 只要医生有未读的就发出通知 read_status True的时候是未读......
    conversation_num = UserUnread(user.id).get_conversation_unread(conversation.id)
    if conversation_num and int(conversation_num) > 0:
        from services.notify import notify
        uids_1, uids_2 = conversation.user_ids()
        notify("conversation/clear", user_id=user.id, data={
            'conversation_id': conversation.id,
            'send_uid': user.id,
            'send_nickname': user.last_name,
            'target_uid': uids_2 if uids_1 == user.id else uids_1,
        })

    info = _conversation_msgs(conversation=conversation, send_user=user, set_read_status=not ascle,
                              offset_msg_id=offset_msg_id, last_msg_id=last_msg_id, is_slice=is_slice)

    conv_status = ConversationUserStatus.objects.get(conversation=conversation, user_id=user.id)
    info['conversation_info'].update(
        {
            'is_star': conv_status.is_star,
            'doctor_id': doctor.id
        }
    )
    return info


@bind('message/conversation/msgs/internal')
def conv_msgs_intrnl(user_ids, conversation_id=None, offset_msg_id=None, last_msg_id=None,
                     is_slice=True, msg_id_only=False):
    if conversation_id is not None:
        conversation = Conversation.objects.get(id=conversation_id)
    elif user_ids is not None:
        if len(user_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        conversation = Conversation.objects.get_conversation(*user_ids)
    else:
        raise RPCPermanentError('invalid arguments')

    return _conversation_msgs(conversation=conversation, send_user=None, set_read_status=False,
                              offset_msg_id=offset_msg_id, last_msg_id=last_msg_id, is_slice=is_slice,
                              msg_id_only=msg_id_only)


@bind('message/conversation/exists')
def conversation_exists(user_ids, conversation_id=None):
    if conversation_id is not None:
        is_exists = Conversation.objects.filter(id=conversation_id).exists()
    elif user_ids is not None:
        if len(user_ids) != 2:
            gen(CODES.UNKNOWN_ERROR)
        is_exists = Conversation.objects.conversation_exists(*user_ids)
    else:
        raise RPCPermanentError('invalid arguments')
    return {
        "is_exists": is_exists
    }


@bind_context('message/conversation/create')
def conversation_create(ctx, target_doctor_id=None):
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)
    try:
        target_doctor = Doctor.objects.get(pk=target_doctor_id)
        target_user = target_doctor.user
    except Doctor.DoesNotExist:
        gen(CODES.MESSAGE_INVALID_TARGET_USER)
    if not target_user:
        gen(CODES.MESSAGE_INVALID_TARGET_USER)

    conversation = Conversation.objects.get_conversation(user.id, target_user.id)
    result = {
        'user_ids': conversation.user_ids(),
        'conversation_id': conversation.id,
    }
    return result


@bind_context('message/message/send', trace_logging=True)
def message_send(ctx, conversation_id=None, target_person_id=None, target_doctor_id=None, target_user_id=None,
                 msg_type=MESSAGE_TYPE.TEXT, content=None, ascle=False, is_system=0, for_coupon=False, kyc_type=-1,
                 send_user_id=None):
    """旧的消息发送接口, 新服务请勿使用
    :param request: send_user_id 发送用户id
    """
    user = get_user_from_context(ctx)
    if send_user_id:
        try:
            user = User.objects.get(pk=send_user_id)
        except:
            user = None
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    # 检测刷评论问题: 当用户 一天内 连续发出相同内容评论3条，即停止对方发帖功能24小时
    # 停止后，客户端发帖时弹alert“发帖失败”
    if (not for_coupon and not send_user_id):  # 美券消息的兼容处理，防止因一天多次领取被拉黑  测肤更美小秘书私信指定send_user_id打招呼
        before24hour = datetime.now() - timedelta(days=1)
        if MessageBlackUser.objects.filter(user_id=user.id, created_time__gte=before24hour):
            gen(CODES.MESSAGE_DOUBTED_CONVERSATION)

        txt = content['text']
        if msg_type == MESSAGE_TYPE.TEXT and len(txt) > 6:
            messages = Message.objects.filter(
                user_id=user.id, type=MESSAGE_TYPE.TEXT,
                send_time__gte=before24hour,
            ).values_list("content", flat=True).order_by("-send_time")[:10]

            if len(messages) == 10 and fmath_all(txt, messages, 0.9):
                # 记录可疑用户
                MessageBlackUser.objects.create(user_id=user.id, content=txt)

    # 获取收信用户
    try:
        if target_person_id is not None:
            target_user_id = Person.objects.get(pk=target_person_id).user_id
        elif target_user_id is not None:
            pass
        elif target_doctor_id is not None:
            target_user_id = Doctor.objects.get(pk=target_doctor_id).user.id
        elif conversation_id is None:
            gen(CODES.MESSAGE_INVALID_TARGET_USER)
    except (Person.DoesNotExist, Doctor.DoesNotExist):
        gen(CODES.MESSAGE_INVALID_TARGET_USER)

    try:
        doctor = Doctor.objects.only('id', 'doctor_type').get(user_id=target_user_id)
        doctor_id = doctor.id
        target_user_type = USER_TYPE.EXPERT if doctor.doctor_type == DOCTOR_TYPE.DOCTOR else USER_TYPE.OFFICER
    except Doctor.DoesNotExist:
        doctor_id = None
        target_user_type = USER_TYPE.NORMAL

    if conversation_id:
        res = internal_message_send(
            sender_user_id=user.id,
            conversation_id=conversation_id,
            conversation_type=CONVERSATION_TYPE.MESSAGE,
            msg_type=msg_type,
            content=content,
            push_stat_labels={'event_type': 'push', 'event': 'received_private_msg'},
            is_system=is_system,
            target_user_type=target_user_type,
            kyc_type=kyc_type
        )
    else:
        res = internal_message_send(
            sender_user_id=user.id,
            target_user_id=target_user_id,
            conversation_type=CONVERSATION_TYPE.MESSAGE,
            msg_type=msg_type,
            content=content,
            push_stat_labels={'event_type': 'push', 'event': 'received_private_msg'},
            is_system=is_system,
            target_user_type=target_user_type,
            kyc_type=kyc_type
        )

    if res['error'] == CODES.MESSAGE_INVALID_USER:
        ctx.session.do_logout()

    if res['error'] != CODES.SUCCESS:
        gen(res['error'])

    # 异步记录异常私信监控日志
    try:
        need_monitor = (  # 普通用户 --> 医生建立会话的第一条文本消息
            target_user_id and
            target_user_type != USER_TYPE.NORMAL and
            msg_type == MESSAGE_TYPE.TEXT and
            not is_system
        )
        if need_monitor:
            from message.tasks import async_write_message_monitor_log
            async_write_message_monitor_log(
                msg_id=res['msg_id'],
                conversation_id=res["conversation"]["id"],
                send_uid=user.id,
                target_uid=target_user_id,
                doctor_id=doctor_id,
                target_type=target_user_type,
                msg_type=msg_type
            )
    except Exception:  # 忽略可能的异常
        logging_exception()

    netease_signal_data = {}

    if doctor_id:
        # 网易云信目前只有针对医生的才进行信令通知
        try:
            netease_signal_info = res.get('netease_signal_info', {})
            target_user_id = netease_signal_info.pop('target_uid', None)
            if target_user_id:
                netease_account_list = get_rpc_remote_invoker()[
                    "diagnosis/consultation_counsel/get_or_create_neteaseim_account_by_user_ids"](
                    user_ids=[target_user_id]
                ).unwrap()

                info = netease_account_list[0]

                netease_signal_data = {
                    "netease_signal_info": netease_signal_info,
                    "target_netease_account_id": info.get("account_id", "")
                }
        except Exception:  # 忽略可能的异常
            logging_exception()

    return {
        'msg_id': res['msg'],
        'msg': res['msg'],
        'doctor_id': doctor_id,
        'target_user_type': target_user_type,
        'netease_signal_data': netease_signal_data,
    }


@bind('message/suozhang/send')
def suozhang_send(conversation_id=None, target_doctor_id=None, target_user_id=None,
                  content='', ascle=False, filter_word=False, msg_type=MESSAGE_TYPE.TEXT,
                  sender_user_id=settings.BOSS, sender_doctor_id=None, kyc_type=-1,
                  need_jpush=True):
    """所长发私信接口
    这个接口设计比较奇怪, 请尽量不要使用
    """
    if sender_doctor_id:
        user = Doctor.objects.get(pk=sender_doctor_id).user
    else:
        user = User.objects.get(id=sender_user_id)
    # 获取收信用户
    if target_user_id is not None:
        pass
    elif target_doctor_id is not None:
        target_user_id = Doctor.objects.get(pk=target_doctor_id).user.id
    elif conversation_id is None:
        gen(CODES.MESSAGE_INVALID_TARGET_USER)

    if msg_type==MESSAGE_TYPE.TEXT:
        content = {'text': content}

    if conversation_id:
        res = internal_message_send(
            sender_user_id=user.id,
            conversation_id=conversation_id,
            conversation_type=CONVERSATION_TYPE.MESSAGE,
            msg_type=msg_type,
            content=content,
            need_jpush=need_jpush,
            filter_word=filter_word,
            kyc_type=kyc_type,
        )
    else:
        res = internal_message_send(
            sender_user_id=user.id,
            target_user_id=target_user_id,
            conversation_type=CONVERSATION_TYPE.MESSAGE,
            msg_type=msg_type,
            content=content,
            filter_word=filter_word,
            kyc_type=kyc_type,
            need_jpush=need_jpush,
        )

    if res['error'] != CODES.SUCCESS:
        gen(res['error'])

    return {
        'msg_id': res['msg'],
        'msg': res['msg'],
    }


@bind('message/msg/send')
def api_internal_message_send(sender_person_id=None, target_person_id=None,
                              sender_user_id=None, target_user_id=None,
                              conversation_id=None,
                              conversation_type=CONVERSATION_TYPE.MESSAGE,
                              msg_type=MESSAGE_TYPE.TEXT,
                              content=None,
                              need_jpush=True, silent=False, push_stat_labels=None):
    """just a proxy to internal_message_send
    """
    if (sender_person_id is not None) and (target_person_id is not None):
        try:
            sender_user_id = Person.objects.get(pk=sender_person_id).user_id
            target_user_id = Person.objects.get(pk=target_person_id).user_id
        except Person.DoesNotExist:
            gen(CODES.MESSAGE_INVALID_TARGET_USER)

    res = internal_message_send(sender_user_id=sender_user_id, target_user_id=target_user_id,
                                conversation_id=conversation_id,
                                conversation_type=conversation_type,
                                msg_type=msg_type,
                                content=content,
                                need_jpush=need_jpush,
                                push_stat_labels=push_stat_labels
                                )

    if res['error'] == CODES.MESSAGE_CONTENT_FILTERED_OUT:
        return {
            'sensitive_words': res.get("sensitive_words"),
            'error': res['error'],
            'message': CODES.getDesc(CODES.MESSAGE_CONTENT_FILTERED_OUT)
        }

    if res['error'] != CODES.SUCCESS:
        gen(res['error'])

    return {
        'msg_id': res['msg_id'],
        'msg': res['msg']
    }


@keyword_argument_only
def internal_message_send(
        sender_user_id, target_user_id=None,
        msg_type=MESSAGE_TYPE.TEXT,
        content=None,
        conversation_id=None,
        conversation_type=CONVERSATION_TYPE.MESSAGE,
        need_jpush=True,
        push_stat_labels=None,
        silent=False,
        filter_word=True,
        is_system=0,
        target_user_type=None,
        kyc_type=-1,
        source=MESSAGE_SOURCE.UNKNOWN
):
    """
    内部发私信接口

    :param sender_user_id: 发送者uid
    :param target_user_id: 接收者uid
    :param msg_type:
    :param content:
    :param conversation_id: 会话id
    :param conversation_type:
    :param need_jpush: 是否需要jpush推送
    :param is_system: 是否是系统发送
    :param kyc_type: kyc入口私信
    :return:
    """
    try:
        user = User.objects.get(pk=sender_user_id)
    except User.DoesNotExist:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    if target_user_id is None and conversation_id is None:  # 不能同时为None
        gen(CODES.MESSAGE_INVALID_TARGET_USER)
    if sender_user_id and target_user_id and str(sender_user_id) == str(target_user_id):  # 发送者和接收者不可以是同一个
        gen(CODES.MESSAGE_INVALID_TARGET_USER)

    # use conversation_id
    if conversation_id is not None:
        conversation = Conversation.objects.get(pk=conversation_id)
        target_user = conversation.get_target_user(user)
        # 若是conversation_type 和 send_uid.person_id 不对应则抛出异常
        # 医生端不能给医生发私信
        if conversation_type == CONVERSATION_TYPE.MESSAGE and target_user:
            pass
            # if target_user.person.id.hex in (settings.KEFU_PERSION_ID, settings.CALL_CENTER_CONSULTANT_PERSON_ID):
            #     gen(CODES.MESSAGE_INVALID_TARGET_USER)

        res = send_one_message(
            send_user=user,
            conversation=conversation,
            conversation_type=conversation_type,
            msg_type=msg_type,
            content=content,
            need_jpush=need_jpush,
            push_stat_labels=push_stat_labels,
            silent=False,
            filter_word=filter_word,
            is_system=is_system,
            kyc_type=kyc_type,
            source=source,
        )
    # use target_user_id
    else:
        try:
            target_user = User.objects.get(pk=target_user_id)
        except User.DoesNotExist:
            gen(CODES.MESSAGE_INVALID_TARGET_USER)

        if conversation_type == CONVERSATION_TYPE.MESSAGE:
            pass
            # if target_user.person.id.hex in (settings.KEFU_PERSION_ID, settings.CALL_CENTER_CONSULTANT_PERSON_ID):
            #     gen(CODES.MESSAGE_INVALID_TARGET_USER)

        res = send_one_message(
            send_user=user,
            target_user=target_user,
            conversation_type=conversation_type,
            msg_type=msg_type,
            content=content,
            need_jpush=need_jpush,
            push_stat_labels=push_stat_labels,
            silent=False,
            filter_word=filter_word,
            is_system=is_system,
            kyc_type=kyc_type,
            source=source,
        )
        conversation = res['conversation']

        # 首次给医生发送私信，自动回复消息 (removed)

    if res['error'] == CODES.MESSAGE_CONTENT_FILTERED_OUT:
        return {'error': res['error'], 'sensitive_words': res.get("sensitive_words")}

    if res['error'] != CODES.SUCCESS:
        return {'error': res['error']}

    else:
        msg_detail = Message.msg_info(res['msg'], conversation.user_ids())
        netease_signal_info = {}

        if conversation_type == CONVERSATION_TYPE.MESSAGE:
            # 仅当普通私信才统计未读消息，【客服】相关不在医生端展示
            from services.notify import notify
            msg_instance = res['message']
            send_message_info = msg_instance.to_dict(with_content=False)
            sender_doctor_instance = get_doctor_by_user_id(user.id)
            if sender_doctor_instance:
                _sender_portrait = sender_doctor_instance.portrait
            else:
                _sender_portrait = get_portrait_by_user(user)
            data = {
                'conversation_id': conversation.id,
                'send_uid': user.id,
                'send_nickname': user.last_name,
                'sender_portrait': _sender_portrait,
                'send_time': send_message_info['send_time'],
                'target_uid': target_user_id,
                'conversation_type': conversation_type,
                'msg_id': res['msg_id'],
                'content': msg_detail['content'],
                'content_display': conversation.last_msg_content_disp
            }

            # 网易云信信令需要传的数据
            netease_signal_info = {
                "operation": "add",
                "event": "conversation",
                "trace_id": current_trace_id(),
                "content": {
                    "content": {
                        # 这部分数据不需要
                    },
                    'conversation_id': conversation.id,
                    'msg_id': res['msg_id'],
                    'send_nickname': user.last_name,
                },
                'target_uid': target_user_id, # 这个是
            }

            # 应该是通知target_user未读数目发生改变
            # notify('conversation/add', user_id=target_user.id, data=data)
            # celery redis 会有任务丢失， 如果换了rabbitmq之后可以切成异步
            notify_info = u'Notify: event:<{}>, operation:<{}>, user_id:<{}>, doctor_id:<{}>, data:<{}>'.format(
                'conversation', 'add', target_user.id, None, json.dumps(data) if data else ''
            )
            doctor_unread_logger.info(notify_info)
            noti_operation('conversation', 'add', user_id=target_user.id, data=data)

        return {
            'conversation': {
                'id': conversation.id,
            },
            'error': res['error'],
            'msg_id': res['msg_id'],
            'msg': msg_detail,
            'netease_signal_info': netease_signal_info,
        }


@bind('message/msg/mget')
def message_mget(msg_ids, same_conversation=True):
    """性能考虑, 目前只允许批量获取单个会话的消息
    """
    if not msg_ids:
        return {'msgs': []}

    assert same_conversation
    first_msg = Message.objects.get(pk=msg_ids[0])
    conversation = first_msg.conversation

    user_ids = conversation.user_ids()
    msgs = _mget_msgs(msg_ids, user_ids)
    result = {
        'msgs': msgs,
        'conversation_info': {
            'user_ids': user_ids,
            'id': conversation.id
        }
    }

    return result


def _mget_msgs(msg_ids, user_ids=None, resort=False):
    """
    :param resort: 按msg_id从小到大排序
    """
    if resort:
        msg_ids = sorted(msg_ids)
    msgs = read_msg_from_es(msg_ids)
    if user_ids:
        msgs = [Message.msg_info(m, user_ids) for m in msgs]
    else:
        # TODO: support msgs which are not in the same conversation
        assert False
    return msgs


@bind_context('message/conversation/sink')
def conversation_sink(ctx, conversation_id):
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    try:
        conv_status = ConversationUserStatus.objects.get(conversation_id=conversation_id, user_id=user.id)
        # 写两个字段
        conv_status.status = CONVERSATION_STATUS.OLD
        conv_status.read_status = False
        conv_status.save()
        sync_conversation(Conversation.objects.get(pk=conversation_id))

        conversation = conv_status.conversation
        uids_1, uids_2 = conversation.user_ids()
        from services.notify import notify
        notify("conversation/clear", user_id=user.id, data={
            'conversation_id': conversation.id,
            'send_uid': user.id,
            'send_nickname': user.last_name,
            'target_uid': uids_2 if uids_1 == user.id else uids_1,
        })
    except ConversationUserStatus.DoesNotExist:
        gen(CODES.MESSAGE_INVALID_CONVERSATION)


@bind_context('message/conversation/unread')
def conversation_unread_list(ctx, offset=0, size=10):
    """
    未读会话数
    :param ctx:
    :param offset:
    :param size:
    :return:
    """
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    conversation_status = ConversationUserStatus.objects.filter(is_empty=False, user_id=user.id)
    conversation_status = conversation_status.filter(read_status=True)

    total_count = conversation_status.count()

    result = {
        'total_count': total_count,
    }

    return result


@bind_context('message/conversation/can_send')
def check_can_send_message(ctx, target_uid):
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    target_user = get_user_by_id(target_uid)
    if not target_user:
        raise RPCNotFoundException
    target_doctor = get_doctor_by_user_id(target_user.id)

    if doctor or target_doctor:
        return {'can_send': True}

    user_follow = user.fans.filter(follow=target_user, bond=True).exists()
    target_follow = target_user.fans.filter(follow=user, bond=True).exists()

    if user_follow and target_follow:
        return {'can_send': True}
    if target_user.person.id.hex == settings.KEFU_PERSION_ID:
        return {'can_send': True}

    return {'can_send': False}


@bind_context('message/conversation/detail')
def get_conversation_detail(ctx, conversation_id=None, send_uid=None, target_uid=None):
    """
    [DEPRECATED]获取当个会话的基本信息, 主要用于判断会话是否存在
    """
    user = get_user_from_context(ctx)
    if not user:
        gen(CODES.MESSAGE_UNKNOWN_USER)

    conversation = None
    if conversation_id is not None:
        conversation = Conversation.objects.get(id=conversation_id)
    elif send_uid and target_uid:
        user_ids = [send_uid, target_uid]
        conversation_query = Conversation.objects.filter(uid_hash=Conversation.gen_uid_hash(user_ids))
        if conversation_query.exists():
            conversation = conversation_query[0]
    else:
        gen(CODES.MESSAGE_NO_PERMISSION)

    if not conversation or not conversation.user.filter(id=user.id).exists():
        gen(CODES.MESSAGE_NO_PERMISSION)

    return conversation.conversation_info(user=user)


@bind('message/conversation/detail_v2')
def get_conversation_detail_v2(user_id, conversation_id=None, user_ids=None):
    if conversation_id is not None:
        conversation = Conversation.objects.filter(id=conversation_id).first()
    elif user_ids is not None:
        conversation, created = Conversation.objects.get_or_create_conversation(*user_ids)
    else:
        raise RPCPermanentError
    if not conversation:
        raise RPCNotFoundException
    cus = ConversationUserStatus.objects.filter(conversation_id=conversation.id, user_id=user_id).first()
    if not cus:
        raise RPCNotFoundException
    return conversation.conversation_info_v2(cus)


@bind('message/create_conversation')
def get_conversation_detail_v2(user_id, target_user_id):
    if not user_id or not target_user_id:
        raise RPCPermanentError

    users = User.objects.filter(id__in=[user_id, target_user_id])
    if not len(users) == 2:
        raise RPCPermanentError

    conversation = Conversation.objects.get_conversation(
        user_id, target_user_id,
        conversation_type=CONVERSATION_TYPE.MESSAGE)

    cus = ConversationUserStatus.objects.filter(conversation_id=conversation.id,
                                                user_id=user_id).first()
    if not cus:
        raise RPCNotFoundException
    return conversation.conversation_info_v2(cus)


# @bind_context('message/conversation/get_partner_refer', login_required=True)
def get_conversation_partner_refer(conversation_id, doctor_id):
    user = Doctor.objects.get(id=doctor_id).user

    cus = ConversationUserStatus.objects.filter(conversation_id=conversation_id).exclude(user_id=user.id)
    assert len(cus) == 1

    partner = cus[0]
    return {
        'partner_refer': partner.refer,
        'partner_business_id': partner.business_id,
    }


@bind_context('message/conversation/makestar', login_required=True)
def makestart_conversation(ctx, conversation_id, doctor_user_id):
    """
    设置或者取消私信的星标
    :param doctor_user_id:医生的用户ID
    :param ctx:
    :param conversation_id:会话的ID
    :return:
    """
    conversationuserstatus = ConversationUserStatus.objects.get(conversation_id=conversation_id,
                                                                user_id=doctor_user_id)

    if conversationuserstatus.is_star:
        conversationuserstatus.is_star = False
    else:
        conversationuserstatus.is_star = True
    conversationuserstatus.save()

    c = Conversation.objects.get(id=conversation_id)
    message_signals.post_touch_conversation(doctor_user_id, c)
    return {'is_star': conversationuserstatus.is_star}


@bind_context('conversation/getconversationbyuids')
def get_conversation_by_uids(ctx, uids):
    """
    根据两个UID获取会话
    :param ctx:
    :param uids:
    :return:
    """
    if len(uids) != 2:
        raise RPCPermanentError
    conversatinstatu_ids = ConversationUserStatus.objects.filter(user_id=uids[0]).values_list('conversation_id',
                                                                                              flat=True)
    conversatinstatu = ConversationUserStatus.objects.filter(user_id=uids[1]). \
        filter(conversation_id__in=conversatinstatu_ids).first()
    return {
        "conversation_id": conversatinstatu.conversation.id
    }


@bind_context('message/doctors/unread')
def conversation_unread_list_doctors_unread(ctx, user_ids):
    """
    未读会话数
    """
    return UserUnread.get_all_conversation_unread_num_by_ids(user_ids)


@bind_context('message/conversation/all_unread')
def conversation_all_unread(ctx):
    user = get_user_from_context(ctx)
    if not user:
        return 0
    user_unread = UserUnread(user.id)
    return user_unread.get_conversation_all_unread()


@bind_context('conversation/doctors_unread')
def conversatin_unread_list_detail(ctx, user_ids):
    """
    根据uids获取未读私信的前三
    :param ctx:
    :param user_ids:
    :return:
    """
    qs = ConversationUserStatus.objects.filter(user_id__in=user_ids,
                                               is_empty=False,
                                               read_status=True).order_by('-last_reply_time')[:3]

    res_data = []
    for item in qs:
        try:
            doctor_user = User.objects.get(id=item.user_id)
            doctor = Doctor.objects.get(user_id=item.user_id)
            conversation = item.conversation
            data = conversation.conversation_info()
            uids = data['user_ids']
            user = User.objects.filter(id__in=uids).exclude(id=doctor_user.id).first()
            data['is_star'] = item.is_star
            data['hospital_name'] = doctor.hospital.name if doctor.hospital else ''
            data['unread_num'] = UserUnread(doctor_user.id).get_conversation_unread(conversation.id, default=0)
            data['partial'] = get_full_path(user.userextra.portrait) or get_full_path(u'img%2Fuser_portrait.png')
            data['nickname'] = filter_user_nick_name(user)
            res_data.append(data)
        except:
            continue
    return res_data


@bind('message/jim/config')
def get_jim_config(user_id):
    """
    Get jim config information by user_id.
    """
    username, password = get_jim_login_info(user_id)
    result = {'username': username, 'password': password}
    register_jim(username, password)

    return result


@bind('message/first_check')
def is_normal_user_first_send_message(message_id, user_id):
    """判断是否当前message是用户第一次主动发送

    :param int message_id: 消息ID
    :param int user_id: 用户ID
    :rtype: boolean
    """
    return bool(Message.objects.filter(id__lt=message_id, is_system=False, user_id=user_id).first())


@bind('message/conversation/list_v2')
def batch_get_conversations(user_ids, offset, size, order_by=MESSAGE_ORDER_TYPE.READ_STATUS,
                            read_status=None, is_star=None, conversation_type=None, search_content=None,
                            with_fields=None):
    """获取对话列表的ID， 注意和之前的v1版本不同的是， 这里只会返回conversation_ids
        权限放在上层处理

    :param user_ids: List[Int] 用户ID列表
    :param offset: Int 偏移量
    :param size: Int 每页数量
    :param order_by: MESSAGE_ORDER_TYPE 排序规则 详细看枚举值
    :param read_status: Optional[Bool] 读取状态
    :param is_star: Optional[Bool] 是否标星
    :param conversation_type: CONVERSATION_TYPE 会话类型
    :param search_content str: 搜索关键词  主要针对 user_name 和 message_content进行过滤
    :return: Dict
    """
    if not isinstance(user_ids, list):
        raise RPCPermanentError
    if not isinstance(size, int):
        raise RPCPermanentError
    if size <= 0 or size >= 50:
        size = 50
    if with_fields is None or not isinstance(with_fields, list):
        with_fields = []

    # 如果没有查询关键字 走mysql过滤， 如果有查询关键字 则从es获取对应的conversation_ids
    # ES filters
    es_filters = {'user_ids': user_ids}
    # Mysql filters
    query_q = Q(user_id__in=user_ids)
    ordering_by = list()

    if read_status is not None and isinstance(read_status, bool):
        query_q &= Q(read_status=read_status)
        es_filters['multi_filter'] = {
            'is_unread': read_status,
            'user_ids': user_ids,
        }

    if is_star is not None and isinstance(is_star, bool):
        query_q &= Q(is_star=is_star)
        es_filters['is_star'] = is_star

    # 这块暂时不支持es的过滤,
    # conversation_type 暂时没有什么特别的需求
    if conversation_type is not None and isinstance(conversation_type, list):
        conversation_type = filter(lambda cy: cy in CONVERSATION_TYPE, conversation_type)
        query_q &= Q(conversation_type__in=conversation_type)

    es_sort_type = CONVERSATION_ORDER.UNREAD
    if order_by == MESSAGE_ORDER_TYPE.LAST_REPLY_TIME:
        ordering_by.append('-last_reply_time')
        es_sort_type = CONVERSATION_ORDER.LAST_REPLY_TIME
    elif order_by == MESSAGE_ORDER_TYPE.READ_STATUS:
        ordering_by.extend(['-read_status', '-last_reply_time'])

    if search_content is not None:
        es_query = {
            'content': search_content,
            'user_last_name': search_content,
        }
        assert isinstance(search_content, string_types)
        es_result = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
                                                      query=es_query, sort_type=es_sort_type)
        total_count = es_result['total_count']
        _conversation_ids = es_result['conversation_ids']
        conversation_user_status = ConversationUserStatus.objects.filter(
            user_id__in=user_ids, conversation_id__in=_conversation_ids
        ).all()
        # 重排序
        conversation_id_to_conversation_user_status = {cus.conversation_id: cus for cus in conversation_user_status}
        conversation_user_status = []
        for conversation_id in _conversation_ids:
            if conversation_id_to_conversation_user_status.get(conversation_id):
                conversation_user_status.append(conversation_id_to_conversation_user_status[conversation_id])
    else:
        total_count = ConversationUserStatus.objects.filter(query_q).count()
        conversation_user_status = ConversationUserStatus.objects.prefetch_related('conversation').\
                                  filter(query_q).order_by(*ordering_by)[offset:offset + size]
    # conversation_info in database
    conversation_info_list = [cs.conversation.conversation_info_v2(cs) for cs in conversation_user_status]
    conversation_id_to_user_id = {cs.conversation_id: cs.user_id for cs in conversation_user_status}
    cs_ids = [cs.id for cs in conversation_user_status]
    conversation_ids = [cs.conversation_id for cs in conversation_user_status]

    # 此接口目前只用于客户端私信，返回unread_num
    for c in conversation_info_list:
        default = 1 if c['is_new'] else 0
        c['unread_num'] = UserUnread(conversation_id_to_user_id[c['id']]).get_conversation_unread(c['id'], default=default)

    if 'mark' in with_fields:
        conversation_id_and_target_user_id = ConversationUserStatus.objects.filter(
            conversation_id__in=conversation_ids
        ).exclude(id__in=cs_ids).values('conversation_id', 'user_id')
        conversation_id_to_target_user_id = {item['conversation_id']: item['user_id']
                                             for item in conversation_id_and_target_user_id}
        from api.models import MarkUser
        mus = MarkUser.objects.filter(user_id__in=user_ids, target_user_id__in=conversation_id_to_target_user_id.values())
        _user_ids_to_comment = {(entry.user_id, entry.target_user_id): entry.comment for entry in mus}
        _conversation_id_to_user_ids = {conversation_id: (user_id, conversation_id_to_target_user_id[conversation_id])
                                        for conversation_id, user_id in conversation_id_to_user_id.items()
                                        if conversation_id_to_target_user_id.get(conversation_id)}
        for c in conversation_info_list:
            c['comment'] = _user_ids_to_comment.get(_conversation_id_to_user_ids[c['id']], u'')

    # filter out items with empty user_ids
    conversation_info_list = [c for c in conversation_info_list if len(c['user_ids']) > 1]

    return {
        'conversation_list': conversation_info_list,
        'total_count': total_count,
    }


@bind('message/conversation/list_v3')
def batch_get_conversations_v3(user_ids, offset, size, order_by=CONVERSATION_ORDER.LAST_REPLY_TIME,
                               last_reply_time_start=None, last_reply_time_end=None, reply_status=None,
                               is_star=None, user_id=None, user_last_name=None, comment=None):
    """
    获取会话列表， 与v2不同的是全部走es获取conversation_ids
    :param user_ids: LIST[USER_ID]
    :param offset: INT 偏移量
    :param size: INT 每页数量
    :param order_by: 排序规则
    :param last_reply_time_start: str 最后回复开始时间筛选
    :param last_reply_time_end: str 最后回复结束时间筛选
    :param reply_status: bool 回复状态（新消息、旧消息）
    :param is_star: bool
    :param user_id: int 精确搜索用户ID
    :param user_last_name: int 搜索用户昵称
    :param comment: int 搜索备注
    :return:
    """
    if not isinstance(user_ids, list):
        raise RPCPermanentError
    if not isinstance(size, int):
        raise RPCPermanentError
    if size <= 0 or size >= 50:
        size = 50
    es_filters = {'user_ids': user_ids}
    if is_star is not None and isinstance(is_star, bool):
        es_filters['is_star'] = is_star
    if last_reply_time_start is not None:
        es_filters['last_reply_time_start_gte'] = parser.parse('{}+0800'.format(last_reply_time_start)).isoformat()
    if last_reply_time_end is not None:
        es_filters['last_reply_time_end_lte'] = parser.parse('{}+0800'.format(last_reply_time_end)).isoformat()
    es_query = dict()
    # if search_content is not None:
    #     es_query = {
    #         'comment': search_content,
    #         'user_last_name': search_content,
    #     }
    if user_id is not None:
        es_query['user_id'] = user_id
    if user_last_name:
        es_query['user_last_name'] = user_last_name
    if comment:
        es_query['comment'] = comment
    es_sort_type = order_by
    # 全部
    es_result_total = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
                                              query=es_query, sort_type=es_sort_type)
    # 已回复
    es_filters['multi_filter_status'] = {
        'status': True,
        'user_ids': user_ids,
    }
    es_result_reply = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
                                 query=es_query, sort_type=es_sort_type)
    # 未回复
    es_filters['multi_filter_status'] = {
        'status': False,
        'user_ids': user_ids,
    }
    es_result_not_reply = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
                                      query=es_query, sort_type=es_sort_type)
    # es_result = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
    #                                         query=es_query, sort_type=es_sort_type)
    if reply_status == None:
        es_result = es_result_total
    else:
        if reply_status:
            es_result = es_result_reply
        else:
            es_result = es_result_not_reply
    _conversation_ids = es_result['conversation_ids']
    cus_ids = []
    for conversation_id in _conversation_ids:
        sub_ids = ConversationUserStatus.objects.filter(
            user_id__in=user_ids, conversation_id=conversation_id
        ).values_list('id', flat=True)
        cus_ids.extend(list(sub_ids))
    conversation_user_status = ConversationUserStatus.objects.filter(id__in=cus_ids).order_by('-last_reply_time')

    conversation_info_list = [cs.conversation.conversation_info_v4(cs, user_ids) for cs in conversation_user_status]
    conversation_id_to_user_id = {cs.conversation_id: cs.user_id for cs in conversation_user_status}
    for c in conversation_info_list:
        default = 1 if c['is_new'] else 0
        c['unread_num'] = UserUnread(conversation_id_to_user_id[c['id']]
                                     ).get_conversation_unread(c['id'], default=default)
    return {
        'conversation_list': conversation_info_list,
        'total_count': es_result_total['total_count'],
        'reply_total': es_result_reply['total_count'],
        'not_reply_total': es_result_not_reply['total_count']
    }


@bind('message/conversation/unread_list')
def batch_get_unread_list(user_ids, use_es=False, only_count=False):
    """
    获取指定用户的未读会话列表，目前使用MYSQL返回结果, ES返回可能会有延迟
    :param only_count: 是否只返回数量，默认否
    :param user_ids: LIST[USER_ID]
    :param use_es: BOOL 是否通过es查询结果
    :return:
    """
    es_filters = {'user_ids': user_ids}
    if use_es:
        es_filters['multi_filter'] = {
            'is_unread': True,
            'user_ids': user_ids,
        }
        es_result = search_conversation_from_es(offset=0, size=1000, filters=es_filters)
        _conversation_ids = es_result['conversation_ids']
        conversation_user_status = ConversationUserStatus.objects.filter(
            user_id__in=user_ids, conversation_id__in=_conversation_ids
        ).all()
    else:
        cus_ids = []
        for user_id in user_ids:
            sub_ids = ConversationUserStatus.objects.filter(user_id=user_id, read_status=True).values_list('id', flat=True)
            cus_ids.extend(list(sub_ids))
        conversation_user_status = ConversationUserStatus.objects.prefetch_related('conversation'). \
                                       filter(id__in=cus_ids).order_by('-last_reply_time')
    if only_count:
        return {
            'unread_count': UserUnread(user_ids[0]).get_conversation_all_unread()
        }
    conversation_info_list = [cs.conversation.conversation_info_v4(cs, user_ids) for cs in conversation_user_status]
    conversation_id_to_user_id = {cs.conversation_id: cs.user_id for cs in conversation_user_status}
    for c in conversation_info_list:
        default = 1 if c['is_new'] else 0
        c['unread_num'] = UserUnread(conversation_id_to_user_id[c['id']]).get_conversation_unread(c['id'], default=default)
    return {'conversation_list': conversation_info_list,}


@bind_context('message/message/read')
def message_read(ctx, message_ids, read_user_id=None,source='user'):
    """设置消息状态为已读 并且通知对应用户

    :param message_ids List[int]: 已读消息ID
    :return:
    """
    if read_user_id is None:
        user = get_user_from_context(ctx)
    else:
        user = get_user_by_id(read_user_id)
    if not user:
        raise RPCLoginRequiredException
    dot_time = int(time.time() * 1000)
    for msg_id in message_ids:
        _logger_data = {
            "time": dot_time,
            "msg_id": msg_id,
            'render': source,
        }
        ctx.logger.app(**_logger_data)
    if message_ids is None or not isinstance(message_ids, list):
        raise RPCPermanentError
    from message.tasks import change_message_is_read_status_and_notify
    change_message_is_read_status_and_notify(msg_ids=message_ids, read_user_id=user.id)
    # from api.tasks.voice_alert_task import voice_alert_publish, VOICE_ALERT_PUBLISH_TASK_ID_FORMAT
    # for message_id in message_ids:
    #     voice_alert_publish.AsyncResult(VOICE_ALERT_PUBLISH_TASK_ID_FORMAT.format(
    #                                         message_id=message_id))\
    #         .revoke()
    return


@bind('message/conversation/msgs_v3')
def conversation_message_list(view_user_id, conversation_id=None, user_ids=None,
                              before_msg_id=None, after_msg_id=None, size=10, from_user_refer=None, business_id=None,
                              is_slice=True):
    """ 权限的判断一定要再上层做好处理

    :param view_user_id int: 查看消息的用户ID
    :param conversation_id Optional[int]: 会话ID
    :param user_ids Optional[List[int]]: 对话的两个用户ID
    :param before_msg_id Optional[int]: 上一个消息ID
    :param after_msg_id Optional[int]: 下一个消息ID
    :param size: -1 暂时代替不需要分页， 以后必须强制分页
    :param from_user_refer Optional[str]: 来源
    :param business_id Optional[int]: 来源的关键信息 例如美购ID
    :return:
    """
    if size <= 0 or size >= 100 or not isinstance(size, int):
        size = 100
    view_user = get_user_by_id(view_user_id)
    if conversation_id is not None:
        conversation = Conversation.objects.filter(id=conversation_id).first()
        if not conversation:
            raise RPCNotFoundException
    elif user_ids is not None:
        if len(user_ids) != 2:
            raise RPCPermanentError('user_key error')
        conversation, created = Conversation.objects.get_or_create_conversation(*user_ids)

    else:
        raise RPCPermanentError('invalid arguments')

    msg_ids = list_messages(conversation_id=conversation.id, before_msg_id=before_msg_id,
                            after_msg_id=after_msg_id, size=size if is_slice else None)
    if not msg_ids:
        return []
    messages = Message.objects.filter(id__in=msg_ids)
    msg_id_to_message = {m.id: m for m in messages}
    # message 重排序
    msg_detail_info_list = []
    for msg_id in msg_ids:
        if msg_id in msg_id_to_message:
            msg_detail_info_list.append(msg_id_to_message[msg_id].to_dict())

    # update refer
    if from_user_refer:
        from message.tasks import update_conversation_refer
        update_conversation_refer.delay(user_id=view_user_id, conversation_id=conversation.id,
                                        from_user_refer=from_user_refer, business_id=business_id)

    # add 医生版实时小红点数量
    # 只要医生有未读的就发出通知 read_status True的时候是未读......
    conversation_num = UserUnread(view_user.id).get_conversation_unread(conversation.id)
    if conversation_num and int(conversation_num) > 0:
        from services.notify import notify
        uids_1, uids_2 = conversation.user_ids()
        notify("conversation/clear", user_id=view_user.id, data={
            'conversation_id': conversation.id,
            'send_uid': view_user.id,
            'send_nickname': view_user.last_name,
            'target_uid': uids_2 if uids_1 == view_user.id else uids_1,
        })

    # 设置为已读
    conversation.user_status_set.filter(user_id=view_user.id).update(
        read_status=False
    )
    message_signals.post_touch_conversation(view_user, conversation)
    return msg_detail_info_list


@bind('message/user/all_read')
def message_all_read(user_id):
    """
    客户端私信全部标为已读
    :param user_id: 当前用户ID
    :return:
    """
    cus = ConversationUserStatus.objects.filter(user_id=user_id)
    view_user = get_user_by_id(user_id)
    # 更改已读状态
    for cu in cus:
        from services.notify import notify
        uids_1, uids_2 = cu.conversation.user_ids()
        # 清除小红点
        notify("conversation/clear", user_id=view_user.id, data={
            'conversation_id': cu.conversation_id,
            'send_uid': view_user.id,
            'send_nickname': view_user.last_name,
            'target_uid': uids_2 if uids_1 == view_user.id else uids_1,
        })
        # 通知es更改已读状态
        message_signals.post_touch_conversation(view_user, cu.conversation)
    cus.update(read_status=False)
    return {'user_id': user_id}


def list_messages(conversation_id, before_msg_id=None, after_msg_id=None, size=10):
    query = Message.objects.filter(conversation_id=conversation_id)
    should_reverse = True
    if before_msg_id is not None:
        query = query.filter(id__lt=before_msg_id).order_by('-id')
    elif after_msg_id is not None:
        query = query.filter(id__gt=after_msg_id).order_by('id')
        should_reverse = False
    else:
        query = query.order_by('-id')

    if size is None:
        result = list(query.values_list('id', flat=True))
    else:
        result = list(query[:size].values_list('id', flat=True))

    if should_reverse:
        result.reverse()

    return result


def send_activity_participation_message_to_doctor(sender_user_id, target_user_id, activity_title):
    """
    用户报名免费活动成功给商家发私信提醒
    :param sender_user_id:
    :param target_user_id:
    :param activity_title:
    :return:
    """
    try:
        if all([sender_user_id, target_user_id]) and sender_user_id != target_user_id:
            content = u'您好，我报名了【{activity_name}】项目，如果被选入望告知，谢谢~'.format(
                activity_name=activity_title
            )
            res = internal_message_send(sender_user_id=sender_user_id, target_user_id=target_user_id,
                                        conversation_id=None, conversation_type=CONVERSATION_TYPE.MESSAGE,
                                        msg_type=MESSAGE_TYPE.TEXT, content={'text': content}, is_system=1)

            if 'error' in res and res['error'] != CODES.SUCCESS:
                logging_exception()
    except:
        logging_exception()


@bind_context('api/activity/participation_notify')
def send_activity_participation_notify(ctx, activity_id):
    """
    用户报名免费活动成功给商家发私信提醒
    :return:
    """
    current_user = get_user_from_context(ctx)
    if not current_user:
        return gen(CODES.LOGIN_REQUIRED)
    try:
        activity = Activity.objects.get(id=activity_id)
    except Activity.DoesNotExist:
        return gen(CODES.ACTIVITY_NOT_EXIST)

    try:
        doctor = Doctor.objects.get(id=activity.doctor_id)
    except Doctor.DoesNotExist:
        return gen(CODES.DOCTOR_NOT_FOUND)

    send_activity_participation_message_to_doctor(
        current_user.id, doctor.user_id, activity.title
    )

    return gen(CODES.SUCCESS)


@bind_context('message/message/message_monitor')
def message_monitor(ctx, msg_ids):
    """ 异常私信监控对backend暴露接口 """

    messages = Message.objects.filter(id__in=msg_ids)
    ret = []
    for message in messages:

        if message.type in SPECIAL_MSG_TYPE:
           content = Message.get_special_msg_content(message.type, message.body)
        else:
           content = Message.get_general_msg_content(message.type, message.content)

        cs = ConversationUserStatus.objects.filter(
            conversation_id=message.conversation_id
        ).exclude(user_id=message.user_id).first()
        if not cs:
            continue
        target_uid = cs.user_id

        try:
            doctor = Doctor.objects.get(user_id=target_uid)

            if doctor.doctor_type == DOCTOR_TYPE.OFFICER:
                doctor_name = doctor.name

            else:
                doctor_name = u'{} 医生'.format(doctor.name)
        except Doctor.DoesNotExist:
            logging_exception()
            continue

        if doctor.hospital_id:
            try:
                hospital = Hospital.objects.get(id=doctor.hospital_id)
                hospital_name = hospital.name
            except Hospital.DoesNotExist:
                logging_exception()
                hospital_name = ""

        ret.append({
            "send_uid": message.user_id,
            "content": content,
            "send_time": message.send_time.strftime('%Y-%m-%d %H:%M:%S'),
            "conversation_id": message.conversation_id,
            "doctor_name": doctor_name,
            "doctor_id": doctor.id,
            "hospital_name": hospital_name
        })

    return ret


@bind_context('message/message/block_message')
def block_message(ctx, user_id):
    """ 手动拉黑私信功能 (内部使用) """

    MessageBlackUser.objects.create(user_id=user_id)

    return {"status": True}


@bind_context('message/message/get_msg_coupon_info')
def get_msg_coupon_info(ctx, gift_id, channel_id):
    """ 获取美券信息 """

    try:
        return Message.get_msg_coupon_info(gift_id, channel_id)
    except Exception as e:
        return {}