from functools import partial
from collections import defaultdict

from django.conf import settings

from gm_types.error import ERROR as CODES

from utils.rpc import gen
from qa.services.base import ServiceBase
from qa.models import (
    QualityReply,
    QualityReplyImage,
    QualityAuthorAnswer,
)
from qa.utils.time import get_timestamp_or_none
from talos.services import UserConvertService
from qa.services.base import ServiceModelCache
from qa.cache.cache_v2 import quality_question_cache


class QualityReplyService(ServiceBase):

    __cached_layer = partial(ServiceModelCache, quality_question_cache)
    cache = __cached_layer("quality_reply")

    @classmethod
    def health_get(cls, reply_id):

        try:
            reply = QualityReply.objects.get(pk=reply_id)
        except QualityReply.DoesNotExist:
            return gen(CODES.QUALITY_REPLY_NOT_FOUND)

        return reply

    @classmethod
    def create_reply(
        cls,
        user_id, answer, content, replied, top_id, images=[]
    ):

        reply = QualityReply(
            quality_answer_id=answer.id,
            quality_question_id=answer.quality_question_id,
            content=content,
            user_id=user_id,
            replied_id=replied and replied.id,
            top_id=top_id,
        )
        reply.save()

        reply_images = [
            QualityReplyImage(
                quality_reply_id=reply.id,
                url=image["image"],
                width=image.get("width", 0),
                height=image.get("height", 0),
            )
            for image in images
        ]
        QualityReplyImage.objects.bulk_create(reply_images)
        images = QualityReplyImage.objects.filter(quality_reply_id=reply.id)

        return {
            "id": reply.id,
            "quality_answer_id": answer.id,
            "user": {
                "id": user_id,
            },
            "images": [{"url": image.url} for image in images],
            "replied_user": {"id": replied.user_id} if replied else {"id": answer.user_id},
            "content": content
        }

    @classmethod
    def replies_by_quality_question_ids(cls, quality_question_ids, offset=0, count=10):

        reply_map = {}
        reply_ids = []
        for quality_question_id in quality_question_ids:
            ids = list(
                QualityReply.objects.filter(
                    quality_question_id=quality_question_id, is_online=True
                ).order_by("-id").values_list("id", flat=True)[offset: offset + count]
            )
            reply_ids.extend(ids)
            reply_map[quality_question_id] = ids

        replies = cls.replies_by_ids(reply_ids)

        result = {}
        for quality_question_id, reply_ids in reply_map.items():
            rs = [replies[reply_id] for reply_id in reply_ids if reply_id in replies]
            result[quality_question_id] = rs

        return result

    @classmethod
    def top_replies_by_quality_question_ids(cls, quality_question_ids, offset=0, count=10):

        reply_map = {}
        top_ids = []
        for quality_question_id in quality_question_ids:
            ids = list(
                QualityReply.objects.filter(
                    quality_question_id=quality_question_id, is_online=True, top_id=None
                ).order_by("-id").values_list("id", flat=True)[offset: offset + count]
            )
            top_ids.extend(ids)
            reply_map[quality_question_id] = ids

        replies = cls.replies_by_ids(top_ids)
        top_replies = {}
        for quality_question_id, reply_ids in reply_map.items():
            rs = [replies[reply_id] for reply_id in reply_ids if reply_id in replies]
            top_replies[quality_question_id] = rs

        return top_replies

    @classmethod
    def objs_by_ids(cls, reply_ids):

        if not reply_ids:
            return {}

        replies = QualityReply.objects.filter(pk__in=reply_ids).in_bulk(reply_ids)
        return replies

    @classmethod
    def replies_by_ids(cls, reply_ids):

        cache_data = cls.cache.mget(reply_ids)

        missing = cache_data.pop(cls.cache.missing_k)
        if not missing:
            return cache_data

        replies = QualityReply.objects.filter(pk__in=reply_ids)

        images = QualityReplyImage.objects.filter(quality_reply_id__in=reply_ids, is_online=True)

        images_dict = defaultdict(list)
        for image in images:
            images_dict[image.quality_reply_id].append({
                "url": image.url,
                "width": image.width,
                "height": image.height,
            })

        user_ids = set()
        replied_ids = set()
        replies_info = {}
        for reply in replies:
            if reply.replied_id:
                replied_ids.add(reply.replied_id)

            info = {
                "id": reply.id,
                "quality_question_id": reply.quality_question_id,
                "quality_answer_id": reply.quality_answer_id,
                "create_time": reply.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "top_id": reply.top_id,
                "replied_id": reply.replied_id,
                "user": {
                    "id": reply.user_id,
                },
                "replied_user": {},
                "content": reply.content,
                "images": images_dict.get(reply.id, [])
            }
            user_ids.add(reply.user_id)
            replies_info[reply.id] = info

        # fill replied reply user_id
        replied_replies = QualityReply.objects.filter(pk__in=replied_ids).in_bulk(replied_ids)
        quality_answer_ids = set()
        for _, reply in replies_info.items():
            replied_reply = replied_replies.get(reply["replied_id"])
            if replied_reply:
                user_ids.add(replied_reply.user_id)
                reply["replied_user"] = {"id": replied_reply.user_id}
            else:
                quality_answer_ids.add(reply["quality_answer_id"])

        quality_answers = QualityAuthorAnswer.objects.filter(pk__in=quality_answer_ids).in_bulk(quality_answer_ids)
        for _, reply in replies_info.items():
            if reply["replied_user"]:
                continue
            quality_answer = quality_answers.get(reply["quality_answer_id"])
            if quality_answer:
                reply["replied_user"] = {"id": quality_answer.user_id}
                user_ids.add(quality_answer.user_id)

        # fill user info
        users_dict = UserConvertService.get_user_info_by_user_ids(user_ids)
        for _, reply in replies_info.items():
            reply["user"].update(users_dict.get(reply["user"]["id"], {}))
            replied_user_id = reply["replied_user"].get("id")
            if replied_user_id:
                reply["replied_user"].update(users_dict.get(replied_user_id, {}))

        cls.cache.mset(list(replies_info.items()))
        replies_info.update(cache_data)

        return replies_info

    @classmethod
    def second_replies_by_top_reply_ids(cls, top_reply_ids, offset=0, count=10):

        reply_map = {}
        second_ids = []
        for top_reply_id in top_reply_ids:
            ids = list(
                QualityReply.objects.filter(
                    top_id=top_reply_id, is_online=True
                ).order_by("-id").values_list("id", flat=True)
            )
            second_ids.extend(ids)
            reply_map[top_reply_id] = ids

        replies = cls.replies_by_ids(second_ids)
        second_replies = {}
        for top_id, second_ids in reply_map.items():
            rs = [second_replies[reply_id] for reply_id in second_ids if reply_id in replies]
            second_replies[top_id] = rs

        return second_replies

    @classmethod
    def count_by_quality_question_id(cls, quality_question_id):

        return QualityReply.objects.using(settings.SLAVE_DB_NAME).filter(
            quality_question_id=quality_question_id, is_online=True
        ).count()