# coding:utf-8

import json
from itertools import chain
from collections import OrderedDict, defaultdict

from api.util.user_util import simple_user_info_by_user_ids
from api.models import Tag
from group.models import (
    Topic,
    TopicTag,
    TopicContent,
    TopicFollow,
    TopicTagV3,
    GroupTopicRelation
)
from agile.services import TagV3Service
from rpc.tool.error_code import gen, CODES
from rpc.tool.dict_mixin import to_dict
from rpc.cache import group_cache
from utils.time_tools import datetime2timestamp

from .group_service import GroupService
from agile.models import TagV3


class TopicService(object):

    _cache = group_cache
    follow_key = "topic:follow:{topic_id}"
    group_topic_rank_key_tpl = "group_topic_sort:{group_id}"

    @classmethod
    def safe_get(cls, topic_id):

        try:
            topic = Topic.objects.get(pk=topic_id)
        except Topic.DoesNotExist:
            gen(CODES.GROUP_NOT_FOUND)

        if not topic.is_online:
            gen(CODES.GROUP_OFFLINE)

        return topic

    @classmethod
    def is_follow(cls, topic_id, user_id):

        follow = TopicFollow.objects.filter(
            topic_id=topic_id, user_id=user_id, is_online=True
        ).first()
        if follow:
            return True

        return False

    @classmethod
    def detail(cls, topic):

        return {
            "id": topic.id,
            "name": topic.name,
            "intro": topic.introduction,
            "header_image": topic.header_image,
            "bg_img": topic.bg_image,
            "is_online": topic.is_online,
            "tags": topic.tag_list,
            "display_hit_rank": topic.display_hit_rank,
            "group_topic_type": topic.group_topic_type,
            "is_single_feed": topic.is_single_feed,
            "show_service_tab": topic.show_service_tab,
            "show_video_tab": topic.show_video_tab,
            "hit_rank_start_time": topic.hit_rank_start_time and datetime2timestamp(topic.hit_rank_start_time),
            "hit_rank_end_time": topic.hit_rank_end_time and datetime2timestamp(topic.hit_rank_end_time),
        }

    @classmethod
    def join_users(cls, topic_id, limit=6):

        follow_ids = TopicFollow.objects.filter(
            topic_id=topic_id, is_online=True).order_by("-id").values_list("user_id", flat=True)[:limit]
        users = simple_user_info_by_user_ids(follow_ids)

        ret = []
        for user_id in follow_ids:
            user = users.get(str(user_id))
            if user:
                ret.append(user)

        return ret

    @classmethod
    def follow_num(cls, topic_id):
        key = cls.follow_key.format(topic_id=topic_id)
        return int(cls._cache.get(key) or 0)

    @classmethod
    def incr_follow_num(cls, topic_id):
        key = cls.follow_key.format(topic_id=topic_id)
        return cls._cache.incr(key)

    @classmethod
    def decr_follow_num(cls, topic_id):
        key = cls.follow_key.format(topic_id=topic_id)
        if cls._cache.exists(key):
            return cls._cache.decr(key)

    @classmethod
    def follow_cnt(cls, topic_id):

        return TopicFollow.objects.filter(topic_id=topic_id, is_online=True).count()

    @classmethod
    def follow(cls, topic_id, user_id):

        try:
            obj = TopicFollow.objects.get(
                topic_id=topic_id, user_id=user_id)
        except:
            TopicFollow.objects.create(
                topic_id=topic_id, user_id=user_id, is_online=True)
            cls.incr_follow_num(topic_id)
            return

        if obj.is_online:
            gen(CODES.GROUP_FOLLOWED)
        else:
            obj.is_online = True
            obj.save()
            cls.incr_follow_num(obj.id)

    @classmethod
    def unfollow(cls, topic_id, user_id):

        try:
            topic = TopicFollow.objects.get(topic_id=topic_id, user_id=user_id)
            if topic.is_online:
                topic.delete()
                cls.decr_follow_num(topic.id)
        except:
            return

    @classmethod
    def add_content(cls, topic_id, card_type, card_id):
        TopicContent.objects.update_or_create(
            topic_id=topic_id, content_type=card_type,
            content_id=card_id, is_online=True,
        )

    @classmethod
    def list_topics_by_group_id(cls, group_id):

        key = cls.group_topic_rank_key_tpl.format(group_id=group_id)
        try:
            cache_topics_ids = json.loads(group_cache.get(key))
        except:
            cache_topics_ids = []

        relations = list(
            GroupTopicRelation.objects.filter(
                is_online=True, group_id=group_id
            ).order_by("rank").values("topic_id", "rank")
        )

        rank_topics_dict = OrderedDict()
        norank_topics_dict = OrderedDict()
        for item in relations:
            topic_id, rank = item["topic_id"], item["rank"]

            if rank:  # 排序话题，缓存中去除
                if topic_id in cache_topics_ids:
                    cache_topics_ids.remove(topic_id)
                rank_topics_dict[topic_id] = item["rank"]
                continue

            # 未排序话题不再缓存中处理：优先排序
            if topic_id not in cache_topics_ids:
                norank_topics_dict[topic_id] = item["rank"]

        # 未排序话题放在后面
        topic_ids = list(
            GroupTopicRelation.objects.filter(
                is_online=True, group_id=group_id, topic_id__in=cache_topics_ids
            ).values_list("topic_id", flat=True)
        )
        topic_ids = sorted(topic_ids, key=lambda i: cache_topics_ids.index(i))
        for topics_id in topic_ids:
            norank_topics_dict[topics_id] = None

        topic_ids = list(rank_topics_dict.keys()) + list(norank_topics_dict.keys())
        ts = Topic.objects.filter(pk__in=topic_ids, is_online=True).in_bulk(topic_ids)

        topics = []

        def expend_topics(topics_dict):
            tps = []
            for _id, rk in topics_dict.items():
                topic = ts.get(_id)
                if not topic:
                    continue
                tps.append({
                    "id": topic.id,
                    "name": topic.name,
                    "rank": rk,
                    "group_topic_type": topic.group_topic_type,
                })
            return tps

        topics.extend(expend_topics(rank_topics_dict))
        topics.extend(expend_topics(norank_topics_dict))

        return topics

    @classmethod
    def get_group_by_topic_id(cls, topic_id):

        group_relation = GroupTopicRelation.objects.filter(is_online=True, topic_id=topic_id).first()
        if not group_relation:
            return None

        try:
            group = GroupService.safe_get(group_relation.group_id)
        except:
            return None

        return group

    @classmethod
    def topic_relation_tag_v3s_by_ids(cls, topic_ids):
        """
        获取小组话题关联的标签3.0的数据
        :param topic_ids:
        :return:
        """
        relation_tag_ids = TopicTagV3.objects.filter(
            topic_id__in=topic_ids, is_online=True
        ).values_list("topic_id", "tag_v3_id")

        tagv3_infos = TagV3Service.get_tags_v3_by_ids(set(item[1] for item in relation_tag_ids))

        group_topic_rel_tag_v3_dic = {}
        for topic_id, tag_v3_id in relation_tag_ids:
            if topic_id not in group_topic_rel_tag_v3_dic:
                group_topic_rel_tag_v3_dic[topic_id] = []

            tag_v3_info = tagv3_infos.get(tag_v3_id, {})
            if not tag_v3_info:
                continue

            group_topic_rel_tag_v3_dic[topic_id].append(tag_v3_info)

        return group_topic_rel_tag_v3_dic

    @classmethod
    def tags_v3_by_topic_id(cls, topic_id):

        tag_ids = list(
            TopicTagV3.objects.filter(
                topic_id=topic_id, is_online=True).values_list("tag_v3_id", flat=True)
        )
        # second_appeal_attrs = TagV3Service.list_second_appeal_attrs(tag_ids)
        # tags_v3 = TagV3Service.list_tags_by_attrs([attr.id for attrs in second_appeal_attrs.values() for attr in attrs])
        # tags_v3.update(TagV3Service.get_tags_v3_by_ids(tag_ids))

        # v7.29.0 变更，不再扩展查询，扩展关系交给策略
        tags_v3 = TagV3Service.get_tags_v3_by_ids(tag_ids)
        return tags_v3

    @classmethod
    def tags_by_topic_id(cls, topic_id):

        tag_ids = list(
            TopicTag.objects.filter(
                topic_id=topic_id, is_online=True).values_list("tag_id", flat=True)
        )

        tags = Tag.objects.filter(pk__in=tag_ids, is_online=True)

        children = chain(*[tag.online_child_tags() for tag in tags])
        parent = chain(*[tag.online_parent_tags() for tag in tags])

        tags_v3 = cls.tags_v3_by_topic_id(topic_id)

        return {
            "children": [to_dict(t, fields=('id', 'name', 'tag_type')) for t in children],
            "parent": [to_dict(t, fields=('id', 'name', 'tag_type')) for t in parent],
            "tags": [to_dict(t, fields=('id', 'name', 'tag_type')) for t in tags],
            "tags_v3": list(tags_v3.values())
        }

    @classmethod
    def list_by_tag_ids(cls, tag_ids):

        topic_tags = list(TopicTag.objects.filter(tag_id__in=tag_ids, is_online=True).values("topic_id", "tag_id"))
        topic_ids = [item["topic_id"] for item in topic_tags]
        topics = Topic.objects.filter(pk__in=topic_ids, is_online=True).in_bulk(topic_ids)

        tag_topics = defaultdict(list)
        for topic_tag in topic_tags:

            topic = topics.get(topic_tag["topic_id"])
            if not topic:
                continue

            tag_topics[str(topic_tag["tag_id"])].append({
                "id": topic.id,
                "name": topic.name,
                "group_topic_type": topic.group_topic_type,
            })

        return dict(tag_topics)

    @classmethod
    def list_by_tag_v3_ids(cls, ids):

        topic_tags = list(TopicTagV3.objects.filter(tag_v3_id__in=ids, is_online=True).values("topic_id", "tag_v3_id"))
        topic_ids = [item["topic_id"] for item in topic_tags]
        topics = Topic.objects.filter(pk__in=topic_ids, is_online=True).in_bulk(topic_ids)

        tag_topics = defaultdict(list)
        from group.tasks.tasks import TOPIC_INFO_KEY
        for topic_tag in topic_tags:

            topic = topics.get(topic_tag["topic_id"])
            if not topic:
                continue
            cache_key = TOPIC_INFO_KEY.format(topic.id)
            extra_info = json.loads(group_cache.get(cache_key) or "{}")
            topic_reply_count = extra_info.get('reply_count', 0)
            topic_content_count = extra_info.get('content_count', 0)
            tag_topics[str(topic_tag["tag_v3_id"])].append({
                "id": topic.id,
                "name": topic.name,
                "header_image": topic.header_image,
                "group_topic_type": topic.group_topic_type,
                "reply_count": topic_reply_count,
                "content_count": topic_content_count,
            })

        return dict(tag_topics)

    @classmethod
    def list_ids_by_tag_v3_ids(cls, tag_ids):
        topic_tag_v3_ids = list(TopicTagV3.objects.filter(
            tag_v3_id__in=tag_ids,
            is_online=True
        ).values_list('topic_id', 'tag_v3_id'))
        # 此处同一个topic可能对应多个标签，但从产品逻辑角度可忽略
        topic_tag_v3_map = {_obj[0]: _obj[1] for _obj in topic_tag_v3_ids}
        online_topic_ids = list(Topic.objects.filter(
            pk__in=topic_tag_v3_map.keys(),
            is_online=True
        ).values_list('id', flat=True))
        return {_topic_id: topic_tag_v3_map[_topic_id] for _topic_id in online_topic_ids}

    @classmethod
    def get_tag2topic_map(cls):
        result = defaultdict(list)

        topic_ids = list(Topic.objects.filter(is_online=True).values_list(
            'id', flat=True
        ))
        topic_tags = TopicTagV3.objects.filter(
            topic_id__in=topic_ids, is_online=True
        ).values('topic_id', 'tag_v3_id')
        for _item in topic_tags:
            result[_item['tag_v3_id']].append(_item['topic_id'])

        offline_tag__ids = TagV3.objects.filter(id__in=result.keys(), is_online=False).values_list(
            'id', flat=True
        )
        for _offline_tag_ids in offline_tag__ids:
            result.pop(_offline_tag_ids)

        return dict(result)
