#!/usr/bin/env python
# -*- coding: utf-8 -*-

from collections import defaultdict
from operator import itemgetter
from urllib.parse import urljoin

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

from gm_types.error import ERROR
from gm_types.gaia import (
    VIDEO_CODE_STATUS,
    LIST_TRACTATE_FROM,
    DOCTOR_TYPE,
)
from gm_types.mimas import (
    TRACTATE_PLATFORM,
    TRACTATE_STATUS,
)

from .base import BaseService
from social.models import SocialInfo
from talos.cache.base import tractate_pv_cache, tractate_vote_count_cache, tractate_favor_count_cache
from talos.services import (
    TagService,
    AgileTagService,
    UserConvertService,
)

from talos.models.tractate import (
    Tractate,
    TractateTag,
    TractateVote,
    TractateFavor,
    TractateExtra,
    TractateVideo,
    TractateImages,
    TractateCheck,
    TractateNewTag,
    TractateRelationService,
)

from talos.tasks.tractate import (
    set_tractate_video_water_mark_url,
    set_tractate_video_clipping_to_video,
)

from talos.rpc import gen


def filter_ids(ids):
    return list(map(int, filter(None, ids)))


class TractateService(BaseService):
    model = Tractate
    audit_can_show_status = [
        TRACTATE_STATUS.UNAUDITED,
        TRACTATE_STATUS.AUDIT_SUCCESS,
    ]
    _query = Q(is_online=True, user_del=False)
    base_query = _query & Q(status__in=audit_can_show_status)

    @classmethod
    def inc_tractate_favor(cls, tractate):
        try:
            te = TractateExtra.objects.get(tractate_id=tractate.id)
        except TractateExtra.DoesNotExist:
            te = TractateExtra.objects.create(tractate_id=tractate.id)

        te.favor_count = F('favor_count') + 1
        te.save(update_fields=['favor_count'])
        return tractate_favor_count_cache.incrby(str(tractate.id), 1)

    @classmethod
    def incr_tractate_reply(cls, tractate):
        try:
            te = TractateExtra.objects.get(tractate_id=tractate.id)
        except TractateExtra.DoesNotExist:
            te = TractateExtra.objects.create(tractate_id=tractate.id)

        te.reply_count = te.reply_count + 1
        te.save(update_fields=['reply_count'])

    @classmethod
    def dec_tractate_favor(cls, tractate):
        try:
            te = TractateExtra.objects.get(tractate_id=tractate.id)
        except TractateExtra.DoesNotExist:
            te = TractateExtra.objects.create(tractate_id=tractate.id)
        if te.favor_count > 0:
            te.favor_count = F('favor_count') - 1
            te.save(update_fields=['favor_count'])
        favor_cache_count = int(tractate_favor_count_cache.get(str(tractate.id)) or 0)
        if favor_cache_count > 0:
            return tractate_favor_count_cache.decr(str(tractate.id), 1)
        return favor_cache_count

    @classmethod
    def inc_tractate_vote(cls, tractate):

        te, _ = TractateExtra.objects.get_or_create(tractate_id=tractate.id)
        te.vote_count = F('vote_count') + 1
        te.save(update_fields=['vote_count'])
        return tractate_vote_count_cache.incrby(str(tractate.id), 1)

    @classmethod
    def dec_tractate_vote(cls, tractate):

        te, _ = TractateExtra.objects.get_or_create(tractate_id=tractate.id)
        if te.vote_count > 0:
            te.vote_count = F('vote_count') - 1
            te.save(update_fields=['vote_count'])
        vote_cache_count = int(tractate_vote_count_cache.get(str(tractate.id)) or 0)
        if vote_cache_count > 0:
            return tractate_vote_count_cache.decr(str(tractate.id), 1)
        return vote_cache_count

    @staticmethod
    def get_follow_info(viewer_user_id, author_ids):
        """
        用户之间的关注状态
        :param viewer_user_id: 浏览用户
        :param author_ids: 作者列表
        :return:
        """
        follow_rels = {}
        if viewer_user_id:
            social_info = SocialInfo(uid=viewer_user_id)
            follow_rels = social_info.is_following_users(uids=author_ids)

        return follow_rels

    @classmethod
    def get_by_id(cls, pk):
        try:
            filter_query = cls.base_query & Q(pk=pk)
            _obj = Tractate.objects.get(filter_query)
        except Tractate.DoesNotExist:
            _obj = None
        return _obj

    @classmethod
    def healthy(cls, tractate_id):

        tractate = cls.get_by_id(tractate_id)
        if not tractate:
            gen(ERROR.TRACTATE_NOT_FOUND)

        return tractate

    @classmethod
    def offline(cls, tractate_id):

        tractate = cls.get_by_id(tractate_id)
        if not tractate:
            gen(ERROR.TRACTATE_NOT_FOUND)

        tractate.is_online = False
        tractate.save()

    @staticmethod
    def tractate_view_increase_num(tractate_ids):
        """
        新帖子增加浏览量
        :param tractate_ids:
        :return:
        """
        for _id in tractate_ids:
            tractate_pv_cache.incrby(str(_id), 1)

    @classmethod
    def create(cls, user_id, content, platform=TRACTATE_PLATFORM.GM, image_urls=[], video_info={}, tag_ids=[],
               new_tag_ids=[]):
        """
        帖子创建
        :param user_id: 用户id
        :param content: 内容
        :param platform: 数据来源
        :param image_urls: 图片列表
            must list like
            '[{"width": 200, "image_url": "", "height": 400},
            {"width": 100, "image_url": "", "height": 350}]'
        :param video_info: 视频数据 dict like {"width": 200, "video_cover_url": "", "height": 400, 'raw_video_url': ''}
        :param tag_ids: 标签列表
        :param new_tag_ids: 新标签列表
        :return:
        """
        # TODO 确认是否需要事务
        tractate = cls.model.objects.create(
            content=content,
            user_id=user_id,
            platform=platform
        )

        _tractate_id = tractate.id

        # 图片
        if image_urls:
            tractate_images = []
            for image_dic in image_urls:
                image_dic["tractate_id"] = _tractate_id
                tractate_images.append(TractateImages(**image_dic))
            TractateImages.objects.bulk_create(tractate_images)

        # 视频
        if video_info:
            video_info["raw_video_url"] = TractateVideo.cleaned_video_url(video_info.get("raw_video_url", ''))
            video_info["tractate_id"] = _tractate_id
            vo = TractateVideo.objects.create(**video_info)
            # 视频水印处理
            set_tractate_video_water_mark_url.delay(vo.id)
            # set_tractate_video_clipping_to_video.delay(vo.id)

        # 标签
        if tag_ids:
            tractate_tags, _tag_ids_set = [], set()
            for tag_id in tag_ids:
                if tag_id not in _tag_ids_set:
                    _tag_ids_set.add(tag_id)
                    tractate_tags.append(TractateTag(tractate_id=_tractate_id, tag_id=tag_id))

            if tractate_tags:
                TractateTag.objects.bulk_create(tractate_tags)

        if new_tag_ids:
            new_tractate_tags, _new_tag_ids_set = [], set()
            for tag_id in new_tag_ids:
                if tag_id not in _new_tag_ids_set:
                    _new_tag_ids_set.add(tag_id)
                    new_tractate_tags.append(TractateNewTag(tractate_id=_tractate_id, tag_id=tag_id))

            if new_tractate_tags:
                TractateNewTag.objects.bulk_create(new_tractate_tags)

        return _tractate_id

    @classmethod
    def get_user_tractate_count(cls, user_id):
        filter_query = Q(user_id=user_id) & cls.base_query
        tractate_count = cls.model.objects.filter(filter_query).count()

        return tractate_count

    @classmethod
    def list_tractate_by_ids(cls, tractate_ids):
        """列出帖子"""
        filter_query = Q(pk__in=tractate_ids) & cls.base_query
        tractates = cls.model.objects.filter(filter_query)

        res = {}
        for item in tractates:
            res[item.id] = item

        return res

    @staticmethod
    def get_tractate_base_info(tractate):
        """
        获取帖子基础数据
        :param tractate:
        :return:
        """
        return {
            'tractate_id': tractate.id,
            'content': tractate.content,
            'is_online': tractate.is_online,
            'user_del': tractate.user_del,
            'content_level': tractate.content_level,
            'create_timestamp': int(tractate.create_time.timestamp()),
            'view_amount': tractate.view_amount,
            'favor_amount': tractate.favor_amount,
            "vote_amount": tractate.vote_amount,
            "reply_amount": tractate.reply_amount,
        }

    @classmethod
    def get_tractate(cls, tractate_id, user_id):
        tractate = cls.healthy(tractate_id)

        tractate_extra = TractateExtra.objects.filter(tractate_id=tractate_id).first()

        has_favor = TractateFavor.objects.filter(tractate_id=tractate_id, user_id=user_id, is_deleted=False).exists()
        has_vote = TractateVote.objects.filter(tractate_id=tractate_id, user_id=user_id, is_online=True).exists()

        data = {
            # 回复点赞相关
            'reply_count': tractate_extra.reply_count if tractate_extra else 0,
            'vote_count': tractate_extra.vote_count if tractate_extra else 0,
            'favor_count': tractate_extra.favor_count if tractate_extra else 0,
            'is_favor': has_favor,
            'is_vote': has_vote
        }
        return tractate, data

    @classmethod
    def get_tractate_objs_by_query_and_order(cls, query=Q(), order_by=[], offset=0, size=10):
        """
        通过query和order获取帖子对象
        :param query:
        :param order_by:
        :param offset:
        :param size:
        :return:
        """
        tractate_queryset = cls.model.objects.filter(query).order_by(*order_by)[offset: offset + size]
        return tractate_queryset

    @classmethod
    def get_tractate_ids_by_query_and_order(cls,
                                            query=Q(),
                                            order_by=[],
                                            offset=0,
                                            size=10,
                                            tractate_from='',
                                            use_new_query=False,
                                            ):
        """
        通过query和order获取帖子id,
        :param query:
        :param order_by:
        :param offset:
        :param size:
        :param tractate_from: 7.12.0 根据不同的帖子入口 使用不同的query filter
        :param use_new_query:  7.12.0  版本兼容
        :return:
        """
        filter_query = Q()
        if use_new_query:
            if tractate_from == LIST_TRACTATE_FROM.MY_PUBLISH:
                filter_query = query & Q(user_del=False)
            elif tractate_from == LIST_TRACTATE_FROM.MY_REPLY:
                filter_query = query
        else:
            filter_query = query & cls.base_query

        queryset = cls.get_tractate_objs_by_query_and_order(
            query=filter_query,
            order_by=order_by,
            offset=offset,
            size=size
        )
        tractate_ids = list(queryset.values_list("id", flat=True))
        return tractate_ids

    @staticmethod
    def sort_objects_by_pk_list(queryset, pk_list):
        """
        按照id做展示顺序
        :param queryset:
        :param pk_list:
        :return:
        """
        return sorted(queryset, key=lambda obj: pk_list.index(obj.id))

    @classmethod
    def _get_tractates_by_ids(cls, tractate_ids, tractate_from='', use_new_query=False):
        _tractate_ids = filter_ids(tractate_ids)
        query = Q(pk__in=_tractate_ids)
        if use_new_query:
            if tractate_from == LIST_TRACTATE_FROM.MY_PUBLISH:
                query = query & Q(user_del=False)

            elif tractate_from == LIST_TRACTATE_FROM.MY_REPLY:
                query = query

        else:
            query = query & cls.base_query

        queryset = Tractate.objects.filter(query)

        return cls.sort_objects_by_pk_list(queryset, _tractate_ids)

    @staticmethod
    def get_user_info_by_user_ids(user_ids):
        result = {}

        user_ids = filter_ids(user_ids)
        if not user_ids:
            return result

        return UserConvertService.get_user_info_by_user_ids(user_ids)

    @staticmethod
    def only_get_tag_ids_by_tractate_ids(tractate_ids):
        tractate_ids = filter_ids(tractate_ids)
        result = {}

        if tractate_ids:
            _result = defaultdict(list)
            trac_tag_ids = TractateTag.objects.filter(
                tractate_id__in=tractate_ids).values_list("tractate_id", "tag_id")
            for trac_id, tag_id in trac_tag_ids:
                _result[str(trac_id)].append(tag_id)
            result = dict(_result)

        return result

    @staticmethod
    def only_get_new_tag_ids_by_tractate_ids(tractate_ids):
        tractate_ids = filter_ids(tractate_ids)
        result = {}

        if tractate_ids:
            _result = defaultdict(list)
            trac_tag_ids = TractateNewTag.objects.filter(
                tractate_id__in=tractate_ids).values_list("tractate_id", "tag_id")
            for trac_id, tag_id in trac_tag_ids:
                _result[str(trac_id)].append(tag_id)
            result = dict(_result)

        return result

    @staticmethod
    def get_tag_info_by_tractate_ids(tractate_ids):
        result = dict()

        tractate_ids = filter_ids(tractate_ids)
        if not tractate_ids:
            return result

        tr_tags = TractateTag.objects.filter(
            tractate_id__in=tractate_ids
        ).values_list('tag_id', 'tractate_id')
        tr_tags_dic = {(tag_id, tractate_id): tractate_id for tag_id, tractate_id in tr_tags}

        tag_ids = set(map(itemgetter(0), list(tr_tags_dic.keys())))
        tags_info = TagService.get_tags_by_tag_ids(ids=tag_ids)
        tag_info_dic = {tag.id: tag for tag in tags_info}  # 把数据转换下结构

        _result = defaultdict(list)
        for k, v in tr_tags_dic.items():
            _tag_id, _ = k
            tag = tag_info_dic.get(_tag_id, None)
            if tag:
                tag_data = {
                    'id': tag.id,
                    'name': tag.name,
                    "tag_id": tag.id,
                    "tag_name": tag.name,
                    "tag_type": tag.tag_type,
                }
                _result[v].append(tag_data)

        return dict(_result)

    @staticmethod
    def get_new_tag_info_by_tractate_ids(tractate_ids):
        _result = {}

        tractate_ids = filter_ids(tractate_ids)
        if not tractate_ids:
            return _result

        tr_tags = TractateNewTag.objects.filter(
            tractate_id__in=tractate_ids
        ).values_list('tag_id', 'tractate_id')
        tr_tags_dic = {(tag_id, tractate_id): tractate_id for tag_id, tractate_id in tr_tags}

        new_tag_ids = set(map(itemgetter(0), list(tr_tags_dic.keys())))
        agile_tag_info_dic = AgileTagService.get_agile_tags_by_agile_tag_ids(new_tag_ids)

        _result = defaultdict(list)
        for k, v in tr_tags_dic.items():
            _tag_id, _ = k
            agile_tag = agile_tag_info_dic.get(_tag_id, None)
            if agile_tag:
                tag_data = agile_tag.to_dict()
                tag_data.update({
                    "tag_id": agile_tag.id,
                    "tag_name": agile_tag.name,
                })
                _result[v].append(tag_data)
        return dict(_result)

    @staticmethod
    def get_image_info_by_tractate_ids(tractate_ids):
        result = dict()

        tractate_ids = filter_ids(tractate_ids)
        if not tractate_ids:
            return result

        tractate_images = TractateImages.objects.filter(
            tractate_id__in=tractate_ids
        ).values('tractate_id', 'image_url', 'width', 'height')

        _result = defaultdict(list)
        for _image_item in tractate_images:
            trac_id = _image_item.get("tractate_id", 0)
            _data = {
                'image_url': _image_item.get("image_url", ""),
                "width": _image_item.get("width", 0),
                "height": _image_item.get("height", 0)
            }
            _result[trac_id].append(_data)

        return dict(_result)

    @staticmethod
    def get_video_info_by_tractate_ids(tractate_ids):
        result = defaultdict(list)
        if not tractate_ids:
            return result

        videos_info = TractateVideo.objects.filter(
            tractate_id__in=tractate_ids,
            is_online=True
        ).values('tractate_id', 'raw_video_url', 'water_video_url', "video_cover_url", 'width', 'height', "webp_url")

        for video_item in videos_info:
            trac_id = video_item.get("tractate_id", 0)
            _video_url = video_item.get("water_video_url", "") or video_item.get("raw_video_url", "") or ""
            webp_url = video_item.get("webp_url", "")
            if webp_url:
                short_video_url = webp_url
            else:
                short_video_url = ""

            _video_data = {
                "video_url": urljoin(settings.VIDEO_HOST, _video_url),
                "video_cover_url": video_item.get("video_cover_url", ""),
                "width": video_item.get("width", 0),
                "height": video_item.get("height", 0),
                "short_video_url": short_video_url,
            }
            result[trac_id].append(_video_data)

        return dict(result)

    @classmethod
    def get_tractate_infos_by_ids(cls,
                                  tractate_ids,
                                  user_id=None,
                                  need_special_info=False,
                                  tractate_from='',
                                  use_new_query=False):
        """
        获取帖子列表数据
        :param tractate_ids:
        :param user_id:
        :param need_special_info: 是否需要社交信息 (收藏，点赞，用户关注的状态)
        :param tractate_from: 7.12.0 根据帖子不同的入口，选择不同的query filter
        :param use_new_query: 7.12.0 版本兼容，老版本不动，新版本使用新query filter
        :return:
        """
        tractate_obj_list = cls._get_tractates_by_ids(
            tractate_ids, tractate_from=tractate_from, use_new_query=use_new_query
        )
        result = cls.get_tractate_list_info_by_obj_list(
            tractate_obj_list,
            user_id=user_id,
            need_special_info=need_special_info
        )

        return result

    @classmethod
    def get_tractate_list_info_by_obj_list(cls, tractate_obj_list, user_id=None, need_special_info=False):
        """
        通过帖子对象列表，封装数据
        :param tractate_obj_list: [tractate_obj, tractate_obj]
        :param user_id:
        :param need_special_info: 是否需要社交信息 (收藏，点赞，用户关注的状态)
        :return:
        """
        result = {
            "valid_tractate_ids": [],
            "tractate_list": []
        }

        if not tractate_obj_list:
            return result

        _user_ids, valid_tractate_ids = [], []
        for t in tractate_obj_list:
            _user_ids.append(t.user_id)
            valid_tractate_ids.append(t.id)

        tractate_images_dic = cls.get_image_info_by_tractate_ids(valid_tractate_ids)
        tractate_video_dic = cls.get_video_info_by_tractate_ids(valid_tractate_ids)
        tractate_tags_dic = cls.get_tag_info_by_tractate_ids(valid_tractate_ids)
        tractate_new_tags_dic = cls.get_new_tag_info_by_tractate_ids(valid_tractate_ids)
        user_dic = cls.get_user_info_by_user_ids(_user_ids)

        follow_rels = need_special_info and user_id and \
                      cls.get_follow_info(viewer_user_id=user_id, author_ids=_user_ids) or {}

        for tractate in tractate_obj_list:
            _tractate_id = tractate.id
            _user_id = tractate.user_id

            user_info = user_dic.get(_user_id, {})
            images_list = tractate_images_dic.get(_tractate_id, [])
            video_item_list = tractate_video_dic.get(_tractate_id, [])
            tag_info = tractate_tags_dic.get(_tractate_id, [])
            new_tag_info = tractate_new_tags_dic.get(_tractate_id, [])
            base_data = cls.get_tractate_base_info(tractate)

            base_data.update({
                "videos": video_item_list,
                "user": user_info,
                "images": images_list,
                "tags": tag_info,
                "new_tags": new_tag_info,
                'is_voted': False,
                'is_favored': False,
                'is_following': follow_rels.get(_user_id, False),
            })

            result["tractate_list"].append(base_data)
        result["valid_tractate_ids"] = valid_tractate_ids

        return result

    @classmethod
    def user_del_tractate(cls, tractate_id, user_id):
        """
        用户删除帖子
        :param tractate_id: 帖子id
        :param user_id: 用户id
        :return:
        """
        try:
            tractate = Tractate.objects.get(pk=tractate_id, user_id=user_id)
            tractate.is_online = False
            tractate.user_del = True
            tractate.save(update_fields=["user_del", "is_online"])
            return gen(ERROR.SUCCESS)
        except Tractate.DoesNotExist:
            return gen(ERROR.TRACTATE_NOT_FOUND)

    @classmethod
    def get_check_message(cls, tractate_id):
        """
        根据帖子id获取审核 内容
        :param tractate_id:
        :return:
        """
        tractate_check = TractateCheck.objects.filter(
            tractate_id=tractate_id, status=TRACTATE_STATUS.AUDIT_REJECT
        ).order_by('-id').first()

        return {
            'message': tractate_check.send_message if tractate_check else ''
        }

    @classmethod
    def create_relation_service(cls, tractate_id, service_ids=[], order_service_ids=[]):
        """
        批量创建和帖子相关的美购数据
        :param tractate_id:
        :param service_ids:
        :return:
        """
        if not tractate_id:
            return gen(ERROR.TRACTATE_NOT_FOUND)

        tractate = cls.get_by_id(tractate_id)
        if not tractate:
            return gen(ERROR.TRACTATE_NOT_FOUND)

        relations_service_list = []
        for service_id in order_service_ids:
            relations_service_list.append(TractateRelationService(tractate_id=tractate_id, service_id=service_id, from_order=True))

        for service_id in service_ids:
            relations_service_list.append(TractateRelationService(tractate_id=tractate_id, service_id=service_id))

        TractateRelationService.objects.bulk_create(relations_service_list)

    @classmethod
    def get_relation_service_by_tractate_ids(cls, tractate_ids):
        """
        获取帖子对应的美购ID
        :param tractate_ids:
        :return:
        """
        all_service_ids, order_service_ids = {}, {}
        if not tractate_ids:
            return all_service_ids, order_service_ids

        relations = TractateRelationService.objects.filter(tractate_id__in=tractate_ids).order_by('-from_order')\
                                                   .values("tractate_id", "service_id", "from_order")
        for relation in relations:
            tractate_id = relation.get("tractate_id")
            if not tractate_id:
                continue
            if str(tractate_id) not in all_service_ids:
                all_service_ids[str(tractate_id)] = [relation.get("service_id", 0)]
                continue
            if str(tractate_id) in all_service_ids:
                all_service_ids[str(tractate_id)].append(relation.get("service_id", 0))

        for relation in relations:
            tractate_id = relation.get("tractate_id")
            if not tractate_id:
                continue
            if str(tractate_id) not in order_service_ids and relation.get("from_order", 0):
                order_service_ids[str(tractate_id)] = [relation.get("service_id", 0)]
                continue
            if str(tractate_id) in order_service_ids and relation.get("from_order", 0):
                order_service_ids[str(tractate_id)].append(relation.get("service_id", 0))

        return all_service_ids, order_service_ids
