# coding=utf-8
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Remove `managed = False` lines for those models you wish to give write DB access
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.
from __future__ import unicode_literals, absolute_import, print_function

import gevent
import hashlib
import json

from django.db import models, transaction, IntegrityError
from django.db.models import Q
from django.utils import timezone
from django.conf import settings
from gm_types.error import ERROR
from gm_types.gaia import DOCTOR_USE_COUPON_TYPE
from gm_types.msg import CONVERSATION_TYPE, MessageTraceEventType
from gm_types.whisper import MESSAGE_SOURCE
from gm_types.utils.enum import Enum
from gm_upload import ImgUrlField, IMG_TYPE

import message.signals as message_signals
from api.tool.datetime_tool import get_timestamp_or_none
from api.tool.image_utils import get_full_path
from api.tool.log_tool import conversation_logger, logging_exception
from api.tool.user_tool import get_portrait_by_user, get_user_by_id
from rpc.context import get_rpc_remote_invoker
from rpc.exceptions import RPCIntegrityError
from rpc.tool.dict_mixin import DictMixin
from rpc.tool.error_code import gen
from rpc.tool.protocol import gm_protocol
from rpc.tool.log_tool import log_message_trace
from .doctor import Doctor
from .hospital import Hospital
from .service import Service
from .types import CONVERSATION_STATUS, JUMP_TYPE, MESSAGE_TYPE, DOCTOR_TYPE
from .types import PRIVATE_CONVERSATION_STATUS, PRIVATE_MESSAGE_PUSH_STATUS
from .user import User


SPECIAL_MSG_TYPE = frozenset([MESSAGE_TYPE.SERVICE, MESSAGE_TYPE.DOCTOR_TOPIC, MESSAGE_TYPE.TEXT_WITH_URL,
                              MESSAGE_TYPE.DIARY, MESSAGE_TYPE.GIFT, MESSAGE_TYPE.CONSULTATION_COUNSEL_REPLAY,
                              MESSAGE_TYPE.CONSULTATION_COUNSEL_REPORT, MESSAGE_TYPE.APPOINTMENT_CARD])

class ConversationManager(models.Manager):

    def conversation_exists(self, *user_ids, **kwargs):
        conversation_query = self.filter(uid_hash=Conversation.gen_uid_hash(user_ids))
        return conversation_query.exists()

    def get_or_create_conversation(self, *user_ids, **kwargs):
        """ 查询或者创建一个 私信会话， 返回 (conversation, created) 二元组 """
        conversation_query = self.filter(uid_hash=Conversation.gen_uid_hash(user_ids))
        if conversation_query.exists():
            return conversation_query[0], False

        queries = (Q(user_status_set__user_id=user_id) for user_id in user_ids)
        conversation_query = self.all()
        for query in queries:
            conversation_query = conversation_query.filter(query)

        if conversation_query.exists():
            conversation = conversation_query.latest('id')
            return conversation, False
        else:
            conversation_already_exists = False
            with transaction.atomic(using=settings.MESSAGE_DB_NAME):
                conversation = self.model(uid_hash=Conversation.gen_uid_hash(user_ids))
                if kwargs.get('conversation_type') in CONVERSATION_TYPE:
                    conversation_type = kwargs['conversation_type']
                else:
                    conversation_type = CONVERSATION_TYPE.MESSAGE
                conversation.conversation_type = conversation_type
                try:
                    conversation.save()
                except IntegrityError:
                    conversation_already_exists = True
                else:
                    for user_id in user_ids:
                        conversation.user_status_set.create(user_id=user_id, status=CONVERSATION_STATUS.OLD,
                                                            read_status=False, conversation_type=conversation_type)
            if conversation_already_exists:
                return self.filter(uid_hash=Conversation.gen_uid_hash(user_ids)).first(), False

            # process extra
            conversation_extra = {}
            if 'conversation_type' in kwargs:
                conversation_extra['conversation_type'] = kwargs['conversation_type']

            message_signals.post_create_conversation(conversation, conversation_extra)

        return conversation, True

    def get_conversation(self, *user_ids, **kwargs):
        conversation_query = self.filter(uid_hash=Conversation.gen_uid_hash(user_ids))
        if conversation_query.exists():
            return conversation_query[0]

        queries = (Q(user_status_set__user_id=user_id) for user_id in user_ids)
        conversation_query = self.all()
        for query in queries:
            conversation_query = conversation_query.filter(query)

        if conversation_query.exists():
            conversation = conversation_query.latest('id')
        else:
            conversation_already_exists = False
            with transaction.atomic(using=settings.MESSAGE_DB_NAME):
                conversation = self.model(uid_hash=Conversation.gen_uid_hash(user_ids))
                if kwargs.get('conversation_type') in CONVERSATION_TYPE:
                    conversation_type = kwargs['conversation_type']
                else:
                    conversation_type = CONVERSATION_TYPE.MESSAGE
                conversation.conversation_type = conversation_type
                try:
                    conversation.save()
                except IntegrityError:
                    conversation_already_exists = True
                else:
                    for user_id in user_ids:
                        conversation.user_status_set.create(user_id=user_id, status=CONVERSATION_STATUS.OLD,
                                                            read_status=False, conversation_type=conversation_type)
            if conversation_already_exists:
                return self.filter(uid_hash=Conversation.gen_uid_hash(user_ids)).first()

            # process extra
            conversation_extra = {}
            if 'conversation_type' in kwargs:
                conversation_extra['conversation_type'] = kwargs['conversation_type']

            message_signals.post_create_conversation(conversation, conversation_extra)

        return conversation


class Conversation(models.Model):
    class Meta:
        verbose_name = u'私信对话'
        verbose_name_plural = u'私信对话'
        db_table = 'api_conversation'
        app_label = 'api'

    # user = models.ManyToManyField(User, through='ConversationUserStatus')  # CHANGED: through ConversationUserStatus
    created_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now)
    last_reply_time = models.DateTimeField(verbose_name=u'最后回复时间', default=timezone.now)
    is_empty = models.BooleanField(verbose_name=u'是否是空对话', default=True)
    uid_hash = models.CharField(verbose_name=u'用户id列表hash', max_length=32, unique=True)
    last_msg_content_disp = models.TextField(verbose_name=u'最后消息内容', max_length=1024, default='',
                                             blank=True)  # duplicate for last msg content
    # add at 7750
    conversation_type = models.SmallIntegerField(verbose_name=u'对话类型', choices=CONVERSATION_TYPE, default=CONVERSATION_TYPE.MESSAGE)
    objects = ConversationManager()

    @staticmethod
    def gen_uid_hash(user_ids=[]):
        user_ids = sorted(map(int, user_ids))
        uid_str = ','.join(map(str, user_ids))
        return hashlib.md5(uid_str).hexdigest()

    def user_ids(self):
        uids = self.user_status_set.values_list('user_id', flat=True)
        return sorted(uids)

    def conversation_info_v2(self, cs):
        """格式化私信对话详细信息"""
        return {
            'id': self.id,
            'last_reply_time': get_timestamp_or_none(self.last_reply_time),
            'is_new': cs.read_status,
            'unread_num': 0,
            'text': self.last_msg_content_disp,
            'user_ids': self.user_ids(),
            'jump_type': JUMP_TYPE.CONVERSATION,
            'conversation_type': self.conversation_type,
            'is_star': cs.is_star,
            'created_time': get_timestamp_or_none(self.created_time)
        }

    def conversation_info_v3(self, cs, user_ids=None):
        """格式化私信对话详细信息"""
        c_user_ids = list(self.user_status_set.values_list('user_id', flat=True))
        doctor_user_id = None
        if user_ids:
            user_ids = map(lambda x: int(x), user_ids)
            for c_user_id in c_user_ids:
                if c_user_id in user_ids:
                    doctor_user_id = c_user_id
        return {
            'id': self.id,
            'last_reply_time': get_timestamp_or_none(self.last_reply_time),
            'is_new': cs.read_status,
            'unread_num': 0,
            'text': self.last_msg_content_disp,
            'user_ids': self.user_ids(),
            'jump_type': JUMP_TYPE.CONVERSATION,
            'conversation_type': self.conversation_type,
            'is_star': cs.is_star,
            'comment': cs.comment,
            'doctor_user_id': doctor_user_id
        }

    def conversation_info_v4(self, cs, user_ids=None):
        """格式化私信对话详细信息
        解决：机构和旗下医生对话用户私信列表显示2条相同对话（接待帐号都为普通医生）
        未读消息列表也全部显示的作为普通医生的信息
        """
        c_user_ids_read_status = self.user_status_set.values_list('user_id', 'read_status')
        single_user_id_2_read_status = {}
        for single_status in c_user_ids_read_status:
            single_user_id_2_read_status[single_status[0]] = single_status[1]

        # doctor_user_id = None
        # if user_ids:
        #     user_ids = map(lambda x: int(x), user_ids)
        #     for c_user_id in single_user_id_2_read_status:
        #         if c_user_id in user_ids and single_user_id_2_read_status[c_user_id]:
        #             doctor_user_id = c_user_id
        return {
            'id': self.id,
            'last_reply_time': get_timestamp_or_none(self.last_reply_time),
            'is_new': cs.read_status,
            'unread_num': 0,
            'text': self.last_msg_content_disp,
            'user_ids': self.user_ids(),
            'jump_type': JUMP_TYPE.CONVERSATION,
            'conversation_type': self.conversation_type,
            'is_star': cs.is_star,
            'comment': cs.comment,
            'doctor_user_id': cs.user_id
        }

    def conversation_info(self, user=None, cs=None, doctor_id=''):
        info = {
            'id': self.id,
            'doctor_id': doctor_id,
            'hospital_id': '',
            'last_reply_time': get_timestamp_or_none(self.last_reply_time),
            'is_new': False,
            'unread_num': 0,
            'text': '',
            'user_ids': self.user_ids(),
            'jump_type': JUMP_TYPE.CONVERSATION,
            'conversation_type': self.conversation_type,
        }

        if user:
            # 更新is_new
            # <<<< old read way
            # status_set = self.user_status_set.filter(user=user)
            # status_set = status_set.filter(status=CONVERSATION_STATUS.NEW)
            # if status_set.exists():
            #    info['is_new'] = True
            # ====
            if cs:
                info['is_new'] = cs.read_status
            else:
                status_set = self.user_status_set.filter(user_id=user.id)
                status_set = status_set.filter(read_status=True).first()
                if status_set:
                    info['is_new'] = True
                    # >>>> new read way

        # <<<< old read way
        # latest_message = self.message_set.latest('id')
        # info['text'] = latest_message.content_display()
        # ====
        info['text'] = self.last_msg_content_disp
        # >>>> new read way

        return info

    def send_message(self, user, type_, content, update_self_status=True, send_time=None, body=None, is_system=0,
                     kyc_type=-1, source=MESSAGE_SOURCE.UNKNOWN):
        """
        统一的发送消息的数据库逻辑

        :param content: 这里指的是消息的文字部分
        """
        if not send_time:
            send_time = timezone.now()

        message = self.message_set.create(user_id=user.id, type=type_, content=content,
                                          send_time=send_time, body=body, is_system=is_system, source=source)
        #判断是否为kyc入口，0:不计费，后期可能计费
        if kyc_type == 0:
            FreeMessage.objects.create(id=message.id, is_system=message.is_system, type=kyc_type)
        if update_self_status:
            # 将自己的对话状态标记为旧
            self.user_status_set.filter(user_id=user.id).update(
                # <<<< to be removed
                status=CONVERSATION_STATUS.OLD,
                # >>>> to be removed
                is_empty=False,
                read_status=False,
                last_reply_time=send_time
            )
        # 把同对话的其他用户的对话状态设置为新
        self.user_status_set.exclude(user_id=user.id).update(
            # <<<< to be removed
            status=CONVERSATION_STATUS.NEW,
            # >>>> to be removed
            is_empty=False,
            read_status=True,
            last_reply_time=send_time
        )

        self.last_reply_time = send_time
        self.is_empty = False
        self.last_msg_content_disp = Message.content_display(type=type_, content=content, user=user)
        self.save()

        c_logger = "new message, sender: {}, content: {}".format(user._get_full_field_content_('username'), content)
        conversation_logger.info(c_logger)
        message_signals.post_touch_conversation(user=None, conversation=self)
        log_message_trace(MessageTraceEventType.TRACE_GAIA_WRITE_TO_MYSQL, {"id": message.id})
        return message

    def get_target_user(self, user):
        try:
            cs = ConversationUserStatus.objects.filter(
                conversation_id=self.id
            ).exclude(user_id=user.id).first()
            if not cs:
                raise User.DoesNotExist
            target_user = User.objects.get(pk=cs.user_id)
        except User.DoesNotExist:
            target_user = None

        return target_user


class ConversationUserStatus(models.Model):
    class Meta:
        verbose_name = u'用户私信状态'
        verbose_name_plural = u'用户私信状态'
        db_table = 'api_conversationuserstatus'
        app_label = 'api'

        index_together = [
            ('user_id', 'is_empty', 'last_reply_time'),
            ('user_id', 'is_empty', 'read_status', 'last_reply_time'),
        ]
        unique_together = [
            ('user_id', 'conversation')
        ]

    conversation = models.ForeignKey(Conversation, related_name=u'user_status_set')
    user_id = models.IntegerField()
    status = models.CharField(max_length=1, verbose_name=u'状态', choices=CONVERSATION_STATUS,
                              default=CONVERSATION_STATUS.NEW)
    is_empty = models.BooleanField(default=True, verbose_name='是否空会话')  # duplicate field in conversation
    read_status = models.BooleanField(default=True,
                                      verbose_name='是否未读')  # True(1) for unread, False(0) for read, designed for sorting
    last_reply_time = models.DateTimeField(verbose_name=u'最后回复时间',
                                           default=timezone.now)  # duplicate field in conversation
    refer = models.CharField(max_length=64, verbose_name=u'用户从哪个页面打开私信', blank=True, default=None)
    business_id = models.CharField(max_length=128, verbose_name=u'实体id', blank=True, default=None)
    is_star = models.BooleanField(verbose_name=u'是否是星标私信', default=False)

    # add at 7755 冗余字段
    conversation_type = models.SmallIntegerField(verbose_name=u'对话类型', choices=CONVERSATION_TYPE,
                                                 default=CONVERSATION_TYPE.MESSAGE)
    # add at 7765 备注由markuser表移动到这里
    comment = models.CharField(verbose_name='备注内容', max_length=100, blank=True, null=True, default='')


class Message(models.Model):
    MSG_PER_PAGE = 20

    class Meta:
        verbose_name = u'私信消息'
        verbose_name_plural = u'私信消息'
        app_label = 'api'

    # TODO: 需要把私信和对话的索引重新整理一次
    conversation = models.ForeignKey(Conversation)
    # <<<< should be removed
    user_id = models.IntegerField()
    type = models.CharField(verbose_name=u'消息类型', max_length=16, default='txt')
    content = models.TextField(verbose_name=u'消息内容', max_length=1024, default='', blank=True)
    body = models.TextField(verbose_name=u'消息内容', max_length=1024, default='', blank=True)
    source = models.SmallIntegerField(verbose_name=u'来源', choices=MESSAGE_SOURCE, default=MESSAGE_SOURCE.UNKNOWN)
    # >>>> should be removed
    send_time = models.DateTimeField(verbose_name=u'发送时间', default=timezone.now)
    is_system = models.BooleanField(verbose_name=u'是否由系统主动发送', default=False)
    is_read = models.BooleanField(verbose_name=u'接收者是否已读', default=False)

    def __unicode__(self):
        return u'【{}】{}'.format(MESSAGE_TYPE.getDesc(self.type), self.content)

    @property
    def message_detail(self):
        return {
            'send_time': get_timestamp_or_none(self.send_time),
            'image': self.content if self.type == MESSAGE_TYPE.IMAGE else None,
            'text': self.content if self.type == MESSAGE_TYPE.TEXT else None,
            'audio': self.content if self.type == MESSAGE_TYPE.AUDIO else None,
            'image_thumb': self.content if self.type == MESSAGE_TYPE.IMAGE else None
        }

    @classmethod
    def get_msg_service_info(cls, service_id):
        try:
            service = Service.objects.get(pk=service_id)
            res = {
                'id': service_id,
                'title': u'在［更美］做整形优惠又放心',
                'image': service.image_header,
                'gengmei_price': service.lowest_gengmei_price,
                'text': service.short_description,
                'name': service.name,
                'url': gm_protocol.get_service_detail(id=service_id),
                'is_multiattribute': service.is_multiattribute,
            }
        except Service.DoesNotExist:
            res = {
                'id': service_id,
                'title': u'在［更美］做整形优惠又放心',
                'image': u'',
                'text': u'',
                'url': u'',
            }
        return res

    @classmethod
    def get_msg_doctor_topic_info(cls, topic_id):
        res = {
            'id': topic_id,
            'title': u'更美发布',
            'image': get_full_path(u'img/icon114.png'),
            'text': u'',
            'url': u'',
        }
        rpc = get_rpc_remote_invoker()
        try:
            topic_infos = rpc['topic/get_topic'](topic_id=topic_id).unwrap()
            if topic_infos:
                res['title'] = Message._who_sent(topic_infos.get('user_id')) + u'的更美发布'
                res['text'] = topic_infos.get('content')
                res['url'] = gm_protocol.get_topic_detail(id=topic_id)
                if topic_infos.get('image_url'):
                    res['image'] = get_full_path(topic_infos.get('image_url'))
                else:
                    res['image'] = get_full_path(u'img/icon114.png')
        except:
            logging_exception()

        return res

    @classmethod
    def get_msg_diary_info(cls, diary_id):
        if diary_id is None:
            return None
        rpc = get_rpc_remote_invoker()
        diary_info = rpc['diary/simple_data'](diary_id=diary_id, for_doctor=True).unwrap()
        result = {
            'images': diary_info['images'],
            'tags_new_era': diary_info['tags'],
            'title': diary_info.get('title', ''),
            'url': gm_protocol.get_diary_detail(id=diary_id),
            'id': diary_id,
        }
        return result

    @classmethod
    def get_msg_coupon_info(cls, gift_id, channel_id):
        """
            这个版本暂时只支持医生券
        """
        if not gift_id or not channel_id:
            return {}
        else:
            gift_id = int(gift_id)
            channel_id = int(channel_id)
        from api.tool.coupon_tool import _gift_id_to_doctor_send_coupon_info
        gift_list = _gift_id_to_doctor_send_coupon_info(gift_ids=[gift_id])
        default = {
                'coupon_name': "该红包已下线",
                'coupon_value': 0,
                'coupon_threshold_desc': "该红包已下线",
                'doctor_name': "",
                'has_threshold_desc': "该红包已下线",
                'end_time': "",
                'get_coupon_data': {
                    'gift_id': gift_id,
                    'channel_id': settings.DOCTOR_BUSINESS_CHANNAL_ID,
                },
                'doctor_coupon_use_desc': "",
                "doctor_coupon_use_type": "",
        }
        result = gift_list[0] if len(gift_list) == 1 else default
        if not result['doctor_coupon_use_type']:
            return result

        doctor_coupon_use_desc = {
            DOCTOR_USE_COUPON_TYPE.PART_GENERAL: "部分美购可用",
            DOCTOR_USE_COUPON_TYPE.DOCTOR_GENERAL: "医生通用券",
            DOCTOR_USE_COUPON_TYPE.FULL_PLATFORM_GENERAL: "全店通用",
        }[result['doctor_coupon_use_type']]
        result['doctor_coupon_use_desc'] = doctor_coupon_use_desc

        return result

    # @classmethod
    # def get_msg_consultation_counsel_replay_info(cls, consultation_record_id):
    #     rpc = get_rpc_remote_invoker()
    #     record_replay_info = rpc['diagnosis/consultation_counsel_replay_info'](consultation_record_id).unwrap()
    #     return dict(
    #         video_preview_url='https://pic.igengmei.com/2019/04/19/1147/0a780dea5aa0-half',
    #         consultation_record_id=consultation_record_id
    #     )

    @classmethod
    def batch_get_toc_message_info(cls, messages, gevent_mode=True):
        """批量获取消息格式化后的数据

        :param messages List[Message]:
        :return:
        """
        if not messages:
            return []
        if not all(map(lambda x: isinstance(x, Message), messages)):
            raise ValueError('messages must be Message object')
        msg_id_to_msg_type_and_body = {m.id: {"msg_type": m.type, "body": m.body}
                                       for m in messages if m.type in SPECIAL_MSG_TYPE}
        msg_id_to_display_content = {}
        if msg_id_to_msg_type_and_body:
            if len(msg_id_to_msg_type_and_body) > 1 and gevent_mode:
                jobs = [
                    gevent.spawn(cls.get_special_msg_content, item['msg_type'], item['body'])
                    for item in msg_id_to_msg_type_and_body.values()
                ]
                gevent.joinall(jobs)
                for job, msg_id in zip(jobs, msg_id_to_msg_type_and_body.keys()):
                    if job.exception is None:
                        msg_id_to_display_content[msg_id] = job.value
                    else:
                        msg_id_to_display_content[msg_id] = u''
            else:
                for msg_id, item in msg_id_to_msg_type_and_body.items():
                    msg_id_to_display_content[msg_id] = cls.get_special_msg_content(item['msg_type'], item['body'])
        result = []
        for msg in messages:
            _data = msg.to_dict(with_content=False)
            if msg.type in SPECIAL_MSG_TYPE:
                _data['content'] = msg_id_to_display_content[msg.id]
            else:
                _data['content'] = cls.get_general_msg_content(msg.type, msg.content)
            result.append(_data)
        return result

    @classmethod
    def get_general_msg_content(cls, type, content):
        """针对一般的消息内容， 只需要做一些简单的数据格式封装"""
        if type in [  # 最初的三种类型
            MESSAGE_TYPE.TEXT,
            MESSAGE_TYPE.AUDIO,
            MESSAGE_TYPE.IMAGE,
        ]:
            content = {
                'text': content if type == MESSAGE_TYPE.TEXT else u'',
                'image': get_full_path(content, '-w') if type == MESSAGE_TYPE.IMAGE else u'',
                'audio': get_full_path(content, '-audio') if type == MESSAGE_TYPE.AUDIO else u'',
                'image_thumb': get_full_path(content, '-thumb') if type == MESSAGE_TYPE.IMAGE else u''
            }
        elif type in [  # 以下类型直接透传
            MESSAGE_TYPE.CUSTOMER_SRV_CTRL,
        ]:
            content = {'text': content}
        else:
            raise TypeError('msg type: {} not belong general msg type'.format(type))
        return content

    @classmethod
    def get_special_msg_content(cls, type, body):
        """需要查表获取是调用其他的服务"""
        content = {}
        try:
            body = json.loads(body)
        except:
            logging_exception()
            return content
        if type == MESSAGE_TYPE.SERVICE:
            content['service_info'] = cls.get_msg_service_info(service_id=body)
        elif type == MESSAGE_TYPE.DOCTOR_TOPIC:
            content['doctor_topic_info'] = cls.get_msg_doctor_topic_info(topic_id=body)
        elif type == MESSAGE_TYPE.TEXT_WITH_URL:
            content['text_with_url_info'] = {
                'text': body['text'],
                'url': body['url'],
            }
        elif type == MESSAGE_TYPE.DIARY:
            content['diary_info'] = cls.get_msg_diary_info(diary_id=body)
        elif type == MESSAGE_TYPE.GIFT:
            content['coupon_info'] = cls.get_msg_coupon_info(
                gift_id=body.get('gift_id'),
                channel_id=body.get('channel_id')
            )
        elif type == MESSAGE_TYPE.CONSULTATION_COUNSEL_REPLAY:
            content['consultation_counsel_replay_info'] = body
            content['consultation_counsel_replay_info']['video_preview_url'] = \
                get_full_path(content['consultation_counsel_replay_info']['video_preview_url'])
        elif type == MESSAGE_TYPE.CONSULTATION_COUNSEL_REPORT:
            content['consultation_counsel_report_info'] = body
            content['consultation_counsel_report_info']['report_img'] = \
                get_full_path(content['consultation_counsel_report_info']['report_img'], '-w')
        elif type == MESSAGE_TYPE.APPOINTMENT_CARD:
            content["appointment_card_info"] = body
        else:
            raise TypeError('msg type: {} not belong special msg type'.format(type))
        return content

    def to_dict(self, with_content=True):
        data = {
            'id': self.id,
            'conversation_id': self.conversation_id,
            'uid': self.user_id,
            'send_time': get_timestamp_or_none(self.send_time),
            'type': self.type,  # 消息类型
            'content': {},
            'is_read': self.is_read,
        }
        if with_content:
            if self.type in SPECIAL_MSG_TYPE:
                data['content'] = Message.get_special_msg_content(self.type, self.body)
            else:
                data['content'] = Message.get_general_msg_content(self.type, self.content)
        return data


    @classmethod
    def msg_info(cls, m, user_ids=[]):
        try:
            doctor = Doctor.objects.get(user__id=m['user_id'])
            doctor_id = doctor.id
        except Doctor.DoesNotExist:
            doctor_id = None

        data = {
            'id': m['id'],
            'uid': m['user_id'],
            'send_time': get_timestamp_or_none(m['send_time']),
            'reply_time': get_timestamp_or_none(m['send_time']),
            'conversation_user_ids': user_ids,  # 参与会话的用户id列表
            'doctor_id': doctor_id,  # 如果发信人不是doctor则为None
            'type': m['type'],  # 消息类型
            'content': {},
        }

        # TODO 这个地方最好重构 返回对应类型和唯一标识(id?) 以便于外面的batch处理 或者服务拆分
        if m['type'] in [  # 以下类型直接透传
            MESSAGE_TYPE.CUSTOMER_SRV_CTRL,
        ]:
            data['content'] = m['content']
        elif m['type'] == MESSAGE_TYPE.SERVICE:
            data['content']['service_info'] = cls.get_msg_service_info(service_id=m['content']['service_id'])
        elif m['type'] == MESSAGE_TYPE.DOCTOR_TOPIC:
            data['content']['doctor_topic_info'] = cls.get_msg_doctor_topic_info(topic_id=m['content']['topic_id'])
        elif m['type'] == MESSAGE_TYPE.TEXT_WITH_URL:
            data['content']['text_with_url_info'] = {
                'text': m['content']['text'],
                'url': m['content']['url'],
            }
        elif m['type'] == MESSAGE_TYPE.DIARY:
            data['content']['diary_info'] = cls.get_msg_diary_info(diary_id=m['content'].get('diary_id', None))
        elif m['type'] == MESSAGE_TYPE.GIFT:
            data['content']['coupon_info'] = cls.get_msg_coupon_info(
                gift_id=m['content'].get('gift_id', None),
                channel_id=m['content'].get('channel_id', None)
            )
        elif m['type'] in [  # 最初的三种类型
            MESSAGE_TYPE.TEXT,
            MESSAGE_TYPE.AUDIO,
            MESSAGE_TYPE.IMAGE,
        ]:
            data['content'] = {
                'text': m['content']['text'] if m['type'] == MESSAGE_TYPE.TEXT else u'',
                'image': get_full_path(m['content']['image'], '-w') if m['type'] == MESSAGE_TYPE.IMAGE else u'',
                'audio': get_full_path(m['content']['audio'], '-audio') if m['type'] == MESSAGE_TYPE.AUDIO else u'',
                'image_thumb': get_full_path(m['content']['image'], '-thumb') if m['type'] == MESSAGE_TYPE.IMAGE else u''
            }

        return data

    @classmethod
    def _who_sent(cls, user):
        if isinstance(user, (int, long)):
            user = get_user_by_id(user)

        if user.id == 22:  # 更美所长
            return user.last_name

        try:
            doctor = Doctor.objects.get(user=user)
            who = doctor.name
            if doctor.doctor_type == DOCTOR_TYPE.DOCTOR:
                who = who + '医生'
        except Doctor.DoesNotExist:
            who = user.last_name
        return who

    @classmethod
    def push_alert_text(cls, type, content, user):
        if type != MESSAGE_TYPE.CUSTOMER_SRV_CTRL:
            text = Message._who_sent(user)
        else:
            text = u''  # do not show anything in alert

        if type == MESSAGE_TYPE.TEXT:
            if content and len(content) > 50:
                content = content[:50] + u'...'
            text += u'私信你了哦：{}'.format(content)
        elif type == MESSAGE_TYPE.AUDIO:
            text += u'发来一段语音'
        elif type == MESSAGE_TYPE.IMAGE:
            text += u'发来一张图片'
        elif type == MESSAGE_TYPE.SERVICE:
            text += u'私信你：' + Message._who_sent(user) + '的更美美购'
        elif type == MESSAGE_TYPE.DOCTOR_TOPIC:
            text += u'私信你：' + Message._who_sent(user) + '的更美发布'
        elif type == MESSAGE_TYPE.TEXT_WITH_URL:
            text += u'私信你：' + content
        elif type == MESSAGE_TYPE.CUSTOMER_SRV_CTRL:
            text += u''  # do not show anything in alert
        elif type == MESSAGE_TYPE.GIFT:
            text += u'私信你：你有一个平台红包券，快来领取~'
        elif type == MESSAGE_TYPE.DIARY:
            text += u'私信你：推荐给你一个变美日记本，快来查看~'
        else:
            text += u'私信你'

        return text

    @classmethod
    def content_display(cls, type, content, user):
        if type in [MESSAGE_TYPE.TEXT, MESSAGE_TYPE.TEXT_WITH_URL, MESSAGE_TYPE.CUSTOMER_SRV_CTRL]:
            text = content
        elif type == MESSAGE_TYPE.AUDIO:
            text = u'[语音]'
        elif type == MESSAGE_TYPE.IMAGE:
            text = u'[图片]'
        elif type == MESSAGE_TYPE.SERVICE:
            text = Message._who_sent(user) + u'的更美美购'
        elif type == MESSAGE_TYPE.DOCTOR_TOPIC:
            text = Message._who_sent(user) + u'的更美发布'
        elif type == MESSAGE_TYPE.DIARY:
            text = u'[更美平台用户变美日记]'
        elif type == MESSAGE_TYPE.GIFT:
            text = u'[更美红包券 优惠多多]'
        elif type == MESSAGE_TYPE.CONSULTATION_COUNSEL_REPLAY:
            text = u'[视频面诊回放]'
        elif type == MESSAGE_TYPE.APPOINTMENT_CARD:
            text = u'[到院预约卡]'
        else:
            text = u''
        return text


class MessageBlackUser(models.Model):

    objects = models.Manager().db_manager(using=settings.MESSAGE_DB_NAME)

    class Meta:
        verbose_name = u'消息用户黑名单'
        verbose_name_plural = u'消息用户黑名单'
        app_label = 'api'

    user_id = models.IntegerField(verbose_name=u'用户')
    content = models.TextField(verbose_name=u'那条消息发生的错误', max_length=1024, default='', blank=True)
    created_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now)

    def __unicode__(self):
        return u'用户【{}】于 {} 发送【{}】被限制'.format(self.user.last_name, self.created_time, self.content)

    @property
    def black_detail(self):
        return {
            'created_time': self.created_time.strftime('%Y-%m-%d'),
            'content': self.content,
            'user': self.user.last_name,
        }


class Notification(models.Model):
    class Meta:
        verbose_name = u'用户通知'
        verbose_name_plural = u'用户通知'
        app_label = 'api'

    title = models.CharField(max_length=100, verbose_name=u'标题')
    user = models.ForeignKey(User, verbose_name=u'通知的用户', related_name='notification')
    content = models.CharField(max_length=300, verbose_name=u'内容', blank=True, default=u'')
    create_time = models.DateTimeField(verbose_name=u'通知时间', default=timezone.now)
    url = models.CharField(max_length=256, verbose_name=u'跳转的url协议地址', blank=True, default=u'')
    is_viewed = models.BooleanField(verbose_name=u'用户是否已读', default=False)

    def get_notification_detail(self):
        data = {
            'title': self.title,
            'user': self.user.last_name,
            'content': self.content,
            'create_time': self.create_time.strftime('%Y-%m-%d  %H:%M:%S'),
            'url': self.url,
            'is_view': self.is_viewed,
            'id': 0,  # 无用字段 目前只是用于填充数据
            'type': 0,
            'pic': '',  # 无用字段
            "imgheader": get_portrait_by_user(self.user)
        }
        return data


class PrivateConversation(models.Model):
    class Meta:
        verbose_name = u'03. 私信对话'
        verbose_name_plural = u'03. 私信对话'
        db_table = 'api_privateconversation'
        app_label = 'api'

    # 对话双方(直接通过用户关系来确认), key通过  generate_key 来计算
    user_key = models.CharField(max_length=20, verbose_name=u"用户Ids", db_index=True, default=u"")
    user = models.ForeignKey(User, related_name="converation_user", verbose_name=u"发布者_用户", null=True, blank=True)
    target_user = models.ForeignKey(User, related_name="converation_user1", verbose_name=u"被回复_用户", null=True,
                                    blank=True)

    text = models.CharField(max_length=500, verbose_name=u"发表内容", null=True, blank=True)
    image = ImgUrlField(img_type=IMG_TYPE.PRIVATECONVERSATION, max_length=500, verbose_name=u"图片", null=True,
                        blank=True)
    audio = models.CharField(max_length=500, verbose_name=u"录音", null=True, blank=True)

    is_viewed = models.BooleanField(default=False, verbose_name=u"是否已看")

    status = models.CharField(default=PRIVATE_CONVERSATION_STATUS.NORMAL,
                              choices=PRIVATE_CONVERSATION_STATUS,
                              max_length=1,
                              db_index=True,
                              verbose_name=u"状态")

    send_time = models.DateTimeField(auto_now_add=True, verbose_name=u"创建时间", null=True)

    def generate_key(self, user, target_user):
        if user.id < target_user.id:
            return u"%s_%s" % (user.id, target_user.id)
        else:
            return u"%s_%s" % (target_user.id, user.id)

    def __unicode__(self):
        if self.text:
            return self.text
        else:
            return u"录音"


# class MsgIdAlloc(models.Model):
#    '''
#    消息id分配器
#    '''
#    ID_TYPE_MSG = 1
#    ID_TYPE_CHOICES = (
#        (ID_TYPE_MSG, 'msg_id'),
#    )
#
#    class Meta:
#        db_table = 'api_msgidalloc'
#
#    counter = models.PositiveIntegerField(default=0)
#
#
#    @classmethod
#    def set_id(cls, id_type, new_counter):
#        new_id_type = -1
#        with transaction.atomic():
#            allocator = MsgIdAlloc.objects.select_for_update().get_or_create(pk=id_type)
#            # check if new counter<=original counter
#            if new_counter<=allocator.counter:
#                raise Exception('set counter to a value less than the current one is not allowed')
#            allocator.counter = new_counter
#            allocator.save()
#            new_id_type = allocator.id
#
#        return new_id_type
#
#
#    @classmethod
#    def alloc_id(cls, id_type):
#        """
#        :desc must be set first!
#        :param id_type:
#        :return:
#        """
#        res = -1
#        with transaction.atomic():
#            allocator = MsgIdAlloc.objects.select_for_update().get(pk=id_type)
#            allocator.counter += 1
#            allocator.save()
#            res = allocator.counter
#
#        return res


class PrivateMessagePush(models.Model):
    class Meta:
        verbose_name = u'私信推送'
        verbose_name_plural = u'私信推送'
        app_label = 'api'

    content = models.CharField(max_length=1024, verbose_name=u'私信内容')
    service = models.ForeignKey(Service, verbose_name=u'关联的美购', null=True, blank=True)
    doctor = models.ForeignKey(Doctor, verbose_name=u'关联的医生', null=True, blank=True)
    hospital = models.ForeignKey(Hospital, verbose_name=u'关联的医院', null=True, blank=True)
    order_status = models.CharField(max_length=100, verbose_name=u'订单的状态', null=True, blank=True)
    send_time = models.DateTimeField(verbose_name=u'发送时间')
    status = models.IntegerField(verbose_name=u'审核状态', choices=PRIVATE_MESSAGE_PUSH_STATUS,
                                 default=PRIVATE_MESSAGE_PUSH_STATUS.NOT_REVIEW)
    user_list = models.TextField(verbose_name=u'自由添加用户id', null=True, blank=True, default=None)
    url = models.CharField(u'跳转链接', null=True, max_length=255)


class VoiceAlertStaff(models.Model):
    class Meta:
        verbose_name = u'语音提醒员工表'
        verbose_name_plural = u'语音提醒员工表'
        app_label = 'api'
        db_table = 'voice_alert_staff'

    merchant_id = models.BigIntegerField(verbose_name=u'绑定的商户id')
    name = models.CharField(verbose_name=u'员工姓名', max_length=128)
    phone = models.CharField(verbose_name=u'员工手机号码', max_length=32)


class VoiceAlertSetting(models.Model):
    class Meta:
        verbose_name = u'语音提醒设置表'
        verbose_name_plural = u'语音提醒设置表'
        app_label = 'api'
        db_table = 'voice_alert_setting'

    merchant_id = models.BigIntegerField(verbose_name=u'绑定的商户id')
    alert_interval = models.IntegerField(verbose_name=u'超过多长时间未读会提醒，单位：秒')


class VoiceAlertStaffDutyTable(models.Model):
    class Meta:
        verbose_name = u'员工值班表'
        verbose_name_plural = u'员工值班表'
        app_label = 'api'
        db_table = 'voice_alert_staff_duty_table'

    setting = models.ForeignKey(VoiceAlertSetting, verbose_name=u'设置id')
    start_time = models.FloatField(verbose_name=u'开始时间(一天中的第几秒)', default=0)
    end_time = models.FloatField(verbose_name=u'结束时间(一天中的第几秒)', default=0)
    weekday = models.IntegerField(verbose_name=u'周几', default=0)
    staff = models.ForeignKey(VoiceAlertStaff, verbose_name=u'员工id')


class VoiceAlertConnectStatus(Enum):
    NOT_CONNECTED = (0, '未接通')
    CONNECTED = (1, '已接通')


class VoiceAlertRecord(models.Model):
    class Meta:
        verbose_name = u'语音提醒记录表'
        verbose_name_plural = u'语音提醒记录表'
        app_label = 'api'
        db_table = 'voice_alert_record'

    merchant_id = models.BigIntegerField(verbose_name=u'绑定的商户id, 设置关联的商户')
    white_list_id = models.BigIntegerField(verbose_name=u'开通的服务白名单id', default=0)
    message_receiver_user_id = models.IntegerField(verbose_name=u'私信接收方用户id，对应一个商户')
    message = models.ForeignKey(Message, verbose_name=u'触发提醒的私信')
    alert_time = models.DateTimeField(verbose_name=u'提醒时间')
    receive_staff = models.ForeignKey(VoiceAlertStaff, verbose_name=u'被提醒的员工')
    result = models.IntegerField(verbose_name=u'结果代码', null=True, default=None)
    failure_code = models.IntegerField(verbose_name=u'失败代码', null=True, default=None)
    mobile = models.CharField(verbose_name=u'被叫电话号码', max_length=32, null=True,
                              default=None)
    call_from = models.CharField(verbose_name=u'主叫电话号码', max_length=32, null=True,
                                 default=None)
    fee = models.CharField(verbose_name=u'费用', max_length=128, null=True, default=None)
    accept_time = models.DateTimeField(verbose_name=u'接通时间', null=True, default=None)
    end_calltime = models.DateTimeField(verbose_name=u'挂断时间', null=True, default=None)
    start_calltime = models.DateTimeField(verbose_name=u'发起呼叫的时间', null=True,
                                          default=None)
    vms_callid = models.CharField(verbose_name=u'vms返回的callid', max_length=128, null=True)

    @classmethod
    def get_connect_status(cls, accept_time):
        if not accept_time:
            return VoiceAlertConnectStatus.NOT_CONNECTED
        return VoiceAlertConnectStatus.CONNECTED

    @classmethod
    def get_call_duration(cls, start_calltime, accept_time):
        if not start_calltime or not accept_time:
            return 0
        return (accept_time - start_calltime).seconds

    @classmethod
    def get_connect_duration(cls, accept_time, end_calltime):
        if not accept_time or not end_calltime:
            return 0
        return (end_calltime - accept_time).seconds


class FreeMessage(models.Model):

    class Meta:
        verbose_name = u'不计费私信消息'
        verbose_name_plural = u'不计费私信消息'
        app_label = 'api'

    send_time = models.DateTimeField(verbose_name=u'发送时间', default=timezone.now, db_index=True)
    is_system = models.BooleanField(verbose_name=u'是否由系统主动发送', default=False)
    type = models.IntegerField(verbose_name=u'不计费私信类型')                                     #目前只有kyc来源私信不扣费