# -*- coding: utf-8 -*-

from __future__ import unicode_literals, absolute_import, print_function

import datetime
import json
import random

from django.conf import settings
from django.db.models import F
from django.db.models import Q
from django.utils import timezone
from gm_rpcd.all import bind
from gm_types.antispam import DocType
from gm_types.doctor import DOCTOR_TITLE_TYPE
from gm_types.error import ERROR as CODES
from gm_types.gaia import CONST_STRINGS as const_strings
from gm_types.gaia import FILTER_WORD_TYPE
from gm_types.push import AUTOMATED_PUSH
from gm_types.gaia import (
    QUESTION_TYPE,
    QUESTION_ORDER_TYPE,
    USER_TYPE,
    TOP_TYPE,
    PLATFORM_CHOICES,
    HOSPITAL_TYPE,
    ANSWER_ORDER_TYPE,
    PLATFORM_CHANNEL,
    DOCTOR_PROCESS_STATUS
)
from gm_types.mimas import QUESTION_SOURCE, QA_CONTENT_TYPE, MEDIA_IMAGE_URL_SOURCE, ATTENTION_NOTIFY_TYPE
from gm_types.mimas.qa import (
    QUESTION_TYPE as QA_QUESTION_TYPE,
    VIDEO_SOURCE_TYPE,
)
from gm_types.push import PUSH_INFO_TYPE
from gm_types.user_hierarchy import EventType
from six.moves import xrange

from live.tasks import get_qiniu_persistent_ids
from qa.cache import (
    high_quality_question_cache,
    question_inviter_cache,
    complains_question_cache,
)
from qa.cache.manager import AnswerSortCacheManager
from qa.libs import (
    get_unread_count_by_user_id,
    get_answers_by_ids,
    get_answer_reply_by_pks,
    get_unread_answer_info_by_user_id,
    get_unread_answer_vote_num_by_user_id,
    get_answer_vote_infos_by_ids,
    update_answer_reply_read_status,
    _get_content_text,
)
from social.models import SocialInfo
from qa.models import Answer, AnswerVote, AnswerVoteReply, BDAnswer
from qa.models import AnswerReply, OverHeadAnswer, OverHeadQuestion
from qa.models import Question, QuestionImage, QuestionTag, AnswerTop
from qa.models import UserAnswerQuestion, QuestionInviter
from talos.logger import qiniu_logger
from talos.models.doctor import DoctorMessageList
from talos.logger import info_logger
from qa.serializers import (
    QuestionSerializer,
    AnswerSerializer,
    AnswerReplySerializer,
    AnswerVoteSerializer,
    AnswerReplyVoteSerializer,
    AnswerListSerializer,
    QuestionTagSerializer
)
from qa.tasks import (
    add_answer_view,
    add_question_view,
    fake_vote_to_answer,
    check_spam,
    push_to_question_provider,
    comment_push,
    add_doctor_question,
)

from talos.services.doctor import DoctorService
from qa.utils.decorator import listing
from qa.utils.image import get_w_path, get_half_path
from qa.utils.statistic import get_async_args_v2
from qa.utils.time import get_timestamp_epoch
from qa.tools.answer import AnswerTools, QaTool
from talos.portal import (
    get_doctor_by_user_id,
)
from talos.libs.datetime_utils import tzlc
from talos.rpc import bind_context
from talos.tools.filterword_tool import filterword_by_custom
from talos.services import (
    UserService,
    get_user_from_context,
    UserConvertService,
)
from user_hierarchy.portal import process_task

from utils.common import get_data_from_rich_text
from utils.protocol import gm_protocol
from utils.push import push_task_to_user_multi, vote_push
from utils.push import special_push_limit, push_time_range_limit
from utils.rpc import gen, rpc_client, get_current_user, logging_exception
from utils.stat_log import SocialStatLogForUserAction
from utils.tasks import common_push, inviter_answered_push_to_user
from ..libs import (
    get_qa_content_text,
    format_qa_content,
    get_media_info_from_content,
    get_media_extra_info,
)
from social.models import UserFollow
from talos.tools.vote_tool import VoteTool as NewVoteTool
from talos.cache.base import vote_cache as new_vote_cache
from talos.tasks.follow_msg_create import create_information_db


@bind("qa/question/create")
def create_question(title, content, tags, images, city=None, in_white_list=False, is_invite_doctor=False, platform=None, question_type=QA_QUESTION_TYPE.TRADE):
    user = get_current_user()

    # add tag of 大杂烩 if in_white_list is True
    if in_white_list:
        tags.append(settings.MULTI_TOPIC_TAG_ID)
        tags = list(set(tags))

    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    #创建问题部分，增加了对内容和标题的同时过滤,由于图文混排，需要过滤出content中的文本部分
    filter_content = get_qa_content_text(platform, content)
    filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_CONTENT, content=filter_content + title)

    tags_dict = [{'tag': tag} for tag in tags]
    images = [{'image_url': image['image']} for image in images]
    data = {'user': user.id, 'title': title, 'content': content, 'city_id': city, 'images': images,
            'tags': tags_dict, 'is_invite_doctor': is_invite_doctor, 'question_type': question_type}
    if platform:
        data['platform'] = platform
        # 仅分离出文本内容做关键词校验  7675版本支持图文混排
        data['content'] = get_qa_content_text(platform, content)
        media_info_dic = get_media_info_from_content(platform, content)
        _image_list = get_media_extra_info(media_info_dic.get("images_list", []))
        for image_dic in _image_list:
            image_dic["image_url_source"] = MEDIA_IMAGE_URL_SOURCE.RICH_TEXT
        data["images"] = _image_list

    serializer = QuestionSerializer(data=data)
    if not serializer.is_valid(raise_exception=False):
        return gen(CODES.CONTENT_CREATE_ERROR)
    convert_content_dict = format_qa_content(platform, content)  # 格式化content 内容
    question = serializer.save(**convert_content_dict)
    # 粉丝消息页面通知数据保存到db
    result = create_information_db.delay(
        user_id=user.id, source_type=ATTENTION_NOTIFY_TYPE.QUESTION, business_id=question.id,
    )
    info_logger.info('开始保存给粉丝的消息:{},问题id:{}'.format(result.task_id, question.id))

    check_spam.delay(question.id, ['title', 'content'], DocType.QUESTION)
    # push to questioner fans
    common_push.delay(uname=user.nick_name, uid=user.id, topic_id=question.id, push_type="question")
    add_doctor_question.apply_async((question.id,),
                                    eta=tzlc(datetime.datetime.now()) + datetime.timedelta(seconds=3 * 60))

    # 问题富文本内的视频处理, 需要处理成带水印的
    if question.content_type == QA_CONTENT_TYPE.VIDEO:
        _, video_list = get_data_from_rich_text(question.content, u'//video[not(@name="new_video")]/@src')
        if video_list:
            get_qiniu_persistent_ids.delay(
                source_id=question.id,
                video_type=VIDEO_SOURCE_TYPE.QUESTION,
                url_list=video_list
            )

    return {'question_id': question.id}


@bind("qa/answer/create")
def create_answer(content, images, question_id, platform=None, platform_channel=None):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    # 问题回答部分，对回答内容进行过滤，以前的逻辑对数字进行了过滤，本次沿用
    content_data = get_qa_content_text(platform, content)

    filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_CONTENT, content=content_data)
    images = [{'image_url': image['image']} for image in images]
    data = {'user': user.id, 'content': content, 'question': question_id, 'images': images}
    try:
        doctor = get_doctor_by_user_id(user.id)
    except IndexError:
        doctor = None
    if doctor:  # doctor object or None
        data.update({"doctor_id": doctor.id, "doctor_title": doctor.title or DOCTOR_TITLE_TYPE.EMPTY})
    if platform:
        data['platform'] = platform
        data['content'] = get_qa_content_text(platform, content)
        media_info_dic = get_media_info_from_content(platform, content)
        _image_list = get_media_extra_info(media_info_dic.get("images_list", []))
        for image_dic in _image_list:
            image_dic["image_url_source"] = MEDIA_IMAGE_URL_SOURCE.RICH_TEXT
        data["images"] = _image_list
    serializer = AnswerSerializer(data=data)

    if not serializer.is_valid(raise_exception=False):
        gen(CODES.CONTENT_CREATE_ERROR)

    convert_content_dict = format_qa_content(platform, content)
    serializer.save(**convert_content_dict)

    countdowns, incr_range = get_async_args_v2(const_strings.CREATE_ANSWER_VOTE)
    #Todo需要修改枚举值
    for cd in countdowns:
        task_info = fake_vote_to_answer.apply_async(args=(1, serializer.data['id']), kwargs={"force_push": True}, countdown=cd)
        from talos.logger import qiniu_logger
        qiniu_logger.info("task_id====={task_id}".format(task_id=task_info.task_id))

    if doctor:
        DoctorMessageList.objects.filter(question_id=question_id, doctor_id=doctor.id).\
            update(process_status=DOCTOR_PROCESS_STATUS.processed)

    #  当我回答完被邀请的问题以后，修改邀请数据表里的满足要求的状态
    query = Q(inviter=user.id, question_id=question_id, answer_id__isnull=True)
    inviters = QuestionInviter.objects.filter(query)
    if inviters.exists():
        need_push_user_ids = list(inviters.values_list("user", flat=True))  # 被邀请者回答完问题，即刻推送给邀请者
        inviters.update(answer_id=serializer.data['id'])
        inviter_answered_push_to_user.delay(user_ids=need_push_user_ids, answer_id=serializer.data['id'],
                                            nickname=user.nick_name)

    answer_sort_cache_manager = AnswerSortCacheManager()
    answer_sort_cache_manager.calc_cache(question_id)
    check_spam.delay(serializer.data['id'], ['content'], DocType.ANSWER)
    # push to the question provider
    question_title = ''
    try:
        question = Question.objects.get(id=question_id)
        if push_time_range_limit():
            push_to_question_provider(
                user_id=question.user.id, answer_id=serializer.data['id'],
                question_title=question.title, user_name=user.nick_name)
            question_title = question.title
    except Question.DoesNotExist:
        logging_exception()
    # push to answerer fans
    common_push.delay(uname=user.nick_name, uid=user.id,
                      topic_id=serializer.data['id'], push_type="answer", question_title=question_title)

    # 处理富文本中的视频
    _answer_info = serializer.data
    if _answer_info["content_type"] == QA_CONTENT_TYPE.VIDEO:
        _, video_list = get_data_from_rich_text(_answer_info["content"], u'//video[not(@name="new_video")]/@src')
        if video_list:
            get_qiniu_persistent_ids.delay(
                source_id=_answer_info["id"],
                video_type=VIDEO_SOURCE_TYPE.ANSWER,
                url_list=video_list
            )

    data = {"answer_id": serializer.data['id']}

    event_data = process_task(user_id=user.id, event_type=EventType.ANSWER_QUESTION, related_item=serializer.data['id'])
    growth_value, point_value = event_data['growth_value'], event_data['point_value']
    submit_count = event_data['submit_count']
    data['growth_value'] = growth_value
    data['point_value'] = point_value
    data['submit_count'] = submit_count
    return data


@bind('qa/answer/create_reply')
def create_answer_reply(content, answer_id, answer_reply_id=None, platform_channel=None):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    # 这里是回复限制部分的评论的评论，沿用以前对数字进行过滤的逻辑
    filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_REPLY, content=content)
    data = {
        'user': user.id,
        'content': content,
        'answer': answer_id,
        'commented_reply': answer_reply_id
        }
    serializer = AnswerReplySerializer(data=data)

    if not serializer.is_valid(raise_exception=False):
        return gen(CODES.CONTENT_CREATE_ERROR)
    is_commented_reply = True if data.get('commented_reply') else False
    answer_reply = serializer.save()

    try:
        doctor = get_doctor_by_user_id(user.id)
    except IndexError:
        doctor = None
    if doctor:
        DoctorMessageList.objects.filter(
            question_id=answer_reply.answer.question_id,
            doctor_id=doctor.id
        ).update(process_status=DOCTOR_PROCESS_STATUS.processed)

    data = dict()
    data['reply_data'] = answer_reply.reply_data_after_create()
    data['comment_data'] = answer_reply.comment_data_after_create()
    check_spam.delay(answer_reply.id, ['content'], DocType.COMMENT)

    # 给被评论的人发送push请求 没有被评论的默认为回答人
    push_user_id = data['comment_data'].get('at_user_id')\
        if is_commented_reply else data['reply_data'].get('reply_user_id')
    if user.id != push_user_id and push_time_range_limit():
        comment_push(
            answer_id=answer_id,
            comment_id=answer_reply.id,
            author_name=user.nick_name,
            content=content,
            user_id=push_user_id,
            is_commented_reply=is_commented_reply
        )

    event_data = process_task(
        user_id=user.id,
        event_type=EventType.COMMENT_OTHER,
        related_item=answer_reply.id
    )

    data['growth_value'] = event_data['growth_value']
    data['point_value'] = event_data['point_value']
    data['submit_count'] = event_data['submit_count']

    _answer = Answer.objects.filter(pk=answer_id).only("question_id").first()
    # 用户行为埋点 评论相关
    SocialStatLogForUserAction.stat_log_for_reply(
        content_type=SocialStatLogForUserAction.CONTENT_TYPE.question,
        user_id=user.id,
        content_id=_answer.question_id
    )
    return data


@bind('qa/answer/vote')
def answer_vote(answer_id):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    data = {'user': user.id, 'answer': answer_id}
    serializer = AnswerVoteSerializer(data=data)

    if not serializer.is_valid(raise_exception=False):
        return gen(CODES.DIARY_HAS_VOTED)

    vote = serializer.save()
    vt_v1 = NewVoteTool(new_vote_cache, vote.answer.user_id, new_version=True)
    vt_v1.receive_answer_vote(vote.id)

    rpc_client['api/person/incr_vote'](user_id=vote.answer.user_id).unwrap()

    countdowns, incr_range = get_async_args_v2(const_strings.ANSWER_VOTE)
    for cd in countdowns:
        is_vote_push = True if random.randint(0, 100) > 80 else False
        fake_vote_to_answer.apply_async(args=(1, answer_id), kwargs={"force_push": is_vote_push}, countdown=cd, expires=cd+1200)

    tag_ids = list(qt.tag.id for qt in vote.answer.question.tags if qt.tag)
    data = serializer.data
    data['tag_ids'] = tag_ids

    event_data = process_task(user_id=user.id, event_type=EventType.VOTE_MULTI_TIMES, related_item=data['id'])
    growth_value, point_value = event_data['growth_value'], event_data['point_value']
    submit_count = event_data['submit_count']
    data['growth_value'] = growth_value
    data['point_value'] = point_value
    data['submit_count'] = submit_count

    # 7715之后添加回答被赞的push操作
    push_url = gm_protocol.get_answer_list(answer=answer_id)
    answer = Answer.objects.get(id=answer_id)
    content = _get_content_text(answer.content)
    content = (content[:25] + '...') if len(content) > 25 else content
    vote_push(
        user_id=vote.answer.user_id,
        push_url=push_url,
        alert=u'{}赞了你的回复{}'.format(user.nick_name, content),
        push_type=AUTOMATED_PUSH.ANSWER_GET_VOTE
    )
    # 用户行为埋点 点赞相关
    SocialStatLogForUserAction.stat_log_for_like(
        content_type=SocialStatLogForUserAction.CONTENT_TYPE.question,
        user_id=user.id,
        content_id=answer.question_id
    )

    return data


@bind('qa/answer/unvote')
def answer_unvote(answer_id):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        vote = AnswerVote.objects.get(user=user.id, answer_id=answer_id)
        vote.delete()

        answer = Answer.objects.get(id=answer_id)
        answer.like_num = F('like_num') - 1
        answer.save(update_fields=['like_num'])
        rpc_client['api/person/decr_vote'](user_id=answer.user_id).unwrap()
    except AnswerVote.DoesNotExist:
        return gen(CODES.OPERATION_NOT_SUPPORTED)

    return {}


@bind('qa/answer_reply/vote')
def answer_reply_vote(answer_reply_id):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    data = {'user': user.id, 'answerreply': answer_reply_id}

    serializer = AnswerReplyVoteSerializer(data=data)
    if not serializer.is_valid(raise_exception=False):
        return gen(CODES.OPERATION_NOT_SUPPORTED)

    serializer.save()

    re_data = serializer.data
    reply = AnswerReply.objects.filter(id=int(answer_reply_id)).first()
    vt_v1 = NewVoteTool(new_vote_cache, reply.user_id, new_version=True)
    vt_v1.receive_answer_reply_vote(reply.id)

    # 用户行为埋点 点赞相关
    _answer = Answer.objects.filter(pk=reply.answer_id).only("question_id").first()
    SocialStatLogForUserAction.stat_log_for_like(
        content_type=SocialStatLogForUserAction.CONTENT_TYPE.question,
        user_id=user.id,
        content_id=_answer.question_id
    )

    event_data = process_task(user_id=user.id, event_type=EventType.VOTE_MULTI_TIMES, related_item=re_data['id'])
    growth_value, point_value = event_data['growth_value'], event_data['point_value']
    submit_count = event_data['submit_count']
    re_data['growth_value'] = growth_value
    re_data['point_value'] = point_value
    re_data['submit_count'] = submit_count

    return re_data


@bind('qa/answer_reply/unvote')
def answer_reply_unvote(answer_reply_id):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    try:
        vote = AnswerVoteReply.objects.get(user=user.id, answerreply_id=answer_reply_id)
        vote.delete()

        answer = AnswerReply.objects.get(id=answer_reply_id)
        answer.like_num = F('like_num') - 1
        answer.save(update_fields=['like_num'])
    except AnswerVoteReply.DoesNotExist:
        return gen(CODES.OPERATION_NOT_SUPPORTED)

    return {}


@bind('qa/question/list')
@listing()
def question_list(start_num, count, refresh_count):
    user = get_current_user()

    data = []

    if refresh_count > 0 and start_num == 0:
        start_num = 10 * (refresh_count - 1)
        filters = {'has_recommended_answer': True}
        kwargs = {'offset': start_num, 'size': 10, 'sort_type': QUESTION_ORDER_TYPE.DROPDOWN_REFRESH, 'filters': filters}
    else:
        kwargs = {
            'offset': start_num, 'size': count + 5,
        }

    questions = rpc_client['api/question/filter'](**kwargs).unwrap()['question_ids']

    # 新增过滤置顶问答的内容
    questions_top = AnswerTop.objects.filter(start_time__lte=timezone.now(),
                                             end_time__gte=timezone.now(), enable=True).order_by('order')
    question_top_list = _get_online_answer_top(questions_top)
    top_id_set = {'question': set(), 'answer': set()}
    for item in question_top_list:
        if item.get('type') == QUESTION_TYPE.QUESTION:
            top_id_set['question'].add(item.get('obj').id)
        elif item.get('type') == QUESTION_TYPE.ANSWER:
            top_id_set['answer'].add(item.get('obj').id)

    # v 7.6.40 变更，记录发现页 问答的浏览量
    answer_ids = []
    question_ids = []
    author_ids = set()
    for question in questions:
        if question['answer']:
            if not (question['answer'] in top_id_set['answer']):
                try:
                    answer = Answer.objects.get(id=question['answer'])
                    if answer.user_id in author_ids:
                        continue

                    author_ids.add(answer.user_id)
                except Answer.DoesNotExist:
                    # qa object not exist.
                    pass
                else:
                    d = {
                        'answer': answer.data_for_list(user),
                        'type': QUESTION_TYPE.ANSWER,
                        'id': answer.question_id
                    }
                    data.append(d)
                    answer_ids.append(answer.id)

        elif question['question'] and (question['question'] not in top_id_set['question']):
            try:
                question = Question.objects.get(id=question['question'])
                if question.user_id in author_ids:
                    continue

                author_ids.add(question.user_id)
            except Question.DoesNotExist:
                # question object not exist
                pass
            else:
                d = {
                    'question': question.data_for_list(user),
                    'type': QUESTION_TYPE.QUESTION,
                    'id': question.id
                }
                data.append(d)
                question_ids.append(question.id)
    add_answer_view.delay(answer_ids=answer_ids)
    add_question_view.delay(question_ids)
    return data


@bind('qa/question/list_exclude_answer')
@listing()
def question_list_exclued_answer(start_num, count):
    data = []
    questions = Question.objects.filter(is_online=True).order_by('-id')
    for question in questions[start_num: start_num + count]:
        data.append(question.data_for_list())
    return {
        'questions': data,
    }


@bind('qa/question/list/discovery')
@listing()
def question_list_discovery(start_num, count, **filters):
    """新版新版本。http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=11599925"""

    fl = filters.get("filters", {})
    fls = {
        'answers_num_gte': 1,
        'answers_level_gte': 3,
    }
    if fl:
        fls.update(fl)

    kwargs = {
        'offset': start_num, 'size': count,
        'sort_type': QUESTION_ORDER_TYPE.DISCOVERY_UPDATE,
        'filters': fls,
    }

    questions = rpc_client['api/question/filter'](**kwargs).unwrap()['question_ids']

    data = []

    # 记录发现页 问答的浏览量
    question_ids = []
    author_ids = set()
    for question in questions:
        try:
            question = Question.objects.get(id=question['question'])
            if question.user_id in author_ids:
                continue
            author_ids.add(question.user_id)
        except Question.DoesNotExist:
            pass
        else:
            d = {
                'question': question.data_for_discovery(),
                'type': QUESTION_TYPE.QUESTION,
                'id': question.id
            }
            data.append(d)
            question_ids.append(question.id)

    add_question_view.delay(question_ids)

    return data


@bind('qa/question/my_list')
@listing()
def my_question(start_num, count):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    # v 7.6.35  排序规则修改 降序排序 由id 变更为 create_time
    questions = Question.objects.filter(user=user.id, is_online=True).order_by('-create_time')[
                start_num: start_num + count]
    data = []
    question_ids = []
    for question in questions:
        data.append(question.data_for_my_list())
        question_ids.append(question.id)
    # v7.6.40 记录 我的问题 浏览量
    add_question_view.delay(question_ids)
    return data


@bind('qa/answer/my_list')
@listing()
def my_answer(start_num, count):
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)
    # v 7.6.35  排序规则修改 降序排序 由id 变更为 create_time
    answers = Answer.objects.filter(user=user.id, is_online=True,
                                    question__is_online=True).order_by('-create_time')[start_num: start_num + count]
    data = []
    # 记录 我的回答 浏览量
    answer_ids = []
    for answer in answers:
        data.append(answer.data_for_list(user))
        answer_ids.append(answer.id)
    add_answer_view.delay(answer_ids=answer_ids)
    return data


@bind("qa/answer/question_info")
def get_question_info_by_answer_id(answer_id, pc_qa_show=False):
    answer = Answer.objects.get(id=answer_id)
    question = answer.question
    _data = {
        'question_id': question.id,
        'question_title': question.title,
        'answer_count': question.answer_num,
        'is_online': question.is_online,
        'answer_content': answer.content,
        'comment_count': answer.comment_num,
        'answer_platform': answer.platform,
    }
    if pc_qa_show:
        _data.update({
            "question_content": question.content,
            "question_images": question.content_images,
            "answer_images": answer.content_images,
            "answer_timestamp": int(answer.create_time.strftime('%s')),
        })

    return _data


@bind("qa/answer/get_answer_reply")
@listing()
def get_answer_reply(answer_id, start_num, count, has_comments=True, need_newest=True,
                     top_comment_id=None, new_order=False, new_response=False):
    """
    v 7.6.95 更新，回答 一级评论的子评论，返回最新回复的两条 7720改成返回时间最早的两条
    :param answer_id:
    :param start_num:
    :param count:
    :param has_comments: 是否取子评论
    :param need_newest: 是否取最新的子评论
    :param top_comment_id: 需要置顶的评论id，7-10-0 置顶后不移除原位置的数据
    :param new_order: 按创建时间排序，True 为正序，False 倒序
    :param new_response: {}
    :return:
    """
    user = get_current_user()
    top_comment = True if top_comment_id else False  # 置顶高亮标识
    try:
        order = 'create_time' if new_order else '-create_time'
        replies = AnswerReply.objects.filter(
            answer_id=answer_id,
            is_online=True,
            commented_reply=None
        ).order_by(order)
        first_reply_count = replies.count()
        replies = replies[start_num: start_num + count]

        if top_comment_id:
            my_reply = AnswerReply.objects.filter(id=top_comment_id, first_reply_id__isnull=True).first()
            replies = list(replies)

            if my_reply and my_reply.first_reply:
                replies = [my_reply.first_reply] + replies
            elif my_reply:
                replies = [my_reply] + replies
            else:
                top_comment = False

    except AnswerReply.DoesNotExist:
        return gen(CODES.ANSWER_NOT_FOUND)

    data = []
    for reply in replies:
        if reply:
            data.append(reply.get_data_for_reply(user, has_comments, need_newest, new_order=new_order))

    if top_comment and len(data) > 0:  # 置顶设置为高亮
        data[0].update({"high_light": 1})

    if new_response:
        return {
            'reply_data': data,
            'first_reply_count': first_reply_count,  # 添加一级评论数
        }

    return data


@bind('qa/answer/get_answer_detail')
def __get_answer_detail(answer_id):
    user = get_current_user()
    try:
        answer = Answer.objects.get(id=answer_id, is_online=True)
    except Answer.DoesNotExist:
        return gen(CODES.ANSWER_NOT_FOUND)

    data = answer.data_for_detail(user)
    data['author'] = answer.data_for_author(user)

    add_answer_view.delay(answer_ids=[answer_id])

    return data


@bind_context('qa/answer/get_answer_by_ids')
def get_answer_detail_by_ids(ctx, answer_ids):
    """
    获取多个answer
    """
    user = get_user_from_context(ctx)
    answers = Answer.objects.filter(id__in=answer_ids, is_online=True)

    data = []
    for answer in answers:
        add_answer_view.delay(answer_ids=[answer.id])
        answer_data = answer.data_for_detail(user)
        answer_data['image'] = answer.get_images()
        answer_data['videos'] = answer.get_videos()

        data.append(answer_data)

    return data


@bind('qa/answer/get_nearby_answer')
def get_next_answer(answer_id):
    pre_id = None
    next_id = None
    question_id = Answer.objects.get(id=answer_id).question_id
    answer_sort_cache_manager = AnswerSortCacheManager()
    sort_ids = answer_sort_cache_manager.get_sort_cache(question_id=question_id)

    for i in range(0, len(sort_ids)):
        if str(sort_ids[i]) == str(answer_id):
            pre_index = i - 1
            next_index = i + 1
            if pre_index < 0:
                pre_id = None
            else:
                pre_id = sort_ids[pre_index]

            if next_index >= len(sort_ids):
                next_id = None
            else:
                next_id = sort_ids[next_index]
            break

    if pre_id == next_id and len(sort_ids) > 1:
        pre_id = sort_ids[-1]

    return {'pre_id': pre_id, 'next_id': next_id}


@bind('qa/question/detail')
def get_question_detail(question_id):
    try:
        question = Question.objects.select_related().get(pk=question_id, is_online=True)
    except Question.DoesNotExist:
        return gen(CODES.QUESTION_NOT_FOUND)

    answer_num = question.answer_num  # 排除已下线问题
    ques_images = QuestionImage.objects.filter(
        question__pk=question_id, image_url_source=MEDIA_IMAGE_URL_SOURCE.CREATE)
    image_data = []
    if ques_images:
        for image in ques_images:
            data = {
                'image_half': get_half_path(image.image_url),
                'image_w': get_w_path(image.image_url)
            }
            image_data.append(data)

    tagSerializer = QuestionTagSerializer(question.tags, many=True)
    tags = [tag['tag'] for tag in tagSerializer.data if tag['tag']]
    result = {
        'id': question_id,
        'title': question.title,
        'content': question.content,
        'is_online': question.is_online,
        'post_date': question.create_time.strftime('%y-%m-%d'),
        'answer_count': answer_num,
        'image': image_data,
        'tags': tags,
        'platform': question.platform,
        'create_time': get_timestamp_epoch(question.create_time),
        'view_num': question.view_amount,
    }
    # 加入分享内容 数据返给backend 由backend做标签处理
    recommand_answer = question.recommend_answer
    if recommand_answer:
        result['share_answer'] = recommand_answer.content
    else:
        answer_most_like = Answer.objects.filter(question_id=question_id,
                                                 is_recommend=False, is_online=True).order_by('-like_num', 'id')
        result['share_answer'] = answer_most_like[0].content if answer_most_like else ''
    add_question_view.delay(question_ids=[question_id])
    return result


@bind('qa/question/author')
def get_author(question_id, return_doctor_info=False):
    try:
        author = Question.objects.get(pk=question_id).user
    except Question.DoesNotExist:
        return gen(CODES.QUESTION_NOT_FOUND)

    result = {
        'user_id': author.id,
        'user_type': USER_TYPE.NORMAL,
        'user_name': author.nickname,
        'user_portrait': author.portrait,
        # add 7.2.5
        'membership_level': author.membership_level,
        'user_level': {
            'membership_icon': author.membership_icon,
            'level_icon': author.level_icon,
            'constellation_icon': author.constellation_icon,
        },
    }
    if return_doctor_info:
        user_dict = UserConvertService.get_user_info_by_user_id(author.id)
        result.update({
            "doctor_id": user_dict.get("doctor_id", ""),
            "doctor_type": user_dict.get("doctor_type", ""),
            "hospital_id": user_dict.get("hospital_id", ""),
        })
    return result


@bind('qa/question/current_user')
def _get_current_user():
    return {
        'current_user': get_current_user()
    }


@bind('qa/answer/detail')
def get_answer_detail(answer_id):
    try:
        answer_data = Answer.objects.select_related().get(pk=answer_id, is_online=True)
    except Answer.DoesNotExist:
        return gen(CODES.ANSWER_NOT_FOUND)

    question_id = answer_data.question.id
    answer_num = Answer.objects.filter(question__id=question_id, is_online=True).count()
    belong_question = {
        'question_id': question_id,
        'question_title': answer_data.question.title,
        'answer_count': answer_num,
    }
    result = {
        'belong_question': belong_question,
        'answer_id': answer_id,
        'answer_content': answer_data.content
    }
    return result


@bind('qa/question/answer_list')
@listing()
def get_answer_list_by_question(question_id=None, start_num=0, count=10, time_sequence=ANSWER_ORDER_TYPE.NOT_ORDER_BY_TIME):
    """获取回答列表"""
    try:
        question = Question.objects.get(pk=question_id, is_online=True)
    except Question.DoesNotExist:
        return gen(CODES.QUESTION_NOT_FOUND)
    recommand_answer = question.recommend_answer

    answer_query = Answer.objects.filter(question_id=question_id, is_online=True)
    if time_sequence == ANSWER_ORDER_TYPE.TIME_REDUCE:
        answer_query = answer_query.order_by("-id")
    elif time_sequence == ANSWER_ORDER_TYPE.TIME_RISE:
        answer_query = answer_query.order_by("id")
    else:
        answer_query = Answer.objects.filter(question_id=question_id,
                                             is_recommend=False,
                                             is_online=True).order_by("rank", '-like_num', 'id')
    answers = []
    if start_num == 0 and time_sequence == ANSWER_ORDER_TYPE.NOT_ORDER_BY_TIME:
        if recommand_answer:
            answers.append(recommand_answer)
            answers += list(answer_query[0: count - 1])
        else:
            answers = list(answer_query[0: count])
    else:
        if recommand_answer and time_sequence == ANSWER_ORDER_TYPE.NOT_ORDER_BY_TIME:
            start_num -= 1
        answers += list(answer_query[start_num: start_num + count])
    # 记录问题详情页，回答列表浏览量
    ids = [answer.id for answer in answers]
    add_answer_view.delay(answer_ids=ids)

    current_user = get_current_user()
    return AnswerTools.get_questions_data(current_user, answers)

@bind('qa/question/hot_answer_list')
def get_hot_answer_list_by_question(question_id):
    """
    获取热门回复列表
    :param question_id:
    :return:
    """
    try:
        question = Question.objects.get(pk=question_id, is_online=True)
    except Question.DoesNotExist:
        return gen(CODES.QUESTION_NOT_FOUND)
    hot_answers = []
    recommand_answer = question.recommend_answer
    # 对热门回复进行过滤
    hot_answers_query = Answer.objects.filter(question_id=question_id,
                                              is_recommend=False,
                                              is_online=True,
                                              like_num__gte=4).order_by("rank", '-like_num', '-id')[:3]
    # 取前三个回复
    if recommand_answer:
        hot_answers.append(recommand_answer)
        hot_answers += list(hot_answers_query[0:2])
    else:
        hot_answers = list(hot_answers_query[0:3])
    # 后两个位置，取最新回复的且赞数达到2个的回答
    last_answer_query = Answer.objects.filter(question_id=question_id,
                                              is_recommend=False,
                                              is_online=True,
                                              like_num__gte=2).order_by('-update_time')[:5]
    for i in list(last_answer_query):
        if i not in hot_answers and len(hot_answers) < 5:
            hot_answers.append(i)

    current_user = get_current_user()
    return AnswerTools.get_questions_data(current_user, hot_answers)


@bind('qa/question/get_cards_info_by_answer_ids')
def get_question_card_by_answer_ids(ids):
    return get_answers_by_ids(ids)


@bind('qa/question/get_user_qa_list')
@listing()
def get_user_qa_list(user_id, start_num=0, count=10):
    """
    每次取十个，如果下线则直接去掉
    """
    result = []
    qa_list = UserAnswerQuestion.objects.filter(user=user_id, is_online=True).order_by('-create_time')[start_num:start_num + count]
    if not qa_list:
        return []
    answer_ids = []
    question_ids = []
    for qa in qa_list:
        if qa.answer and qa.answer.is_online and qa.answer.question.is_online:
            answer_ids.append(qa.answer_id)
            _detail = qa.answer.data_for_list()
            result.append({
                'type': QUESTION_TYPE.ANSWER,
                'answer_id': _detail['answer_id'],
                'title': _detail['title'],
                'comment_num': _detail['comment_num'],
                'vote_num': _detail['vote_num'],
                'content': _detail['content'],
                'is_voted': _detail['is_voted'],
                'question_id': _detail['question_id'],
                'platform': _detail['platform'],
            })

        elif qa.question and qa.question.is_online:
            question_ids.append(qa.question_id)
            _detail = qa.question.data_for_list()
            result.append({
                'type': QUESTION_TYPE.QUESTION,
                'answer_num': _detail['answer_num'],
                'title': _detail['title'],
                'question_id': _detail['question_id'],
                'platform': _detail['platform'],
            })
    add_question_view.delay(question_ids)
    add_answer_view.delay(answer_ids)
    return result


@bind('qa/question/get_user_qa_count')
def get_user_qa_count(user_id):
    """
    通过user_id 计算当前用户发布了多少回答
    update v 7.6.75 回答逻辑更改，需要再判断该回答对应的问题在线状态.
    :param user_id:
    :return:
    """
    query = Q(user=user_id, is_online=True)
    answer_query = query & Q(question__is_online=True)
    question_count = Question.objects.filter(query).count()
    answer_count = Answer.objects.filter(answer_query).count()

    return question_count + answer_count


@bind('qa/question/top')
def question_list_top(num):
    """
    问答置顶
    v 7.6.40 增加记录发现页 top3中 问答浏览量
    :param num: 获取置顶的数量
    :return: 生效时间大于当前，结束时间之前，顺序排序前3
    """
    user = get_current_user()

    data = []
    answer_ids = []
    question_ids = []
    current_time = timezone.now()
    questions_top = AnswerTop.objects.filter(
        start_time__lte=current_time, end_time__gte=current_time, enable=True
    ).exclude(top_type=TOP_TYPE.SLIDE).order_by('order')
    question_top_list = _get_online_answer_top(questions_top, num)
    for item in question_top_list:
        if item.get('type') == QUESTION_TYPE.ANSWER:
            d = {
                'answer': item.get('obj').data_for_list(user),
                'type': item.get('type'),
                'id': item.get('obj').question_id,
                'top_type': item.get('top_type')
            }
            answer_ids.append(item.get('obj').id)

        elif item.get('type') == QUESTION_TYPE.QUESTION:
            d = {
                'question': item.get('obj').data_for_list(user),
                'type': item.get('type'),
                'id': item.get('obj').id,
                'top_type': item.get('top_type')
            }
            question_ids.append(item.get('obj').id)
        data.append(d)
    # 记录发现页 top3 问答的浏览量
    add_answer_view.delay(answer_ids=answer_ids)
    add_question_view.delay(question_ids)
    return data


@bind('qa/answer/tag_related_question')
def get_tag_related_question(answer_id):
    """
    获取答案的问题的标签相关推荐的问题,返回3个
    :return:
    """
    re_data = {}
    question_info = ''
    try:
        question = Answer.objects.get(id=answer_id, is_online=True).question
    except Answer.DoesNotExist:
        return None
    tag_ids = [tag.tag_id for tag in question.tags.all()]

    if tag_ids:
        question_all_recommend = Question.objects.filter(is_online=True,
                                                         is_recommend=True).values_list('id', flat=True)
        question_answer_recommend = Answer.objects.filter(is_online=True,
                                                          is_recommend=True).values_list('question_id', flat=True)
        question_ids = set(list(question_all_recommend) + list(question_answer_recommend))
        if question.id in question_ids:
            question_ids.remove(question.id)
        if question_ids:
            question_quesyset_ids = list(QuestionTag.objects.filter(question__in=question_ids,
                                                                    tag__in=tag_ids).values_list('question_id',
                                                                                                 flat=True))

            question_quesyset = Question.objects.filter(id__in=question_quesyset_ids, is_online=True)
            indexs = question_quesyset.count()
            sample_index = random.sample(xrange(indexs), 3) if indexs >= 3 else random.sample(xrange(indexs), indexs)

            question_info = [
                question_quesyset[i] for i in sample_index
            ]

    if question_info:
        re_data['type'] = u'相关问题'
        re_data['data'] = [{'id': item.id, 'title': item.title, 'answer_count': item.answer_num}
                           for item in question_info]
    else:
        re_data['type'] = u'优质问题'
        ids = json.loads(high_quality_question_cache.get('high_quality'))
        if ids:
            if question.id in ids:
                ids.remove(question.id)

            if len(ids) >= 3:
                sample_index = random.sample(ids, 3)
            else:
                sample_index = random.sample(ids, len(ids))

            questions = Question.objects.filter(id__in=sample_index, is_online=True)
            re_data['data'] = [{'id': item.id, 'title': item.title, 'answer_count': item.answer_num}
                               for item in questions]
        else:
            re_data['data'] = []
    # v 7.6.40 记录问题浏览量
    question_ids = [q["id"] for q in re_data.get("data", [])]
    add_question_view.delay(question_ids)
    return re_data if re_data.get('data') else None


@bind('qa/answer_reply/list')
def qa_answer_reply_list(pks):
    """
    get qa reply list by pks.    first get info，after update is_read
    :param pks:
    :return:
    """
    user = get_current_user()
    data = get_answer_reply_by_pks(pks)
    # update to is_read
    update_answer_reply_read_status(user.id, pks)
    return data


@bind('qa/answer/read')
def qa_answer_read():

    user = get_current_user()
    if not user:
        return

    my_question_ids = list(Question.objects.filter(user=user.id, is_online=True).values_list("id", flat=True))
    Answer.objects.filter(
        question_id__in=my_question_ids,
        is_online=True,
        questioner_read=False
    ).update(questioner_read=True)

    QuestionInviter.objects.filter(
        user=user.id,
        user_read=False
    ).update(user_read=True)

@bind('qa/question/list_for_es')
def get_question_list_for_es(pks):
    """

    :param pks:
    :return:
    """
    questions = Question.objects.filter(id__in=pks)
    data = {str(question.id): question.data_for_es() for question in questions}
    #  v 7.6.40 记录问答浏览量 如果该问题下有回答，会按照第一个回答的信息获取
    question_ids = [int(question["question_id"]) for question in data.values() if question["question_id"]]
    answer_ids = [int(question["answer_id"]) for question in data.values() if question["answer_id"]]
    add_question_view.delay(question_ids)
    add_answer_view.delay(answer_ids)
    return data


@bind('qa/answer/list_for_es')
def get_answer_list_for_es(pks):
    """

    :param pks:
    :return:
    """
    answers = Answer.objects.filter(id__in=pks, question__is_online=True)
    data = {str(answer.id): answer.data_for_es() for answer in answers}
    #  v 7.6.40 记录回答浏览量
    ids = list(map(int, data.keys()))
    add_answer_view.delay(ids)
    return data


@bind('qa/question/zone_recommend')
def get_zone_related_qa(zone_id):
    """
    圈子推荐
    :return:
    """
    user = get_current_user()
    overHeadAnswers = OverHeadAnswer.objects.filter(zone=zone_id, deleted=False)
    overHeadQuestions = OverHeadQuestion.objects.filter(zone=zone_id, deleted=False)

    obj_list = []
    for answer in overHeadAnswers:
        obj_list.append({
            'question': '',
            'answer': answer.answer,
            'rank': answer.rank
        })
    for question in overHeadQuestions:
        obj_list.append({
            'question': question.question,
            'answer': '',
            'rank': question.rank
        })
    obj_list = sorted(obj_list, key=lambda x: x['rank'])
    result = []
    answer_ids = []
    question_ids = []
    for obj in obj_list:
        if obj['question']:
            question = obj['question']
            question_ids.append(question.id)
            result.append({
                'question': question.data_for_list(user),
                'type': QUESTION_TYPE.QUESTION,
                'id': question.id
            })
        elif obj['answer']:
            answer = obj['answer']
            answer_ids.append(answer.id)
            if answer.question.is_online:
                result.append({
                    'answer': answer.data_for_list(user),
                    'type': QUESTION_TYPE.ANSWER,
                    'id': answer.question_id
                })
    # v 7.6.40 记录问答浏览量
    add_question_view.delay(question_ids)
    add_answer_view.delay(answer_ids)
    return result


@bind('qa/question/tab_choice')
def get_choice_question_with_first_answer(question_pks):
    """
    tab 精选
    :param question_pks:
    :return:
    """
    data = []
    answer_ids = []
    question_ids = []
    user = get_current_user()
    for question in Question.objects.filter(id__in=question_pks):
        answer = question.fisrt_answer
        if answer and question.is_online:
            data.append({
                'answer': answer.data_for_list(user),
                'type': QUESTION_TYPE.ANSWER,
                'id': answer.question_id
            })
            answer_ids.append(answer.id)
        else:
            data.append({
                'question': question.data_for_list(user),
                'type': QUESTION_TYPE.QUESTION,
                'id': question.id
            })
            question_ids.append(question.id)
    # v 7.6.40 记录问答浏览量
    add_question_view.delay(question_ids)
    add_answer_view.delay(answer_ids)
    return data


def _get_online_answer_top(questions_top, num=3):
    """
    返回上线的置顶问答列表id
    :param questions_top: questions_top 条件内有效的置顶数据
    :param num:
    :return:
    [{'type': QUESTION_TYPE., 'obj': Answer obj,'top_type':''},{'type': QUESTION_TYPE.QUESTION, 'obj': Question obj,'top_type':''}]
    """
    count_item = 0
    top_id_list = []
    for item in questions_top:
        if count_item < num:
            if item.answer_id:
                answer = item.answer
                if answer.is_online and answer.question.is_online:
                    top_id_list.append(
                        {'type': QUESTION_TYPE.ANSWER, 'obj': answer, 'top_type': item.top_type})
                    count_item += 1
            elif item.question_id:
                question = item.question
                if question.is_online:
                    top_id_list.append(
                        {'type': QUESTION_TYPE.QUESTION, 'obj': question, 'top_type': item.top_type})
                    count_item += 1

    return top_id_list


@bind('qa/question/get_data_list_by_ids')
def get_question_data_by_ids(ids, new_info_control=False):
    user = get_current_user()
    questions = Question.objects.filter(id__in=ids, is_online=True)

    user_ids = questions.values_list("user", flat=True)
    users_info = UserConvertService.get_user_info_by_user_ids(user_ids)

    data = {}
    for question in questions:
        _data = question.data_for_list(user)
        _data.update({
            "intact_question_images": QaTool.get_intact_images(question, "question_image"),
        })

        if new_info_control:
            _data.update({
                "new_user_info": users_info.get(question.user_id, {}),
            })

        data.update({str(question.id): _data})
    # v 7.6.40 记录问题的浏览量
    question_ids = list(map(int, data.keys()))
    add_question_view.delay(question_ids=question_ids)
    return data


@bind('qa/question/check')
def check_question_for_multi_topic(question_id):
    try:
        q = Question.objects.get(id=question_id)
        tag_list = [tag['id'] for tag in q.tags]
        return True, tag_list
    except Question.DoesNotExist:
        return False, []


@bind('qa/answer/lst_for_task')
def get_answer_for_task(type_q, ids, begin_day=None, end_day=None):
    '''
    问答相关表2异步任务的
    :param type_q: 区分不同表内容
    :param ids:
    :param begin_day: 涨粉(虚拟)每日轮询,查询时间
    :param end_day: 涨粉(虚拟)每日轮询,查询时间
    :return:
    '''
    raw_data = []
    if type_q == 'answer':
        raw_data = Answer.objects.filter(id__in=ids)
    elif type_q == 'question':
        raw_data = Question.objects.filter(id__in=ids)
    elif type_q == 'answerreply':
        raw_data = AnswerReply.objects.filter(id__in=ids)
    elif type_q == 'answer_recommend':
        raw_data = Answer.objects.filter(update_time__range=[begin_day, end_day],
                                         is_recommend=True).values_list('user', flat=True).distinct()
        return list(raw_data)
    re_data = [{'id': item.id, 'is_spam': True if item.spam_label else False, 'content': item.content} for item in
               raw_data]

    return re_data


@bind('qa/foreignkey/question')
def question_list_for_foreignkey(question_ids):
    if not question_ids:
        return []

    questions = Question.objects.filter(pk__in=question_ids)
    return QuestionSerializer(questions, many=True).data


@bind('qa/foreignkey/answer')
def answer_list_for_foreignkey(answer_ids):
    if not answer_ids:
        return []
    answers = Answer.objects.filter(pk__in=answer_ids, question__is_online=True)
    return AnswerSerializer(answers, many=True).data


@bind('qa/answervote/get_unread_count')
def get_unread_count():
    user = get_current_user()
    if not user:
        return 0

    count = get_unread_answer_vote_num_by_user_id(user.id)
    return count


@bind('qa/answervote/read_votes')
def read_votes():
    user = get_current_user()
    if not user:
        return

    AnswerVote.objects.filter(answer__user=user.id, is_fake=False, unread=True).update(unread=False)
    AnswerVoteReply.objects.filter(answerreply__user=user.id, unread=True).update(unread=False)
    return


@bind('qa/answervote/get_info_by_ids')
def get_answer_vote_user_info(vote_ids):
    return get_answer_vote_infos_by_ids(vote_ids)


@bind('qa/answer_reply/get_unread_info')
def get_unread_answer_reply():
    user = get_current_user()
    if user:
        replies_count = get_unread_count_by_user_id(user.id)
    else:
        replies_count = 0

    return replies_count


@bind('qa/question/list_for_index_slide')
@listing()
def get_question_for_index_slide(start_num, count):
    now = timezone.now()
    queryset = AnswerTop.objects.filter(enable=True, top_type=TOP_TYPE.SLIDE, start_time__lt=now,
                                        end_time__gt=now)[start_num: start_num + count]
    data = []
    for item in queryset:
        if item.question_id and item.question.is_online:
            data.append({
                'id': item.id,
                'question_id': item.question_id,
                'title': item.question.title,
            })
        elif item.answer_id and item.answer.is_online and item.answer.question.is_online:
            data.append({
                'id': item.id,
                'question_id': item.answer.question_id,
                'title': item.answer.question.title
            })
    # v 7.6.40 记录问题浏览量
    question_ids = [int(item["question_id"]) for item in data]
    add_question_view.delay(question_ids)
    return data


@bind('qa/answer/list_by_ids')
@listing()
def get_answer_list_by_ids(answer_ids):
    """
    首页推荐回答，Feed策略上线之前，数据为Fake
    :param answer_ids:
    :return:
    """
    answer_ids = list(map(int, answer_ids))
    user = get_current_user()
    queryset = Answer.objects.filter(pk__in=answer_ids, question__is_online=True)
    #  v 7.6.35 记录首页推荐回答的浏览量
    pks = [answer.id for answer in queryset]
    add_answer_view.delay(answer_ids=pks)

    queryset = sorted(queryset, key=lambda answer: answer_ids.index(answer.id))
    return [answer.data_for_list(user) for answer in queryset]


@bind('qa/question_answer/list_by_ids')
@listing()
def get_qa_list_by_ids(answer_ids, new_info_control=False):
    """
    首页推荐回答，Feed策略上线之前，数据为Fake
    :param answer_ids:
    :param new_info_control: 新信息控制
    :return:
    """
    if not answer_ids:
        return []

    answer_ids = list(map(int, answer_ids))
    user = get_current_user()
    queryset = Answer.objects.filter(pk__in=answer_ids, question__is_online=True)

    pks, user_ids, question_ids = [], [], []
    for answer in queryset:
        pks.append(answer.id)
        user_ids.append(answer.user_id)
        question_ids.append(answer.question_id)

    if new_info_control:
        _current_user_id = user and user.id or 0
        user_info_dic = UserConvertService.get_user_info_by_user_ids(user_ids)
        following_dic = QaTool.get_following_info(user_ids, _current_user_id)
        tags_dic = QaTool.get_tags_info_by_question_ids(question_ids)
    else:
        user_info_dic = {}
        following_dic = {}
        tags_dic = {}

    queryset = sorted(queryset, key=lambda answer: answer_ids.index(answer.id))

    answer_sorted_list = []
    for answer in queryset:
        _data = answer.data_for_qa(user)
        _data.update({
            "intact_answer_images": QaTool.get_intact_images(answer, "answer_image"),
            "question_intact_question_images": QaTool.get_intact_images(answer.question, "question_image"),
        })

        if new_info_control:
            _data.update({
                "new_user_info": user_info_dic.get(answer.user_id, {}),
                "is_following": following_dic.get(answer.user_id, False),
                "question_tags": tags_dic.get(answer.question_id, [])
            })

        answer_sorted_list.append(_data)

    #  v 7.6.35 记录首页推荐回答的浏览量
    add_answer_view.delay(answer_ids=pks)

    return answer_sorted_list


@bind('qa/answer/list_for_index_recommend')
@listing()
def get_answer_for_index_recommend(start_num, count):
    """
    首页推荐回答，Feed策略上线之前，数据为Fake
    :param start_num:
    :param count:
    :return:
    """
    user = get_current_user()
    queryset = Answer.objects.order_by('-like_num')[start_num: start_num + count]
    return [answer.data_for_list(user) for answer in queryset]


@bind('qa/question/list_for_index_recommend')
@listing()
def get_question_for_index_recommend(start_num, count):
    """
    首页推荐问题，Feed策略上线之前，数据为Fake
    :param start_num:
    :param count:
    :return:
    """
    user = get_current_user()
    queryset = Question.objects.order_by('-like_num')[start_num: start_num + count]
    return [question.data_for_list(user) for question in queryset]


@bind('qa/notify/recommended')
def notify_recommended_user(uid, answer_id):
    """
    当运营后台设置回答为推荐时，触发该接口通知用户消息
    """
    push_url = gm_protocol.get_answer_list(answer_id)
    extra = {
        'type': PUSH_INFO_TYPE.GM_PROTOCOL,
        'msgType': 4,
        'pushUrl': push_url,
        'push_url': push_url
    }

    push_task_to_user_multi(user_ids=[uid],
                            platform=[PLATFORM_CHOICES.ANDROID, PLATFORM_CHOICES.IPHONE],
                            extra=extra,
                            alert='恭喜美人，你的优质回答已经被所长推荐了哦，请继续加油，一举成为更美红人吧！',
                            push_type=AUTOMATED_PUSH.ANSWER_RECEIVED_COMMENTS)

    rpc_client['api/notification/create'](uid=uid,
                                          title='您的回答被评为优秀至发现首页',
                                          content='恭喜美人，你的优质回答已经被所长推荐了哦，请继续加油，一举成为更美红人吧！',
                                          url=push_url).unwrap()


@bind("qa/answer/reseived_aggregation")
def answer_message_received_user():
    """
    消息 新加回答 信息展示接口 (与回复逻辑雷同)
    :return: result 字典类型，count 总数，last_user_info 最新用户
    """
    result = {
        "count": 0,
        "last_user_info": {},
    }
    user = get_current_user()
    if not user:
        return result

    result.update(get_unread_answer_info_by_user_id(user.id))
    #result["count"] += get_unread_count_by_user_id(user.id)

    return result


@bind("qa/answer/received")
@listing()
def get_my_answer_received(start_num, count=10):
    """
    消息 - 回答 列表页
    :param start_num:
    :param count:
    :return: data list 类型
    """
    user = get_current_user()
    data = []  # 返回给前端的信息
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    # 我邀请的人对邀请回答的问题，做出了回答
    invite_answers = QuestionInviter.objects.filter(user=user.id, answer_id__isnull=False).values("answer_id", "user_read")
    answer_id_state_dic = {answer["answer_id"]: answer["user_read"] for answer in invite_answers}  # 把数据做一次组装
    invited_answer_ids = list(answer_id_state_dic.keys())  # 被邀请人做出回答的id
    my_question_ids = list(Question.objects.filter(is_online=True, user=user.id).values_list("id", flat=True))
    query = Q(is_online=True, question__is_online=True) & (Q(question_id__in=my_question_ids) | Q(id__in=invited_answer_ids))
    answers = Answer.objects.filter(query).order_by("-create_time").distinct()[start_num: start_num + count]

    if not answers:
        return data

    normal_answer_ids = []  # 我提的问题被回答的id
    invite_answer_ids = []  # 我邀请他人回答问题，已回答的id

    for answer in answers:
        item = answer.data_for_received()
        if answer.id in answer_id_state_dic:
            invite_answer_ids.append(answer.id)
        else:
            normal_answer_ids.append(answer.id)
        data.append(item)

    # 只有存在 出现被邀请人回答的id,才会去查邀请者的查看状态
    if invite_answer_ids:
        for item in data:
            answer_id = int(item["answer"]["id"])
            if answer_id in answer_id_state_dic:
                item["answer"]["is_new"] = not answer_id_state_dic[answer_id]

    Answer.objects.filter(id__in=normal_answer_ids, questioner_read=False).update(questioner_read=True)  # 更新已读状态
    QuestionInviter.objects.filter(answer_id__in=invite_answer_ids, user=user.id, user_read=False).update(user_read=True)
    return data


@bind("qa/answer/received_v1")
@listing()
def get_my_answer_received_v1(answer_ids):
    """
    消息 - 回答 列表页
    :param answer_ids:
    :return: data list 类型
    """
    # 7770 需求，回答、回复合并，先从混排缓存中得到对应的数据，然后根据id进行数据获取
    if not answer_ids:
        return {}

    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    invite_answers = QuestionInviter.objects.filter(user=user.id, answer_id__isnull=False).values("answer_id", "user_read")
    answer_id_state_dic = {answer["answer_id"]: answer["user_read"] for answer in invite_answers}  # 把数据做一次组装
    # 我的问题的回答 + 被邀请的问题回答
    answers = Answer.objects.filter(is_online=True, question__is_online=True, pk__in=answer_ids)
    if not answers:
        return {}

    normal_answer_ids = []  # 我提的问题被回答的id
    invite_answer_ids = []  # 我邀请他人回答问题，已回答的id

    data = []
    for answer in answers:
        item = answer.data_for_received()
        item["id"] = answer.id
        if answer.id in answer_id_state_dic:
            invite_answer_ids.append(answer.id)
        else:
            normal_answer_ids.append(answer.id)
        data.append(item)

    # 只有存在 出现被邀请人回答的id,才会去查邀请者的查看状态
    if invite_answer_ids:
        for item in data:
            answer_id = int(item["answer"]["id"])
            if answer_id in answer_id_state_dic:
                item["answer"]["is_new"] = not answer_id_state_dic[answer_id]

    Answer.objects.filter(id__in=normal_answer_ids, questioner_read=False).update(questioner_read=True)  # 更新已读状态
    QuestionInviter.objects.filter(answer_id__in=invite_answer_ids, user=user.id, user_read=False).update(user_read=True)

    return {
        str(item["id"]): item
        for item in data
    }


@bind("qa/question/recommend_answer_list")
@listing()
def get_question_can_answer(start_num, count=10):
    """
    去回答列表页
    :param start_num:
    :param count:
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    result = []
    #  邀请回答的问题，取 今天下午五点到昨天下午五点之间 所有未回答的问题
    today = datetime.datetime.now().replace(hour=17, minute=0, second=0)
    last_day = today - datetime.timedelta(days=1)
    query = Q(inviter=user.id, answer_id__isnull=True, invite_time__range=[last_day, today])
    invited_questions = QuestionInviter.objects.filter(query).order_by("-invite_time")  # 查询满足条件的所有邀请问题表里的数据
    # 用来 筛选 及 计算 邀请问题的个数
    invite_question_ids = invited_questions and list(set(invited_questions.values_list("question_id", flat=True))) or []

    if len(invite_question_ids) > start_num:  # 取邀请的信息

        pre_invite_dic = {}  # 用来预处理的邀请问题信息
        for invite in invited_questions:
            question_id = invite.question_id
            if question_id in pre_invite_dic:
                pre_invite_dic[question_id]["invite_count"] += 1
            else:
                pre_invite_dic[question_id] = {
                    "question_id": str(question_id),
                    'title': invite.question.title,
                    'answer_num': invite.question.answer_num,
                    'user_id': invite.user.id,
                    'nickname': invite.user.nickname,
                    'portrait': invite.user.portrait,
                    'invite_count': 1,
                    "invite_time": invite.invite_time,
                    'type': QUESTION_SOURCE.INVITATION,
                    "is_online": invite.question.is_online,
                }
        invite_list = sorted(list(pre_invite_dic.values()), key=lambda data: data["invite_time"], reverse=True)
        invite_list = list(filter(lambda data: data["is_online"], invite_list))
        for invite in invite_list:
            del invite["invite_time"]

        result += invite_list[start_num: start_num + count]

    if len(result) < count:  # 取推荐的信息
        if start_num < len(invite_question_ids):
            start_num = 0
        else:
            start_num = start_num - len(invite_question_ids)
        new_count = count - len(result)

        questions = Question.objects.filter(is_online=True).order_by("-create_time")[start_num: start_num + new_count]
        #  获取我已经回答的问题的id
        answered_question_ids = list(Answer.objects.filter(user=user.id).values_list("question_id"))
        invite_question_ids += answered_question_ids

        question_list = []
        for question in questions:
            if question.id in invite_question_ids:
                continue

            data = question.data_for_list()
            data.update({
                "type": QUESTION_SOURCE.RECOMMEND
            })
            question_list.append(data)
        result += question_list
    return result


@bind("qa/question/inviter_list")
@listing()
def get_inviter_list(question_id, start_num, count=10):
    """
    邀请人列表页
    :param question_id: 问题id
    :param start_num: 起始
    :param count: 个数
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    # 先看当前问题、当前用户下，有没有被邀请的用户
    inviters = list(
        QuestionInviter.objects.filter(question_id=question_id, user=user.id).values_list("inviter", flat=True))
    # 计算出来的，满足邀请条件的user_ids
    question_inviter_pool = question_inviter_cache.get("question_inviter_pool")
    user_pool = question_inviter_pool and json.loads(question_inviter_pool) or []
    user_ids = list(filter(lambda x: x not in inviters, user_pool))  # 过滤掉已经邀请的用户
    # 如果当前用户在邀请人内，过滤掉！
    if user.id in user_ids:
        user_ids.remove(user.id)
    return user_ids[start_num: start_num + count]


@bind("qa/question/invite_status")
def question_invite_status(question_id, inviter):
    """
    点击邀请，写入库的接口
    :param question_id: # 邀请回答的问题id
    :param inviter: # 被邀请人 user_id
    :return: 返回状态
    """
    result = {}

    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    if not inviter or not question_id:
        return gen(CODES.PARAMS_INCOMPLETE)

    data = {"user": int(user.user_id), "inviter": int(inviter), "question_id": int(question_id)}
    create = QuestionInviter.objects.create(**data)

    if create:
        result["status"] = True
    else:
        result["status"] = False

    return result


@bind('qa/answer/tags')
def get_answer_tags(answer_id):
    tags = []
    try:
        question = Answer.objects.get(id=answer_id).question
        tags = question.tags.values_list('tag', flat=True)
    except Answer.DoesNotExist:
        pass

    return list(tags)


@bind('qa/question/tags')
def get_question_tags(question_id):
    tags = list(QuestionTag.objects.filter(question_id=question_id).values_list('tag', flat=True))
    return tags


@bind('qa/mip/answer_list_by_question_id')
def get_answer_list_by_question_id_for_mip(question_id):
    """
    通过 question_id 获取 满足 mip 需求的answer数据
    mip 二期，增加返回问题信息 (问答数据 均不考虑是否在线情况) 去除推荐回答
    :param question_id:
    :return:
    """
    result = {
        "question_info": {},
        "doctor_answers": [],  # 医生回答的数据
        "recommend_answers": [],  # 推荐回答的数据
    }
    doctor_count = 0
    query = Q(question_id=question_id)

    question = Question.objects.filter(pk=question_id).first()
    if question:
        result["question_info"].update({
            'id': question_id,
            'title': question.title,
            'content': question.content,
            'create_time': get_timestamp_epoch(question.create_time),
            'view_num': question.view_amount,
        })

    # 医生回答的不重复数据 (公立医院，有职称，有科室)  等hera后台上线以后，把去重删除
    doctor_answer_query = query & Q(doctor_title__gte=DOCTOR_TITLE_TYPE.RESIDENT, hospital_type=HOSPITAL_TYPE.PUBLIC)

    doctor_answers = BDAnswer.objects.filter(doctor_answer_query).order_by("-is_recommend", "-like_num", "-id")
    doctor_id_list = []
    for answer in doctor_answers:
        if doctor_count < settings.MIP_LIMIT and answer.doctor_department and answer.doctor_id not in doctor_id_list:
            doctor_id_list.append(answer.doctor_id)
            _data = answer.data_for_list()
            result["doctor_answers"].append(_data)
            doctor_count += 1

    return result


def _get_relation_college_tag_question_ids(start_num, count, exclude_ids=None):
    """
    获取关联大学类型标签的问题id
    :param start_num: 起始数
    :param count: 个数
    :param exclude_ids:需要排除的id
    :return: question_id list
    """
    _tags = complains_question_cache.get("tag_ids")
    tags = _tags and json.loads(_tags) or []
    kw = {
        "filters": {
            "raw_tag_ids": tags,
            "answer_num_range": {"gte": settings.ANSWER_NUM_BASELINE},
        },
        "offset": start_num,
        "size": count,
        "sort_type": QUESTION_ORDER_TYPE.MOUTHGUN_RECOMMEND,
    }
    questions = rpc_client["api/question/filter"](**kw).unwrap()
    question_ids = [item.get('question', 0) for item in questions.get("question_ids", {})]

    if exclude_ids:
        question_ids = list(filter(lambda qid: qid not in exclude_ids, question_ids))

    return list(filter(None, question_ids))


@bind("qa/xcx/question_recommends")
def get_relation_college_tag_questions(from_home=False, exclude_ids=None, start_num=0, count=10):
    """
    吐槽小程序，推荐问题，该接口有两个作用：
    1、首页进入的随机推荐回答id
    2、相关推荐的问题列表
    :param exclude_ids: 需要排除的id list结构
    :param from_home: 是否是来自于首页：bool
    :param start_num:
    :param count:
    :return: result {}
    """
    result = {
        "question_id": 0,  # 首页推荐的问题
        "question_list": []  # 问答详情页，相关推荐问题
    }

    if from_home:
        # 先取后台推荐的问题
        recommends = complains_question_cache.get("recommend")
        _recommend_ids = recommends and json.loads(recommends) or []

        # 实时取在线的
        question_ids = _recommend_ids and list(Question.objects.filter(
            pk__in=_recommend_ids, is_online=True).values_list("id", flat=True)) or []

        if not question_ids:  # 取不到按照推荐问题，取第一个
            question_ids = _get_relation_college_tag_question_ids(0, 1)

        result["question_id"] = question_ids and random.choice(question_ids) or 0
        return result

    kw = {
        "start_num": start_num,
        "count": count,
        "exclude_ids": exclude_ids,
    }
    question_ids = _get_relation_college_tag_question_ids(**kw)
    questions = Question.objects.filter(pk__in=question_ids, is_online=True)

    for question in questions:
        if question.answer_num >= settings.ANSWER_NUM_BASELINE:
            result["question_list"].append(question.data_for_list())

    return result


@bind("qa/answer/get_answer_sub_replies")
@listing()
def get_answer_sub_replies(answer_id, reply_id, start_num=0, count=10, new_order=False):
    """
    获取一级评论的子评论
    :param answer_id: 回答id
    :param reply_id: 一级评论的id
    :param start_num: 起始数
    :param count: 每次的个数
    :return:
    """
    try:
        reply = AnswerReply.objects.get(answer_id=answer_id, id=reply_id, is_online=True)
    except AnswerReply.DoesNotExist:
        return gen(CODES.ANSWER_NOT_FOUND)

    data = {
        "sub_replies": reply.get_all_comments(need_newest=True, start_num=start_num, count=count, new_order=new_order),
    }

    return data


@bind("qa/answer/replies/receive")
def get_my_answer_replies_receive(replies_ids):
    """获取我收到的回答帖评论。"""

    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    user_ids = []
    answer_ids = []

    topic_replies_dict = AnswerTools.get_answer_replies(replies_ids)
    for _, reply in topic_replies_dict.items():
        # reply.commented_reply 表示被评论的回复，有则表示当前是二级
        answer_ids.append(reply.answer_id)
        user_ids.append(reply.user_id)  # 评论用户id

    answers_dict = {}
    answers = Answer.objects.filter(pk__in=answer_ids, is_online=True)
    for answer in answers:
        user_ids.append(answer.user_id)  # 问题的用户id
        answers_dict[answer.id] = answer

    users_dict = UserService.get_users_by_user_ids(user_ids)

    doctors_dict = {}
    doctors = DoctorService.get_doctor_from_user_ids(user_ids)
    for doctor in doctors:
        doctors_dict[doctor.user_id] = doctor

    result = {}
    for reply_id, reply in topic_replies_dict.items():

        reply_user = users_dict.get(reply.user_id, {})

        doctor = doctors_dict.get(reply.user_id, {})
        doctor_info = {}
        if doctor:
            doctor_info = {
                "user_id": doctor.user_id,
                "doctor_id": doctor.id,
                "name": doctor.name,
            }

        commented_reply = {}
        if  reply.commented_reply:
            commented_reply = {
                "id": reply.commented_reply_id,
                "content": reply.commented_reply.content
            }

        info = {
            "id": reply.id,
            "answer_id": reply.answer_id,

            "is_deleted": reply.is_online,
            "is_new": reply.is_read,
            "reply_date": reply.create_time.strftime("%Y-%m-%d %H:%M:%S"),
            "reply_content": reply.content,
            "commented_reply": commented_reply,
            "my_content": commented_reply["content"] if commented_reply else reply.answer.content,

            "reply_user": {
                "id": reply_user.id,
                "name": reply_user.nickname,
                "portrait": reply_user.portrait,
            },
            "author": {
                "id": user.id,
                "name": user.nick_name,
                "portrait": user.portrait,
            },

            "doctor": doctor_info,
        }
        result[str(reply_id)] = info

    return result


@bind('qa/answer_reply/read')
def qa_answer_reply_read():

    user = get_current_user()
    if not user:
        return

    my_reply_ids = list(AnswerReply.objects.filter(
        user=user.id, is_online=True
    ).values_list("id", flat=True))
    AnswerReply.objects.filter(
        commented_reply_id__in=my_reply_ids,
        is_online=True,
        is_read=False
    ).update(is_read=True)

    answer_ids = list(Answer.objects.filter(
        user=user.id, is_online=True
    ).values_list("id", flat=True))
    AnswerReply.objects.filter(
        answer_id__in=answer_ids,
        is_online=True,
        is_read=False
    ).update(is_read=True)


@bind("qa/answer/get_share_data")
def get_answer_share_data_by_id(answer_id):
    try:
        answer = Answer.objects.get(pk=answer_id, is_online=True)
    except Answer.DoesNotExist:
        return gen(CODES.ANSWER_NOT_FOUND)

    try:
        question = Question.objects.get(pk=answer.question_id, is_online=True)
    except Question.DoesNotExist:
        return gen(CODES.QUESTION_NOT_FOUND)

    _data = {
        "question_id": question.id,
        "answer_id": answer_id,
        "answer_content": answer.content,
        "question_content": question.content,
        "question_tags": [tag_obj.tag for tag_obj in question.tags],
        "question_old_images": question.content_images,
        "answer_old_images": answer.content_images,
    }
    return _data


@bind('mimas/user/follow/answer/list')
def user_follow_answer_list(offset=0, count=10):
    """
    我关注的用户的回答列表
    :param user_id:
    :param offset:
    :param count:
    :return:
    """
    user = get_current_user()
    if not user:
        return gen(CODES.LOGIN_REQUIRED)

    user_follow_ids = list(UserFollow.objects.filter(
        user_id=user.id, bond=True).order_by('-id').values_list('follow_id', flat=True))
    questions = Question.objects.filter(
        user__in=user_follow_ids, is_online=True).order_by('-id')[offset: offset+count]
    data = []
    question_ids = []
    for question in questions:
        data.append(question.data_for_list())
        question_ids.append(question.id)
    add_question_view.delay(question_ids)
    return data
