# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import

import json
import operator
import itertools
import time

from django.utils import timezone
from rpc.tool.log_tool import info_logger
from gm_types.gaia import FILTER_WORD_TYPE
from gm_types.gaia import LOGIN_AUTH_TYPE
from gm_types.msg import CONVERSATION_TYPE, MessageTraceEventType

from api.models import UserBlackList, Conversation, Message, MESSAGE_SOURCE
from api.models.types import MESSAGE_TYPE
from api.tool.user_tool import get_doctor_by_user_id
from hippo.tasks import message_sync
from hippo.tool.chain_hospital_tools import get_master_merchant
from hippo.utils import get_merchant
from message import signals as message_signals
from message.tasks import send_wechatpub_message, send_jim_message, send_new_message_push
from message.tasks import send_jim_message_v1, send_new_message_push_v1
from message.tasks import async_dingtask_monitor_message
from message.utils.db import write_msg_to_es, build_conversation_extra
from message.utils.es_abstract import get_esop, table_message
from message.utils.push_service import build_conversation_push_extra, UserPushService, DoctorPushService
from rpc.cache import wechatpub_cache
from rpc.cache import conversation_cache
from rpc.context import get_current_context_or_throw_exception
from rpc.context import get_rpc_remote_invoker
from rpc.tool.error_code import CODES
from rpc.tool.log_tool import logging_exception
from rpc.tool.log_tool import current_trace_id, log_message_trace
from talos.models.report import FilterWord


def scan_message(timeout=600):
    timeout_seconds = int(timeout)
    timeout_str = '{:d}s'.format(timeout_seconds)

    result_iter = get_esop().helper_scan(
        table=table_message,
        scroll=timeout_str,
    )
    if not result_iter:
        return ()
    return itertools.imap(operator.itemgetter('_source'), result_iter)


def send_one_message(send_user, target_user=None,
                     conversation=None,
                     conversation_type=CONVERSATION_TYPE.MESSAGE,
                     msg_type=MESSAGE_TYPE.TEXT,
                     content=None,
                     need_jpush=True, is_system=0,
                     push_stat_labels=None, silent=False, filter_word=True, kyc_type=-1, source=MESSAGE_SOURCE.UNKNOWN):
    """
    this interface is considered the common message sending interface
    all the common logic about message sending must be written here
    :return:
    """
    res = {
        'error': CODES.SUCCESS,
        'msg_id': None,
        'msg': None,  # a dict representation for msg
        'conversation': None  # conversation object
    }

    # 检查消息类型
    pure_text_content = ''
    body = ''
    if msg_type == MESSAGE_TYPE.TEXT:
        if not content['text']:
            res['error'] = CODES.MESSAGE_CONTENT_EMPTY
            return res
        # 私信敏感词过滤
        if filter_word:
            filter_word_list = get_rpc_remote_invoker()['talos/filterWord/list'](
                filter_type=FILTER_WORD_TYPE.TOPIC_CHAT
            )
            filter_word_list = filter_word_list.unwrap()
            sensitive_word_list = []
            for word in filter_word_list:
                if word in content['text']:
                    sensitive_word_list.append(word)
            if sensitive_word_list:
                res['error'] = CODES.MESSAGE_CONTENT_FILTERED_OUT
                res['sensitive_words'] = list(set(sensitive_word_list))
                return res
            #if any(word in content['text'] for word in filter_word_list):
            #    res['error'] = CODES.MESSAGE_CONTENT_FILTERED_OUT
            #    return res
        pure_text_content = content['text']
        body = content['text']
    elif msg_type == MESSAGE_TYPE.AUDIO:
        pure_text_content = content['audio']
        body = content['audio']
    elif msg_type == MESSAGE_TYPE.IMAGE:
        pure_text_content = content['image']
        body = content['image']
    elif msg_type == MESSAGE_TYPE.SERVICE:
        assert 'service_id' in content
        content['service_id'] = int(content['service_id'])
        pure_text_content = u'service:%s' % unicode(content['service_id'])
        body = content['service_id']
    elif msg_type == MESSAGE_TYPE.DOCTOR_TOPIC:
        assert 'topic_id' in content
        content['topic_id'] = int(content['topic_id'])
        pure_text_content = u'doctor_topic:%s' % unicode(content['topic_id'])
        body = content['topic_id']
    elif msg_type == MESSAGE_TYPE.TEXT_WITH_URL:
        assert 'text' in content
        assert 'url' in content
        pure_text_content = content['text']
        body = {
            'text': content['text'],
            'url': content['url']
        }
    elif msg_type == MESSAGE_TYPE.CUSTOMER_SRV_CTRL:
        assert 'text' in content
        pure_text_content = content['text']
        body = content['text']
    elif msg_type == MESSAGE_TYPE.DIARY:
        assert 'diary_id' in content
        pure_text_content = u'diary:%s' % unicode(content['diary_id'])
        body = int(content['diary_id'])
    elif msg_type == MESSAGE_TYPE.GIFT:
        assert "channel_id" in content
        assert "gift_id" in content
        pure_text_content = u'channel_id:{},gift_id:{}'.format(
            unicode(content['channel_id']),
            unicode(content['gift_id'])
        )
        body = {
            'channel_id': int(content['channel_id']),
            'gift_id': int(content['gift_id'])
        }

    elif msg_type == MESSAGE_TYPE.CONSULTATION_COUNSEL_REPLAY:
        assert "consultation_record_id" in content
        assert "video_preview_url" in content
        pure_text_content = u'consultation_record_id:{},video_preview_url:{}'.format(
            unicode(content['consultation_record_id']),
            unicode(content['video_preview_url'])
        )
        body = {
            'consultation_record_id': content['consultation_record_id'],
            'video_preview_url': content['video_preview_url']
        }

    elif msg_type == MESSAGE_TYPE.CONSULTATION_COUNSEL_REPORT:
        assert "consultation_record_id" in content
        assert "report_img" in content
        pure_text_content = u'consultation_record_id:{},report_img:{}'.format(
            unicode(content['consultation_record_id']),
            unicode(content['report_img'])
        )
        body = {
            'consultation_record_id': content['consultation_record_id'],
            'report_img': content['report_img']
        }
    elif msg_type == MESSAGE_TYPE.APPOINTMENT_CARD:
        assert "phone" in content
        assert "city" in content
        assert "nickname" in content
        pure_text_content = u'phone:{},city:{},nickname{},title{},extra{}"'.format(
            unicode(content['phone']),
            unicode(content['city']),
            unicode(content['nickname']),
            unicode(content['title']),
            unicode(content['extra'])
        )
        body = {
            "phone": content['phone'],
            "city": content["city"],
            "nickname": content["nickname"],
            "title": content["title"],
            "extra": content["extra"]
        }
    else:
        pass  # raise an error

    # 发信用户是否黑名单
    if UserBlackList.objects.filter(user=send_user.username).exists() or not send_user.is_active:
        res['error'] = CODES.MESSAGE_INVALID_USER
        return res

    # 获取会话
    if not conversation:  # in such case, target_user must be specified
        conversation, created = Conversation.objects.get_or_create_conversation(
            send_user.id, target_user.id,
            conversation_type=conversation_type)

    elif not target_user:
        target_user = conversation.get_target_user(send_user)
    res['conversation'] = conversation

    send_time = timezone.now().replace(microsecond=0)

    # 设置双方是否首次聊天
    conversation_cache.set(
        "message:last_send_time:{}:{}".format(send_user.id, target_user.id),
        str(send_time)
    )

    body = json.dumps(body, ensure_ascii=False)
    # 存储私信: 双写到mysql和es
    message = conversation.send_message(user=send_user, type_=msg_type, content=pure_text_content,
                                        send_time=send_time, body=body, is_system=is_system, kyc_type=kyc_type,
                                        source=source)
    res['msg_id'] = msg_id = message.id
    res['msg'] = msg = {
        'id': msg_id,
        'user_id': send_user.id,
        'send_time': send_time,
        'type': msg_type,
        'content': content,
    }
    res['message'] = message
    conversation_extra = build_conversation_extra(
        conversation_type=conversation_type)
    es_res = write_msg_to_es(msg_id=msg_id,
                             user_id=send_user.id,
                             conversation_id=conversation.id,
                             conversation_extra=conversation_extra,
                             send_time=send_time,
                             msg=msg)
    message_signals.post_send_message(conversation=conversation)
    # 写es失败 返回异常
    if not es_res['succ']:
        res['error'] = CODES.MESSAGE_SAVING_FAILED
        return res

    # 检查报警
    from api.tool.voice_alert import dispatch_voice_alert_task
    dispatch_voice_alert_task(message, target_user)

    # logging
    try:
        ctx = get_current_context_or_throw_exception()
        ctx.logger.app(
            msg_id=msg_id,
            time=int(time.time()* 1000),
        )
    except BaseException:
        logging_exception()

    # 发送任务到提醒推送
    # 7750 发送推送逻辑更改
    # 之前的逻辑  注意：JMessage != Jpush Message
    # Android是发送Jpush Notification, 因为安卓设备就算关闭了推送也能接收Notification进行处理
    # 但是Ios 设置关闭了推送就接收不到Notification, 所以当时接入JMessage 建立长链接进行私信消息的推送
    # 总结起来就是 Android Jpush Notification, Ios Jpush Notification + JMessage(IM)
    # 7750 之后的逻辑
    # 因为Jpush 有 Notification 和 Message(自定义消息|应用内消息)
    # Ios关闭了推送还是可以收到Jpush 中的Message类型，
    # 并且在重新打开App时， 极光SDK 会自动拉取离线Jpush 中的Message ，所以不会有Message丢失的情况
    # Jpush Notification 主要是push通知, Jpush Message 主要是站内逻辑处理
    # 总结起来就是 Android/Ios Jpush Notification + Jpush Message
    if need_jpush and target_user:
        # 用户版发送Jpush Notification + Jpush Message
        # 医生推送 Jpush Notification 医生版暂时还没有接入极光JMessage(JIM)
        alert = Message.push_alert_text(type=msg_type, content=pure_text_content, user=send_user)
        send_new_message_push(target_user.id, msg_id, alert, silent)
        # send_new_message_push_v1.apply_async(args=(current_trace_id(), target_user.id, msg_id, alert, silent), priority=0)
        log_message_trace(MessageTraceEventType.TRACE_GAIA_MESSAGE_JPUSH_TASK_SENDED, {
            "target_user_id": target_user.id,
            "msg_id": msg_id,
            "arert": alert,
            "slient": silent
        })
        # JMessage(JIM) 7750之后就不会再使用JMessage 但是不能删除 旧版本还是需要JIM来进行通讯的
        conversation_user_ids = conversation.user_ids()
        extras = build_conversation_push_extra(
            conversation_id=conversation.id,
            msg_id=msg_id,
            conversation_type=conversation_type,
            user_ids=conversation_user_ids,
            text=Message.content_display(type=msg_type, content=pure_text_content, user=send_user),
            send_user=send_user,
            last_reply_time=message.send_time,
        )
        send_jim_message.delay(target_user.id, extras)
        # send_jim_message_v1.apply_async(args=(current_trace_id(), target_user.id, extras), priority=1)

        log_message_trace(MessageTraceEventType.TRACE_GAIA_MESSAGE_JIM_TASK_SENDED, {
            "target_user_id": target_user.id,
            "extras": extras,
        })

    # send msg to webchat pub if user from wechatpub channel
    if (conversation_type == CONVERSATION_TYPE.MESSAGE and
            target_user and
                target_user.userextra.auth_type == LOGIN_AUTH_TYPE.WechatPub and
                msg_type in (MESSAGE_TYPE.TEXT, MESSAGE_TYPE.SERVICE, MESSAGE_TYPE.IMAGE, MESSAGE_TYPE.DIARY)):
        # check if user has finished converstaion
        k = 'wx_forbidden:%s:%s' % (send_user.id, target_user.id)
        forbidden = wechatpub_cache.get(k)
        if forbidden:
            res['error'] = CODES.MESSAGE_INVALID_CONVERSATION
            return res

        send_wechatpub_message(target_user.id, msg_type=msg_type, content=content)

        # logging
        try:
            ctx = get_current_context_or_throw_exception()
            ctx.logger.app(
                receiver_id=str(target_user.id),
                sender_id=str(send_user.id),
                service_id=str(content['service_id']),
                event='send_service_link'
            )
        except KeyError:
            pass
        except:
            logging_exception()

    res['error'] = CODES.SUCCESS
    message_sync.delay(conversation, send_user, target_user)

    try:  # 临时任务，监控私信消息内容
        if msg_type == MESSAGE_TYPE.TEXT:
            async_dingtask_monitor_message.delay(msg_id)
    except:
        pass

    return res


def get_last_send_time(send_user_id, target_user_id):
    """ 获取最后一次发送时间 """

    last_send_time = conversation_cache.get(
        "message:last_send_time:{}:{}".format(send_user_id, target_user_id)
    )
    if last_send_time:
        return last_send_time

    # 读取数据库判断
    conversation = Conversation.objects.get_conversation(send_user_id, target_user_id, conversation=CONVERSATION_TYPE.MESSAGE)
    m = Message.objects.filter(conversation_id=conversation.id, user_id=send_user_id).last()

    if not m:
        return None

    send_time = str(m.send_time)

    # 设置双方是否首次聊天
    conversation_cache.set(
        "message:last_send_time:{}:{}".format(send_user_id, target_user_id),
        send_time, ex=60 * 60 * 24 * 3
    )
    return send_time
