from functools import partial

from collections import defaultdict
from itertools import chain

from django.conf import settings
from django.db.models import Q, Count

from gm_types.error import ERROR as CODES
from gm_types.mimas import QUALITY_QUESTION_CLASSIFY_TYPE

from qa.cache.cache_v2 import quality_question_cache
from qa.models import (
    QualityClassfyQuestion,
    QualityQuestionClassifyRelation,
    QualityQuestion,
    QualityQuestionPool,
    QualityUserQuestion,
    QualityQuestionVote,
    QualityAuthorAnswer,
    QualityReply,
)
from talos.services import UserConvertService
from qa.services.base import ServiceModelCache, ServiceBase
from utils.rpc import gen
from utils.group_routine import GroupRoutine


class QualityQuestionService(ServiceBase):

    __cached_layer = partial(ServiceModelCache, quality_question_cache)

    @classmethod
    def vote(cls, user_id, quality_question_id):

        vote, created = QualityQuestionVote.objects.get_or_create(user_id=user_id, quality_question_id=quality_question_id)
        if created:
            return

        if vote.is_online:
            gen(CODES.QUALITY_QUESTION_VOTED)

        vote.is_online = True
        vote.save()

    @classmethod
    def cancel_vote(cls, user_id, quality_question_id):

        try:
            vote = QualityQuestionVote.objects.get(
                user_id=user_id, quality_question_id=quality_question_id,
            )
        except:
            return

        if vote.is_online:
            vote.is_online = False
            vote.save()
            return True

    @classmethod
    def user_question_ids(cls, user_id, answer_id, question_ids):
        """用户在当前问题下，提问过哪几个"""

        user_question_ids = QualityUserQuestion.objects.filter(
            user_id=user_id,
            answer_id=answer_id,
            question_id__in=question_ids,
        ).values_list("question_id", flat=True)

        return list(user_question_ids)

    @classmethod
    def list_user_ids_by_id(cls, quality_question_id, exclude_user_id=None):

        user_ids = list(
            QualityUserQuestion.objects.filter(
                quality_question_id=quality_question_id, is_online=True
            ).exclude(user_id=exclude_user_id).values_list("user_id", flat=True)
        )

        return user_ids

    @classmethod
    def question_ids_by_tag_ids(cls, tag_ids):

        quality_classify_questions = QualityClassfyQuestion.objects.filter(
            classify_type=QUALITY_QUESTION_CLASSIFY_TYPE.TAG,
            tag_id__in=tag_ids, is_online=True
        )
        quality_classify_questions_dict = {
            item.id: item.tag_id
            for item in quality_classify_questions
        }

        related_questions = list(
            QualityQuestionClassifyRelation.objects.filter(
                quality_classify_id__in=list(quality_classify_questions_dict.keys())
            ).order_by('id')
        )
        tag_question_map = defaultdict(list)
        for item in related_questions:
            tag_id = quality_classify_questions_dict[item.quality_classify_id]
            tag_question_map[tag_id].append(item.question_id)

        return tag_question_map

    @classmethod
    def question_ids_by_tag_group_ids(cls, tag_group_ids):

        quality_classify_questions = QualityClassfyQuestion.objects.filter(
            classify_type=QUALITY_QUESTION_CLASSIFY_TYPE.TAG_GROUP,
            tag_group_id__in=tag_group_ids, is_online=True
        )
        quality_classify_questions_dict = {
            item.id: item.tag_group_id
            for item in quality_classify_questions
        }

        related_questions = list(
            QualityQuestionClassifyRelation.objects.filter(
                quality_classify_id__in=list(quality_classify_questions_dict.keys())
            ).order_by('id')
        )
        group_question_map = defaultdict(list)
        for item in related_questions:
            tag_group_id = quality_classify_questions_dict[item.quality_classify_id]
            group_question_map[tag_group_id].append(item.question_id)

        return group_question_map

    @classmethod
    def list_question_by_tags(cls, tag_ids):
        """获取标签对应的问题，没有则去标签对应大组对应的问题"""

        if not tag_ids:
            return {}

        tag_question_map = cls.question_ids_by_tag_ids(tag_ids)
        if not tag_question_map:
            # 根据标签大组拿问题
            # {tag_id: [{id: 1, name: xx}]}
            try:
                tag_category_map = cls.call_rpc("api/tag_v3/list_group_by_tags", tag_ids=tag_ids)
            except:
                tag_category_map = {}

            group_tag_map = {}
            tag_group_map = defaultdict()
            group_ids = []
            for tag_id, item in tag_category_map.items():
                t_id = int(tag_id)
                group_ids.extend([item["id"] for item in chain(*tag_category_map.values())])
                group_tag_map.update({group_id: t_id for group_id in set(group_ids)})
                tag_group_map[t_id] = group_ids

            group_question_map = cls.question_ids_by_tag_group_ids(group_ids)
            for tag_id, group_ids in tag_group_map.items():
                x = sorted(set(group_ids) & set(group_question_map.keys()))
                if not x:
                    continue
                # 选择第一个小组
                tag_question_map[tag_id].extend(group_question_map[x[0]])

        return tag_question_map

    @classmethod
    def list_common_question(cls, count=6):

        quality_classify = QualityClassfyQuestion.objects.filter(
            classify_type=QUALITY_QUESTION_CLASSIFY_TYPE.COMMON,
            is_online=True
        ).first()
        if not quality_classify:
            return []

        question_ids = list(
            QualityQuestionClassifyRelation.objects.filter(
                quality_classify_id=quality_classify.id
            ).order_by('id').values_list("question_id", flat=True)[: count]
        )
        questions = cls.list_question_by_ids(question_ids)

        res = []
        for question_id in question_ids:
            if question_id not in questions:
                continue
            res.append(questions[question_id])

        return res

    @classmethod
    def list_question_by_ids(cls, question_ids, is_online=True):

        questions = QualityQuestionPool.objects.filter(
            pk__in=question_ids, is_online=is_online,
        )

        return {
            item.id: {
                "id": item.id,
                "title": item.title,
            }
            for item in questions
        }

    @classmethod
    def count_by_answer_id(cls, answer_id):

        return QualityQuestion.objects.using(settings.SLAVE_DB_NAME).filter(
            answer_id=answer_id, is_online=True
        ).count()

    @classmethod
    def count_by_answer_ids(cls, answer_ids):
        data = QualityQuestion.objects.using(settings.SLAVE_DB_NAME).filter(
            answer_id__in=answer_ids, is_online=True
        ).values('answer_id').annotate(count=Count('id'))

        return data

    @classmethod
    def list_quality_questions_info(cls, quality_question_ids, user=None):

        # cache = cls.__cached_layer("list_quality_questions_info")
        # cache_data = cache.mget(quality_question_ids)
        # missing = cache_data.pop(cache.missing_k)
        # if not missing:
        #     return cache_data

        result = cls.list_quality_question_by_ids(quality_question_ids, user)

        # cache.mset(result)
        # result.update(cache_data)

        return result

    @classmethod
    def quality_question_ids_by_answer_ids(cls, answer_ids, user_id, offset=0, count=10):

        res = {}

        routine = GroupRoutine()
        for answer_id in answer_ids:
            routine.submit(answer_id, cls.quality_question_ids_by_answer_id,
                           answer_id, user_id, offset, count)
        routine.go()

        for answer_id in answer_ids:
            res[answer_id] = routine.results.get(answer_id, [])

        return res

    @classmethod
    def quality_question_ids_by_answer_id(cls, answer_id, user_id, offset=0, count=10):

        query = Q()
        if user_id:
            query = Q(user_id=user_id)
        query &= Q(answer_id=answer_id)

        quality_question_ids = QualityUserQuestion.objects.filter(
            query).order_by("-id").values_list("quality_question_id", flat=True)

        user_question_ids = list(
            QualityQuestion.objects.filter(
                pk__in=quality_question_ids, is_online=True
            ).order_by("-ask_cnt").values_list("id", flat=True)
        )

        quality_questions_ids = []
        if offset < len(user_question_ids):
            quality_questions_ids = user_question_ids[offset: offset + count]

        # 登录用户自己提问数量不足需要用其他数量补齐
        l_ids = len(quality_questions_ids)
        if user_id and l_ids < count:

            qs = QualityQuestion.objects.filter(answer_id=answer_id, is_online=True).order_by("-ask_cnt").\
                exclude(pk__in=quality_question_ids).values_list("id", flat=True)

            if l_ids:
                quality_questions_ids.extend(qs[0: count - l_ids])
            else:
                quality_questions_ids.extend(qs[offset: offset + count])

        return quality_questions_ids

    @classmethod
    def list_quality_question_by_ids(cls, quality_question_ids, user=None):

        quality_questions = QualityQuestion.objects.filter(
            pk__in=quality_question_ids, is_online=True
        )
        if not quality_questions:
            return {}

        voted_ids = []
        if user:
            voted_ids = list(
                QualityQuestionVote.objects.filter(
                    user_id=user.id, quality_question_id__in=quality_question_ids, is_online=True
                ).values_list("quality_question_id", flat=True)
            )

        question_extra_info = {}
        question_ids = set()
        for quality_question in quality_questions:
            question_extra_info[quality_question.question_id] = {
                "id": quality_question.id,
                "question_id": quality_question.question_id,
                "ask_cnt": quality_question.ask_cnt * 4,
                "vote_cnt": quality_question.vote_cnt,
                "is_voted": quality_question.id in voted_ids,
            }
            question_ids.add(quality_question.question_id)

        questions_dict = QualityQuestionPool.objects.filter(pk__in=question_ids, is_online=True).in_bulk(question_ids)

        questions = {}
        for _, question in questions_dict.items():
            info = question_extra_info[question.id]
            info["title"] = question.title

            questions[info["id"]] = info

        # fill user
        routine = GroupRoutine()
        for quality_question_id in quality_question_ids:
            routine.submit(quality_question_id, cls.get_quality_questions_extra_info, quality_question_id)
        routine.go()
        for quality_question_id, question in questions.items():
            question.update(routine.results.get(quality_question_id, {}))

        questions = cls.fill_quality_questions_user_info(questions)
        return questions

    @classmethod
    def fill_quality_questions_user_info(cls, quality_questions):

        user_ids = set()
        for _, question in quality_questions.items():
            if question["first_user_id"]:
                user_ids.add(question["first_user_id"])
            if question["latest_user_ids"]:
                user_ids.update(question["latest_user_ids"])

        users_dict = UserConvertService.get_user_info_by_user_ids(user_ids)
        for _, question in quality_questions.items():

            first_user_id = question.get("first_user_id", None)
            first_user = users_dict[first_user_id] if first_user_id in users_dict else None
            question["user_name"] = first_user["user_name"] if first_user else ""

            latest_user_ids = question.get("latest_user_ids", [])
            latest_users = [users_dict[uid] for uid in latest_user_ids if uid in users_dict]
            question["user_portraits"] = [u["portrait"] for u in latest_users]

        return quality_questions

    @classmethod
    def get_quality_questions_cnt_info_by_answer_ids(cls, answer_ids):

        routine = GroupRoutine()
        for answer_id in answer_ids:
            routine.submit(answer_id, cls.get_quality_questions_cnt_info_by_answer_id,
                           answer_id)
        routine.go()

        count_infos = {}
        for answer_id in answer_ids:
            count_infos[answer_id] = routine.results.get(answer_id, {})

        return count_infos

    @classmethod
    def get_quality_questions_cnt_info_by_answer_id(cls, answer_id):

        ret = {
            "question_cnt": 0,
            "answer_cnt": 0,
            "reply_cnt": 0,
        }

        quality_question_ids = list(QualityQuestion.objects.filter(answer_id=answer_id, is_online=True).values_list("id", flat=True))

        ret["question_cnt"] = len(quality_question_ids)
        ret["answer_cnt"] = QualityAuthorAnswer.objects.filter(
            quality_question_id__in=quality_question_ids, is_online=True
        ).count()
        ret["reply_cnt"] = QualityReply.objects.filter(
            quality_question_id__in=quality_question_ids, is_online=True
        ).count()

        return ret

    @classmethod
    def get_quality_questions_extra_info(cls, quality_question_id):
        """获取追问相关信息。

        包括最新4个用户以及第一个用户、回复数、评论数。
        """

        result = {
            "first_user_id": None,
            "latest_user_ids": [],
            "user_name": "",
            "user_portraits": [],
            "answer_cnt": 0,
            "reply_cnt": 0,
        }

        latest_user_ids = list(
            QualityUserQuestion.objects.filter(
                quality_question_id=quality_question_id, is_online=True
            ).order_by("-id").values_list("user_id", flat=True)[:3]
        )
        if not latest_user_ids:
            return

        result["latest_user_ids"] = latest_user_ids
        result["answer_cnt"] = QualityAuthorAnswer.objects.filter(
            quality_question_id=quality_question_id, is_online=True
        ).count()

        result["reply_cnt"] = QualityReply.objects.filter(
            quality_question_id=quality_question_id, is_online=True
        ).count()

        first_user_id = QualityUserQuestion.objects.filter(
            quality_question_id=quality_question_id, is_online=True
        ).order_by("id").values_list("user_id", flat=True).first()

        result["first_user_id"] = first_user_id

        return result

    @classmethod
    def question_check_or_get(cls, question_id):

        try:
            question = QualityQuestionPool.objects.get(pk=question_id)
        except QualityQuestionPool.DoesNotExist:
            return gen(CODES.QUESTION_NOT_FOUND)

        if not question.is_online:
            return gen(CODES.QUESTION_NOT_ONLINE)

        return question

    @classmethod
    def get_question_info_by_quality_question_ids(cls, quality_question_ids):

        quality_questions = QualityQuestion.objects.filter(pk__in=quality_question_ids).in_bulk(quality_question_ids)
        question_ids = [item.question_id for _, item in quality_questions.items()]

        questions = QualityQuestionPool.objects.filter(pk__in=question_ids).in_bulk(question_ids)

        result = {}
        for quality_question_id, quality_question in quality_questions.items():
            result[quality_question_id] = questions.get(quality_question.question_id)

        return result

    @classmethod
    def quality_questions(cls, quality_question_ids):

        questions = QualityQuestion.objects.filter(pk__in=quality_question_ids).in_bulk(quality_question_ids)
        return questions

    @classmethod
    def quality_question_health_get(cls, quality_question_id):

        try:
            quality_question = QualityQuestion.objects.get(pk=quality_question_id)
        except QualityQuestion.DoesNotExist:
            return gen(CODES.QUALITY_QUESTION_NOT_FOUND)

        if not quality_question.is_online:
            return gen(CODES.QUALITY_QUESTION_OFFLINE)

        return quality_question

    @classmethod
    def questioning(cls, answer_id, question, user_id):

        try:
            quality_question = QualityQuestion.objects.get(answer_id=answer_id, question_id=question.id)
        except QualityQuestion.DoesNotExist:
            quality_question = QualityQuestion(
                answer_id=answer_id,
                question_id=question.id,
            )
            quality_question.save()

        try:
            QualityUserQuestion.objects.get(quality_question_id=quality_question.id, user_id=user_id)
            return gen(CODES.QUALITY_HAS_QUESTIONING)
        except QualityUserQuestion.DoesNotExist:
            pass

        user_question = QualityUserQuestion(
            quality_question_id=quality_question.id,
            answer_id=answer_id,
            question_id=question.id,
            user_id=user_id,
        )
        user_question.save()

        quality_question.ask_cnt += 1
        quality_question.save()

        quality_questions_users_info = cls.get_quality_questions_extra_info(quality_question.id)

        question = {
            "id": quality_question.id,
            "user_question_id": user_question.id,
            "question_id": question.id,
            "title": question.title,
            "ask_cnt": (quality_question.ask_cnt - 1)*4 + 1,
            "vote_cnt": quality_question.vote_cnt,
            "user": {
                "id": user_id
            },
        }
        question.update(quality_questions_users_info)

        questions = cls.fill_quality_questions_user_info({quality_question.id: question})

        return questions[quality_question.id]

    @classmethod
    def get_consult_count_by_answer_ids(cls, answer_ids):
        """
        获取api_answer 下的追问数
        :param answer_ids:
        :return:
        """
        result = {}
        if not all([answer_ids, isinstance(answer_ids, (list, tuple))]):
            return result

        ask_info = QualityQuestion.objects.filter(answer_id__in=answer_ids).values(
            'answer_id', 'ask_cnt',
        )
        for item in ask_info:
            result[item['answer_id']] = item['ask_cnt'] * 4

        return result

    @classmethod
    def list_user_question_by_user_quality_ids(cls, quality_user_question_ids):
        user_questions = QualityUserQuestion.objects.filter(pk__in=quality_user_question_ids)
        res = {}
        for item in user_questions:
            res[item.id] = {
                "quality_question_id": item.quality_question_id,
                "answer_id": item.answer_id,
                "user_id": item.user_id,
                "create_time": item.create_time,
            }

        return res

