# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function

import random
from operator import attrgetter
from itertools import chain

from django.conf import settings
from django.utils import timezone
from django.db.models import Q, F
from gm_types.gaia import TAG_ALERT_TYPE

from api.manager import tag_manager
from api.models import Tag, TagRelation, transaction, TagAlert, Service, SideSkidCategory, UserTagRelation
from api.business.tag import TagControl
from api.tool.user_tool import get_user_from_context, get_user_city_tagid
from api.tool.itemwiki_tool import ItemwikiHot
from api.models import (
    ItemWiki,
    RECOMMEND_TYPE,
    TAG_TYPE,
    Order,
    Zone,
    RecommendUserAttention,
)
from polymer.models import PolymerTag, PolymerAgileTag
from agile.models import AgileTagMapping, AgileTag
from talos.models.diary import Diary
from api.manager.tag_manager import (
    get_service_tags, get_bodypart_tags, get_wiki_by_tag,
    get_tag_by_id, get_province_tags,
    get_city_tags,
    get_tags_by_type,
    get_tags_by_ids,
)
from relation.models import UserTagRelation
from search.utils.diary import filter_diary
from search.utils.service import filter_service
from rpc.exceptions import RPCPermanentError, RPCPermissionDeniedException
from rpc.decorators import bind, bind_context, cache_page
from rpc.tool.error_code import CODES, gen
from rpc.decorators import list_interface
from rpc.tool.dict_mixin import to_dict
from api.tasks.tag_task import user_tag_relation_change
from gm_types.gaia import DIARY_ORDER_TYPE, USER_TAG_CHOICES


@bind_context("api/tag/show", login_required=True)
@list_interface(limit_name='each_page_amount')
def tag_show(ctx, page, each_page_amount, query, category):
    tag_types = tag_manager.get_all_tag_types()
    page_info = tag_manager.get_page_info(page, each_page_amount, query, category)
    tags = tag_manager.get_page_tags(page, each_page_amount, query, category)
    return {
        "page_info": page_info,
        "tag_types": tag_types,
        "tags": tags,
    }


@bind_context("api/tag/recovery_alert", login_required=True)
def tag_recovery_alert(ctx, alert_id, diary_id):
    """get alert info.

    prd link: http://wiki.gengmei.cc/pages/viewpage.action?pageId=1053307
    """
    # todo app中已无入口，不再维护！！20180313
    # todo app中已无入口，不再维护！！20180313
    # todo app中已无入口，不再维护！！20180313
    user = get_user_from_context(ctx)
    diary = Diary.objects.get(id=diary_id)
    # check diary's author is user
    if diary.user_id != user.id:
        return gen(CODES.DIARY_NOT_FOUND)

    ta = TagAlert.objects.get(id=alert_id)

    found_tag = False
    for t in diary.operation_tags:
        if t.id == ta.tag_id:
            found_tag = True
            break

    if not found_tag:
        return gen(CODES.DIARY_NOT_FOUND)

    # init with tag info, and alert info
    result = {
        'tag': ta.tag.to_dict(),
        'service_tag': ta.service_tag and ta.service_tag.to_dict() or None,
        'tagalert': [ta.get_alert_detail(), ],
        'service': {},
        'diaries': [],
    }

    # updated @6.0, dont show other recovery tips
    # http://wiki.gengmei.cc/pages/viewpage.action?pageId=1844925
    # get rest alert info
    # if tagalert type is pre_operation, dont return after operation alerts
    # if ta.type != TAG_ALERT_TYPE.PRE_OPERATION:
    #     q = Q()
    #     # if alert type is after_operation, exclude after operation_n_days
    #     if ta.type == TAG_ALERT_TYPE.AFTER_OPERATION:
    #         q &= Q(type__in=[TAG_ALERT_TYPE.PRE_OPERATION, TAG_ALERT_TYPE.AFTER_OPERATION])

    #     if ta.type == TAG_ALERT_TYPE.AFTER_OPERATION_DAYS:
    #         q &= Q(days__lte=ta.days)

    #     for t in ta.tag.tagalert_set.filter(q).exclude(id__in=[ta.id]):
    #         result['tagalert'].append(t.get_alert_detail())

    # get recommoned service
    filters = {}
    if ta.service_tag:
        filters = {'tag_ids': [ta.service_tag.id, ], }

    service_filter_result = filter_service(offset=0, size=1, filters=filters)
    service_id_list = service_filter_result['service_ids']
    if service_id_list:
        try:
            service = Service.objects.get(id=service_id_list[0])
            result['service'] = service.get_service_detail()
            from api.manager import coupon_manager
            result['service']["coupon_gifts"] = coupon_manager.get_top_coupon_gift_infos(service_id=service.id)
        except Service.DoesNotExist:
            pass

    # get related diaries
    diaries = []
    if ta.type == TAG_ALERT_TYPE.PRE_OPERATION:
        filters = {
            'tag_ids': [ta.tag_id, ],
            'has_cover': True,
        }
        diary_ids = filter_diary(
            offset=0,
            size=4,
            filters=filters, sort_type=DIARY_ORDER_TYPE.YOU_MAY_LIKE
        )['diary_ids']
        diaries = Diary.objects.filter(id__in=diary_ids)

    else:
        if diary.order_id:
            order = Order.objects.get(id=str(diary.order_id))
            validate_time = order.validate_time
            # get 4 orders validate time gte validate_time
            orders_gte = Order.objects.filter(
                validated=True,
                validate_time__gte=validate_time,
            ).exclude(id__in=[diary.order_id]).order_by('validate_time')[:10]

            # get 4 orders validate time lte validate_time
            orders_lte = Order.objects.filter(
                validated=True,
                validate_time__lte=validate_time,
            ).exclude(id__in=[diary.order_id]).order_by('-validate_time')[:10]

            # merge these
            now = timezone.now()
            orders = list(chain(orders_gte, orders_lte))
            has_diary_order_ids = list(map(
                lambda obj: str(obj),
                Diary.objects.filter(order_id__in=[order.id for order in orders]).values_list('order_id', flat=True))
            )
            orders = filter(
                lambda order: str(order.id) in has_diary_order_ids,
                orders
            )
            orders = sorted(orders, key=lambda x: abs((x.validate_time - now).total_seconds()))
            orders = orders[:4]
            diaries = map(lambda x: x.diary, orders)

    for d in diaries:
        result['diaries'].append({
            'id': d.id,
            'cover': d.cover and d.cover[-1] or None,
        })

    return result


@bind_context("api/tag/add", login_required=True)
def tag_add(ctx, name, tag_type):
    TagControl.add_tag(name=name, tag_type=tag_type)


@bind_context("api/tag/delete", login_required=True)
def tag_delete(ctx, tag_ids):
    raise RPCPermanentError("Tag can not delete")


@bind_context("api/tag/update", login_required=True)
def tag_update(ctx, id, name, tag_type):
    tag = TagControl.get_tag(pk=id)
    tag.name = name
    tag.tag_type = tag_type
    tag.save()


@bind_context("api/tagrel/show", login_required=True)
@list_interface(limit_name='each_page_amount')
def tagrel_show(ctx, page, each_page_amount, query):
    page_info = TagRelation.get_page_tagrelations_info(page, each_page_amount, query)
    tagrels = TagRelation.get_page_tagrelations(page, each_page_amount, query)
    tags = tag_manager.get_page_tags(page, each_page_amount, query, '')

    return {
        "page_info": page_info,
        "tagrels": tagrels,
        "tags": tags,
    }


@bind_context("api/tagrel/add", login_required=True)
def tagrel_add(ctx, parent_id, child_id):
    parent = TagControl.get_tag(parent_id)
    child = TagControl.get_tag(child_id)
    TagControl.add_relation(parent=parent, child=child)


@bind_context("api/tagrel/delete", login_required=True)
def tagrel_delete(ctx, tagrel_ids):
    TagRelation.objects.filter(id__in=tagrel_ids).soft_delete()


@bind_context("api/tagrel/delete/all", login_required=True)
def tagrel_delete_all(ctx):
    raise RPCPermanentError("Not Allowed")


@bind_context("api/tagrel/update", login_required=True)
def tagrel_update(ctx, id, parent_id, child_id):
    with transaction.atomic():
        TagRelation.objects.filter(id=id).soft_delete()

        parent = TagControl.get_tag(pk=parent_id)
        child = TagControl.get_tag(pk=child_id)
        TagControl.add_relation(parent=parent, child=child)


################################################################################
#                                                                              #
#                                    TAG 2                                     #
#                                                                              #
################################################################################

from rpc.decorators import bind_context
from gm_types.utils.enum import EnumMeta
from ..models.tag import dfs_parent_relation_closure
from ..business.tag import is_valid_tag_type
from django.db import models
from hera.models import BackendUser, UserPerm


def extract_enum(enum):
    assert isinstance(enum, EnumMeta)
    for enum_id, description in enum:
        yield {
            'id': int(enum_id),
            'description': description
        }


TAG_TYPE_items = list(extract_enum(TAG_TYPE))


class QueryResult(object):

    def __init__(self, fetch_related_tags=False, queried_tag_list=(), queried_tag_total_count=None):
        self._fetch_related_tags = fetch_related_tags
        self._tag_map = {}
        self._rel_set = set()

        self._queried = None
        if queried_tag_total_count is not None:
            queried_tag_id_list = []
            for tag in queried_tag_list:
                queried_tag_id_list.append(tag.id)
                self.add_tag_data(tag)

            self._queried = {
                'id_list': queried_tag_id_list,
                'total_count': queried_tag_total_count,
            }

    def add_tag_data(self, tag):
        self._tag_map[tag.id] = {
            'id': tag.id,
            'name': tag.name,
            'tag_type_id': int(tag.tag_type),
            'is_online': tag.is_online,
        }

    def add_relation_data(self, rel):
        self._rel_set.add((rel.parent_id, rel.child_id))
        if self._fetch_related_tags:
            self.add_tag_data(rel.parent)
            self.add_tag_data(rel.child)

    def to_json_data(self):
        return {
            'tag_type_list': list(TAG_TYPE_items),
            'tag_queried': self._queried,
            'tag_list': list(self._tag_map.values()),
            'tag_relations': [
                {
                    'parent_tag_id': parent_id,
                    'child_tag_id': child_id,
                }
                for parent_id, child_id in self._rel_set
            ]
        }


@bind('api/tag/tag_type/query')
def api_tag_type_query():
    """
    获取标签类型

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {}
    data Result = {
        tag_type_list  : Array[{
            id          : Int
            description : Str
        }]
        tag_queried     : None
        tag_list        : Array[Void]
        tag_relations   : Array[Void]
    }
    {-# Straight End #-}
    """

    qr = QueryResult()
    return qr.to_json_data()


def tag_query(tag_type_id=None, name=None, name_like=None, is_online=None, tag_id=None):
    tags = Tag.objects.all()

    if tag_type_id is not None:
        tag_type_id_str = str(tag_type_id)
        assert is_valid_tag_type(tag_type_id_str)
        tags = tags.filter(tag_type=tag_type_id_str)

    if name is not None:
        assert isinstance(name, basestring)
        tags = tags.filter(name=name)

    if name_like is not None and len(name_like) > 0:
        assert isinstance(name_like, basestring)
        tags = tags.filter(name__contains=name_like)

    if is_online is not None:
        assert isinstance(is_online, bool)
        tags = tags.filter(is_online=is_online)

    if tag_id is not None:
        tags = tags.filter(id=tag_id)

    tags = tags.order_by('-id')

    return tags


@bind('api/tag/tag/get')
def api_tag_get(tag_ids):
    """
    获取标签

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        tag_ids  : Array[Int]
    }
    data Result = {
        tag_type_list : Array[{
            id          : Int
            description : Str
        }]
        tag_queried   : None
        tag_list        : Array[{
            id          : Int
            name        : Str
            tag_type_id : Int
            is_online   : Bool
        }]
        tag_relations : Array[{
            parent_tag_id : Int
            child_tag_id  : Int
        }]
    }
    {-# Straight End #-}
    """

    qr = QueryResult()
    for tag in Tag.objects.filter(id__in=tag_ids):
        qr.add_tag_data(tag)
    return qr.to_json_data()


@bind('api/tag/tag/query')
@list_interface(offset_name='start_num', limit_name='count', element_model=Tag)
def api_tag_query(tag_type_id=None, name=None, name_like=None, is_online=None, start_num=None, count=None):
    """
    查询标签

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        tag_type_id  : Int  | None | Void
        name         : Str  | None | Void
        name_like    : Str  | None | Void
        is_online    : Bool | None | Void
        start_num    : Int  | None | Void
        count        : Int  | None | Void
    }
    data Result = {
        tag_type_list : Array[{
            id          : Int
            description : Str
        }]
        tag_queried   : {
            id_list     : Array[Int]
            total_count : Int
        }
        tag_list      : Array[{
            id          : Int
            name        : Str
            tag_type_id : Int
            is_online   : Bool
        }]
        tag_relations : Array[{
            parent_tag_id : Int
            child_tag_id  : Int
        }]
    }
    {-# Straight End #-}
    """

    tags = tag_query(tag_type_id=tag_type_id, name=name, name_like=name_like, is_online=is_online)

    if start_num is None:
        start_num = 0
    if count is None:
        count = 20

    tag_total_count = tags.count()
    tags = tags[start_num:start_num + count]

    qr = QueryResult(
        queried_tag_total_count=tag_total_count,
        queried_tag_list=list(tags),
    )

    return qr.to_json_data()


@bind('api/tag/all')
def api_tag_all(tag_id=None):
    """
        取所有tag，暂时用于搜索自动补充，ascle redis缓存需要
    """
    if not tag_id:
        tags = tag_query()
    else:
        tags = tag_query(tag_id=tag_id)
    return [{'id': tag.id, 'name': tag.name} for tag in tags]


@bind('api/tag/relation/query')
def api_tag_relation_query(fetch_related_tags, parent_ids=None, child_ids=None, parent_or_child_ids=None):
    """
    获取标签关系

    parent_ids, child_ids, parent_or_child_ids 不能同时为空

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        fetch_related_tags  : Bool
        parent_ids          : Array[Int] | None | Void
        child_ids           : Array[Int] | None | Void
        parent_or_child_ids : Array[Int] | None | Void
    }
    data Result = {
        tag_type_list : Array[{
            id          : Int
            description : Str
        }]
        tag_queried   : None
        tag_list        : Array[{
            id          : Int
            name        : Str
            tag_type_id : Int
            is_online   : Bool
        }]
        tag_relations : Array[{
            parent_tag_id : Int
            child_tag_id  : Int
        }]
    }
    {-# Straight End #-}
    """

    if parent_ids is None and child_ids is None and parent_or_child_ids is None:
        raise RPCPermanentError(message="缺少条件")

    tag_relations = TagRelation.objects.all()

    q1 = models.Q()
    q2 = models.Q()
    if parent_ids is not None:
        q1 &= models.Q(parent_id__in=parent_ids)
    if child_ids is not None:
        q1 &= models.Q(child_id__in=child_ids)

    if parent_or_child_ids is not None:
        q2 |= models.Q(parent_id__in=parent_or_child_ids)
        q2 |= models.Q(child_id__in=parent_or_child_ids)

    q = q1 | q2

    tag_relations = tag_relations.filter(q)

    if fetch_related_tags:
        tag_relations = tag_relations.select_related('parent', 'child')

    qr = QueryResult(fetch_related_tags=fetch_related_tags)
    for rel in tag_relations:
        qr.add_relation_data(rel)

    return qr.to_json_data()


@bind('api/tag/relation/query/ancestors')
def api_tag_relation_query(fetch_related_tags, initial_tag_ids):
    """
    获取祖先标签及关系

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        fetch_related_tags : Bool        -- 是否获取标签详情
        initial_tag_ids    : Array[Int]  -- 获取这些 id 的祖先
    }
    data Result = {
        tag_type_list : Array[{
            id          : Int
            description : Str
        }]
        tag_queried   : None
        tag_list        : Array[{
            id          : Int
            name        : Str
            tag_type_id : Int
            is_online   : Bool
        }]
        tag_relations : Array[{
            parent_tag_id : Int
            child_tag_id  : Int
        }]
    }
    {-# Straight End #-}
    """
    assert isinstance(initial_tag_ids, list)
    tag_relations = dfs_parent_relation_closure(initial_tag_ids=initial_tag_ids,
                                                fetch_related_tags=fetch_related_tags).values()

    qr = QueryResult(fetch_related_tags=fetch_related_tags)
    for rel in tag_relations:
        qr.add_relation_data(rel)

    return qr.to_json_data()


def tag_add_for_business(ctx, name, tag_type_id):
    tag_type_id_str = str(tag_type_id)

    if tag_type_id_str != TAG_TYPE.FREE:
        raise RPCPermissionDeniedException

    tag = TagControl.add_tag(name=name, tag_type=tag_type_id_str)
    return {
        'id': tag.id,
        'name': tag.name,
        'tag_type_id': int(tag.tag_type),
        'is_online': tag.is_online,
        'free_to_add': tag.free_to_add,
    }


def tag_add_for_hera(ctx, name, tag_type_id):
    if not UserPerm.check_perm(ctx.session.user, ['tag.can_create']):
        raise RPCPermissionDeniedException

    tag_type_id_str = str(tag_type_id)
    tag = TagControl.add_tag(name=name, tag_type=tag_type_id_str)
    return {
        'id': tag.id,
        'name': tag.name,
        'tag_type_id': int(tag.tag_type),
        'is_online': tag.is_online,
        'free_to_add': tag.free_to_add,
    }


@bind_context(
    "api/tag/tag/add",
    login_required=True,
)
def tag_add(ctx, name, tag_type_id):
    """
    添加标签

    NOTE: maybe this api url should be `api/tag/tag/get_or_create`, when we has to
          distinguish from getting an exist one or creating a new one, update
          this api url.

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        name        : Str
        tag_type_id : Int | Str -- compaitable with type int and str
    }
    data Result = {
        id          : Int
        name        : Str
        tag_type_id : Int
        is_online   : Bool
        free_to_add : Bool
    }
    {-# Straight End #-}
    """

    if BackendUser.check_staff(ctx.session.user):
        return tag_add_for_hera(ctx, name, tag_type_id)
    else:
        return tag_add_for_business(ctx, name, tag_type_id)


@bind(
    "api/tag/tag/set",
    login_required=True,
    # TODO: 限定权限
    perm_required=['tag.can_modify', 'tag.can_delete'],
)
def tag_set(id, name=None, tag_type_id=None, is_online=None):
    """
    更新标签

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        id          : Int
        name        : Str  | None | Void
        tag_type_id : Int  | None | Void
        is_online   : Bool | None | Void
    }
    data Result = None
    {-# Straight End #-}
    """

    tag = Tag.objects.get(pk=id)

    if name is not None:
        tag.name = name
    if tag_type_id is not None:
        tag_type_id_str = str(tag_type_id)
        tag.tag_type = tag_type_id_str
    if is_online is not None:
        tag.is_online = is_online
    tag.save()


@bind(
    "api/tag/relation/change",
    login_required=True,
    # TODO: 怎么限定权限？？？
    perm_required=['tag.can_modify', 'tag.can_delete'],
)
def tag_relation_change(relations_to_add, relations_to_delete):
    """
    添加、删除标签关系

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        relations_to_add    : Array[{
            parent_id : Int
            child_id  : Int
        }]
        relations_to_delete : Array[{
            parent_id : Int
            child_id  : Int
        }]
    }
    data Result = None
    {-# Straight End #-}
    """
    with transaction.atomic():
        for rel in relations_to_delete:
            parent_id = rel['parent_id']
            child_id = rel['child_id']
            TagRelation.del_relation_by_id(parent_id, child_id)
        for rel in relations_to_add:
            parent_id = rel['parent_id']
            child_id = rel['child_id']
            TagRelation.add_relation_by_id(parent_id, child_id)


@bind('api/tag/type/city')
def city_tags(province_id=None, is_online=True):
    return get_city_tags(province_id, is_online)


@bind('api/tag/type/province')
def province_tags(with_city_tag=False, is_online=True):
    return get_province_tags(with_city_tag, is_online)


@bind('api/tag/type/wiki')
def tag_wiki(tag_id, is_online=True):
    """get wiki list by tag_id."""
    tag = get_tag_by_id(tag_id)

    # if it's a wiki tag, return directly
    if tag.tag_type == TAG_TYPE.ITEM_WIKI:
        wiki = ItemWiki.get_wiki_by_tagid(tag_id)
        if wiki and (not is_online or (is_online and tag.is_online)):
            return [{'wiki_name': tag.name, 'tag_id': tag.id, 'wiki_id': wiki.id, 'item_name': wiki.item_name}]
        return []

    # if it's not a body part or sub body part item return no wikis error
    if tag.tag_type not in (TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM):
        raise gen(CODES.TAG_HAS_NO_WIKI)

    wiki_tags = get_wiki_by_tag(tag, is_online)
    if not wiki_tags:
        raise gen(CODES.TAG_HAS_NO_WIKI)

    return wiki_tags


@bind('api/tag/type/bodypart')
# @cache_page(7200)
def bodypart_tags(with_level_3=False, is_online=True, ordering=False):
    """level one tags, and sub_bodypart_items tags.

    args:
        if with_level_3 is true, sub tags includes wikiitem tags.
    """
    return get_bodypart_tags(with_level_3, is_online, ordering=ordering)


# @bind_context('api/tag/topics/type/ask')
# @list_interface(offset_name='start_num', limit_name='count', element_model=Problem, element_func_list=[Problem.get_topic_info])
# def tag_topics(ctx, tag_id, start_num=0, count=10, order_by=0):
#     """get topic type `ask` topics by tag_id."""
#     # todo 7660上线后可删，不再维护
#     filters = {'tag_ids': [tag_id, ], 'topic_type': TOPIC_TYPE.ASK}
#
#     tag_id = assert_uint(tag_id)
#     start_num = assert_uint(start_num, 0)
#     count = assert_uint(count, 10)
#
#     user_city_tag_id = None
#     if order_by == PROBLEM_ORDER_TYPE.CITY:
#         user = get_user_from_context(ctx)
#         if user:
#             user_city_tag_id = get_user_city_tagid(user.id)
#
#     topic_ids = filter_topic(
#         offset=start_num,
#         size=count,
#         user_city_tag_id=user_city_tag_id,
#         sort_type=order_by,
#         filters=filters
#     )
#
#     topics = Problem.objects.filter(id__in=topic_ids['topic_ids'])
#     topics = {t.id: t for t in topics}
#
#     result = []
#     user = get_user_from_context(ctx)
#
#     for tid in topic_ids['topic_ids']:
#         if tid not in topics:
#             continue
#         result.append(topics[tid].get_topic_info(user))
#
#     return result
#
#
# @bind_context('api/tag/topics/filter')
# @list_interface(offset_name='start_num', limit_name='count', element_model=Problem, element_func_list=[Problem.get_topic_info])
# def tag_topics_filter(ctx, tag_id, is_elite=None, start_num=0, count=10, order_by=0):
#     """get topics related to `tag id`."""
#     # todo 已无调用，可删 180314
#     filters = {'tag_ids': [tag_id, ]}
#     if is_elite is True:
#         filters['is_elite'] = True
#
#     tag_id = assert_uint(tag_id)
#     start_num = assert_uint(start_num, 0)
#     count = assert_uint(count, 10)
#     sort_type = order_by or PROBLEM_ORDER_TYPE.DEFAULT
#
#     user_city_tag_id = None
#     if order_by == PROBLEM_ORDER_TYPE.CITY:
#         user = get_user_from_context(ctx)
#         if user:
#             user_city_tag_id = get_user_city_tagid(user.id)
#
#     topic_ids = filter_topic(
#         offset=start_num,
#         size=count,
#         user_city_tag_id=user_city_tag_id,
#         sort_type = order_by,
#         filters=filters
#     )
#
#     topics = Problem.objects.filter(id__in=topic_ids['topic_ids'])
#     topics = {topic.id: topic for topic in topics}
#     topic_view_ids = topics.keys()
#
#     topic_view_increase_num(topic_view_ids)
#
#     user = get_user_from_context(ctx)
#
#     result = []
#     for tid in topic_ids['topic_ids']:
#         if tid not in topics:
#             continue
#         result.append(topics[tid].get_topic_info(user))
#
#     return result


@bind_context('api/tag/as_zone/personal')
@list_interface(limit_name='count')
def my_tag_detail(ctx, tag_id, start_num=0, count=10):
    """return tag related information.

    NOTE:
        get tag's information such as name, icon etc. and return user
        followed tag ids.
        if tag is related to a wiki, set `is_wiki_tag` as true, return
        wiki_id as well.
    """
    tag = get_tag_by_id(tag_id)

    tag_info = {}
    tag_info.update(tag.get_tag_info())
    tag_info['is_wiki_tag'] = False
    tag_info['wiki_id'] = None

    if tag.tag_type == TAG_TYPE.ITEM_WIKI:
        wiki = ItemWiki.get_wiki_by_tagid(tag.id)
        if wiki:
            tag_info['is_wiki_tag'] = True
            tag_info['wiki_id'] = wiki.id

    # check if user follow tag
    user = get_user_from_context(ctx)
    tag_info['followed'] = False
    if user:
        followed = UserTagRelation.user_follow_tag(user.id, tag.id)
        tag_info['followed'] = followed

    return tag_info


@bind_context('api/tag/as_zone/personal_V2')
@list_interface(limit_name='count')
def my_tag_detailV2(ctx, tag_id, start_num=0, count=10):
    """return tag related information.

    NOTE:
        get tag's information such as name, icon etc. and return user
        followed tag ids.
        if tag is related to a wiki, set `is_wiki_tag` as true, return
        wiki_id as well.
        6.0新版
    """
    tag = get_tag_by_id(tag_id)

    tag_info = {}
    tag_info.update(tag.get_tag_infoV2())
    tag_info['is_wiki_tag'] = False

    if not tag_info['has_zone']:
        tag_info['wiki_id'] = None
        if tag.tag_type == TAG_TYPE.ITEM_WIKI:
            wiki = ItemWiki.get_wiki_by_tagid(tag.id)
            if wiki:
                tag_info['is_wiki_tag'] = True
                tag_info['wiki_id'] = wiki.id

    # check if user follow tag
    user = get_user_from_context(ctx)
    tag_info['followed'] = False
    if user:
        followed = UserTagRelation.user_follow_tag(user.id, tag.id)
        tag_info['followed'] = followed

    return tag_info


@bind_context('api/tag/follow', login_required=True)
def follow_tag(ctx, tag_ids):
    """follow tags.

    args:
        tag_ids: list of tags
    """
    if not tag_ids:
        raise gen(CODES.PARAMS_INCOMPLETE)

    user = get_user_from_context(ctx)
    UserTagRelation.multi_follow_tags(user.id, tag_ids)
    user_tag_relation_change.delay(user)
    return gen(CODES.SUCCESS)


@bind_context('api/tag/unfollow', login_required=True)
def follow_tag(ctx, tag_id):
    if not tag_id:
        raise gen(CODES.PARAMS_INCOMPLETE)

    # make sure user in table
    user = get_user_from_context(ctx)
    UserTagRelation.unfollow_tag(user.id, tag_id)
    user_tag_relation_change.delay(user)
    return gen(CODES.SUCCESS)


@bind('api/tag/service')
def tag_service(service_id):
    """get service realted tags."""
    result = {}
    if service_id:
        result['service_tags'] = get_service_tags(service_id)
    return result


@bind('api/tag/info')
@cache_page(120)
def tag_info(tag_id):
    tag = get_tag_by_id(tag_id)
    return tag.get_tag_info()


@bind('api/tag/info_by_ids')
def tags_info(tag_ids, new_tag=False):
    """ return tag info

    :param tag_ids:
    :return:
        list of tag info as {
            id, tag_type, name
        }
    """

    if new_tag:
        tags = AgileTag.objects.filter(pk__in=tag_ids)
        return [to_dict(t, fields=('id', 'name', 'attribute')) for t in tags]
    else:
        tags = get_tags_by_ids(tag_ids)
        return [to_dict(t, fields=('id', 'name', 'tag_type')) for t in tags]

@bind('api/tag/info_by_ids_type')
def tags_info_type(tag_ids):
    """ return tag info

    :param tag_ids:
    :return:
        list of tag info as {
            id, tag_type, name
        }
    """
    tags = get_tags_by_ids(tag_ids)
    return [to_dict(t, fields=('id', 'name', 'tag_type', 'recommend_type')) for t in tags]


@bind_context('api/zone/count', login_required=True)
def get_tag_count(ctx):
    user = get_user_from_context(ctx)
    num = len(UserTagRelation.followed_tags_obj(user.id))
    return num


@bind('api/tag/user_followed_tag_obj')
def user_followed_tags_obj(user_id):
    objs = UserTagRelation.followed_tags_obj(user_id)
    dict_objs = map(to_dict, objs)
    for d in dict_objs:
        d.pop('managers')
    return dict_objs


@bind('api/tag/tractate_list')
def tractate_recommend_tag_list():
    result = {}
    base_query = Q(is_online=True, free_to_add=True)
    filter_query = base_query & Q(recommend_type=RECOMMEND_TYPE.RECOMMENDED_TRACTATE)

    tractate_tags = Tag.objects.filter(filter_query).order_by("ordering", "-id")
    result.update({
        'recommend_tags': [obj.get_tag_info() for obj in tractate_tags],
    })
    return result


@bind_context('api/tag/list')
def tag_list(ctx):
    """tag index page."""
    user = get_user_from_context(ctx)

    result = {}

    # get suozhang recommend/hot tag objs
    recommended_tags = Tag.objects.filter(
        is_online=True,
        free_to_add=True,
        recommend_type__in=[RECOMMEND_TYPE.RECOMMENDED_BY_SUOZHANG, RECOMMEND_TYPE.RECOMMENDED_HOT]
    ).order_by('ordering')

    sz_tags = []
    hot_tags = []
    for tag in recommended_tags:
        if tag.recommend_type == RECOMMEND_TYPE.RECOMMENDED_BY_SUOZHANG:
            sz_tags.append(tag)
        else:
            hot_tags.append(tag)

    # get user tags includes followed and created
    user_tags = []
    if user:
        # my tag only include tags have followed

        user_tags = UserTagRelation.followed_tags_obj(user.id)

    # get user created tags
    result.update({
        'my_tags': [obj.get_tag_info() for obj in user_tags],
        'recommend_tags': [obj.get_tag_info() for obj in sz_tags],
        'hot_tags': [obj.get_tag_info() for obj in hot_tags],
    })
    return result


@bind_context('api/tag/type_zone')
def tag_type_zone(ctx, tag_types, start_num=0, count=10, only_my=False):
    user = get_user_from_context(ctx)
    end_num = start_num + count
    user_tags = []
    if user:
        user_tags = UserTagRelation.followed_tags_obj(user.id)

    user_tags = filter(
        lambda x: x.tag_type in tag_types,
        user_tags
    )

    if only_my:
        user_tags = user_tags[start_num:end_num]
        result = {
            'my_tags': [obj.get_tag_infoV2() for obj in user_tags]
        }

    else:
        all_tags = Zone.objects.select_related().filter(
            is_online=True,
            tag__tag_type__in=tag_types
        ).order_by("circle_rank")[start_num:end_num]

        result = {
            'my_tags': [obj.get_tag_infoV2() for obj in user_tags],
            'all_tags': [obj.tag.get_tag_infoV2() for obj in all_tags],
        }
    return result


@bind('api/tag/recommended')
def tag_recommended(recommend_type, start_num=0, count=10):
    """get recommended tags by recommend type."""
    if recommend_type not in (RECOMMEND_TYPE.RECOMMENDED_BY_SUOZHANG,
                              RECOMMEND_TYPE.RECOMMENDED_HOT, RECOMMEND_TYPE.RECOMMENDED_IN_PAST,
                              RECOMMEND_TYPE.RECOMMENDED_TO_FOLLOW):
        return gen(CODES.PARAMS_INVALID)

    tags = Tag.objects.filter(Q(recommend_type=recommend_type)).order_by('ordering')
    return [t.get_tag_info() for t in tags[start_num:start_num + count]]


@bind('api/tag/recommended_to_follow')
def tag_recommended(start_num=0, count=10):
    """get recommended tags by recommend type."""
    tags = Tag.objects.filter(Q(recommended_to_follow=True))
    tags = tags.order_by('tag_type', 'ordering')
    return [t.get_tag_info() for t in tags[start_num:start_num + count]]


@bind('api/tag/recommended_interest')
def tag_recommended_interest(start_num=0, count=10):
    recommend_tags = RecommendUserAttention.objects.filter(is_online=True).order_by('ordering')[
                     start_num:start_num + count]
    result = []
    for recommend_tag in recommend_tags:
        data = {}
        data['icon'] = recommend_tag.image_url
        data['id'] = recommend_tag.tag.id
        data['name'] = recommend_tag.tag.name
        result.append(data)
    return result


# @bind_context('api/tag/diaries')
# @list_interface(offset_name='start_num', limit_name='count', element_model=Diary)
# def tag_diaries(ctx, tag_id, start_num=0, count=10, doctor_id=0, hospital_id=0):
#     """get diary list by tag_id."""
#     # todo 7660上线后，可删，不再维护
#     user = get_user_from_context(ctx)
#     filters = {
#         'tag_ids': [int(tag_id)]
#     }
#     if doctor_id:
#         filters['doctor_id'] = doctor_id
#     if hospital_id:
#         filters['hospital_id'] = hospital_id
#
#     diary_ids = filter_diary(offset=start_num, size=count,
#                              filters=filters, sort_type=DIARY_ORDER_TYPE.HOT)['diary_ids']
#     diaries = Diary.objects.filter(id__in=diary_ids)
#     diaries = sorted(diaries, key=lambda f: diary_ids.index(f.pk))
#
#     result = []
#     for d in diaries:
#         diary_data = d.get_diary_info(user=user)
#         diary_data['view_num'] = d.view_num
#         result.append(diary_data)
#
#     return result


@bind('api/tag/restricted')
def tag_restricted():
    """get tags with flag free_to_add set to false."""
    tags = Tag.objects.filter(free_to_add=False)

    result = []
    for tag in tags:
        result.append(tag.get_tag_info())
    return result


@bind_context('api/tag/hot')
def hot_tags(ctx):
    hot_tags = ItemwikiHot.get_objs()

    if not hot_tags:
        return {'wiki_groups': {}}

    hot_data = {
        'wiki': [],
        'group_name': u'热门',
        'icon': settings.HOT_TAG_ICON,
    }
    for tag in hot_tags:
        hot_data['wiki'].append(tag.wiki_data())
    return {'wiki_groups': hot_data}


@bind('api/tag/tag_by_type')
@cache_page(120)
def get_tag_by_type_v2(tag_type=TAG_TYPE.BODY_PART):
    tags = SideSkidCategory.objects.filter(is_online=True).order_by('ordering', 'created_time')
    data = [tag.to_dict() for tag in tags]
    return data


@bind('api/tag/get_gmhome_tag')
def get_gmhome_tag(tag_id):
    tags = SideSkidCategory.objects.get(id=tag_id).tags.all()
    data = [tag.id for tag in tags]
    return data


def _filter_tag_by_type(types):
    return Tag.objects.filter(is_online=True, tag_type__in=types)


@bind('api/tag/iterator')
@list_interface(limit_name='count')
def tag_iterator(start_num=0, count=10, is_online=True):
    """only return tag type bodypart bodypart_subitem and itemwiki."""
    tags = _filter_tag_by_type([TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI])
    return [t.to_dict() for t in tags[start_num:start_num + count]]


@bind('api/tag/iterator_all_tags')
@list_interface(limit_name='count')
def tag_iterator_operation_tags(start_num=0, count=10, is_online=True):
    """only return tag type bodypart bodypart_subitem and itemwiki, city, country, province. """
    tags = Tag.objects.filter(is_online=True)
    return [t.to_dict() for t in tags[start_num:start_num + count]]


@bind('api/tag/join_count')
def zone_join_count(tag_id):
    # deprecated at 2017-11-21 by pengfei.x
    # this api is used in old diarybook detail h5 page, already been deprecated
    joined_count = random.randint(10 ** 4, 10 ** 5)
    return {'joined': joined_count}


@bind('api/tag/for_fault_toleration')
@cache_page(120)
def correct_tag_name():
    types = [
        TAG_TYPE.BODY_PART,
        TAG_TYPE.BODY_PART_SUB_ITEM,
        TAG_TYPE.ITEM_WIKI,
    ]
    return get_tags_by_type(types)


@bind('api/tag/bodypart_tag_id_by_service_id')
def bodypart_tag_id_by_service_id(id):
    try:
        service = Service.objects.get(pk=id)
    except Service.DoesNotExist:
        return

    if not service.doctor:
        return

    # 选出一/二/三级tag_id中最小的
    tags = service.tags.filter(tag_type__in=[
        TAG_TYPE.BODY_PART,
        TAG_TYPE.BODY_PART_SUB_ITEM,
        TAG_TYPE.ITEM_WIKI,
    ]).order_by('pk')

    if len(tags) <= 0:
        return

    target_tags = [tags[0], ]
    target_tags = TagControl.get_ancestors(
        initial_set=target_tags,
        exclude_init=False,
        is_online_only=True
    )
    target_tags = filter(lambda t: t.tag_type in [
        TAG_TYPE.BODY_PART,
        TAG_TYPE.BODY_PART_SUB_ITEM,
        TAG_TYPE.ITEM_WIKI], target_tags
                         )

    if not target_tags:
        return

    for t in target_tags:
        if t.tag_type == TAG_TYPE.BODY_PART:
            target_bodypart_tag = t
            return target_bodypart_tag.id, [t.id for t in target_tags]


@bind('api/tag/closure_tags')
def closure_tags_info(tag_ids, is_online_only=True):
    """
    获取tag_ids所对应的tag闭包
    :param tag_ids:
    :param is_online_only: 闭包路径上是否要求都是在线的
    :return:
    """
    tags = Tag.objects.filter(id__in=tag_ids)
    closure_tags = TagControl.get_ancestors(
        initial_set=tags,
        exclude_init=False,
        is_online_only=is_online_only)

    return [to_dict(t, fields=('id', 'name', 'tag_type')) for t in closure_tags]


@bind('api/tag/second_tag_id_by_diary_tags')
def get_second_tag_id_by_diary_tags(diary_tags):
    """
    此接口为一次性接口, 等之后产品修改相关逻辑后,即可删除相关代码
    """
    has_second_tag = False
    second_tag_id = None
    for t in diary_tags:
        if t['type'] == TAG_TYPE.BODY_PART_SUB_ITEM:
            second_tag_id = t['tag_id']
            has_second_tag = True
            break
    if not has_second_tag:
        for t in diary_tags:
            if t['type'] == TAG_TYPE.ITEM_WIKI:
                tag = Tag.objects.get(id=t['tag_id'])
                parent_tags = tag.parent_tags()
                for p in parent_tags:
                    if p.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM:
                        second_tag_id = p.id
                        break
    return second_tag_id


@bind('api/tag/hera_search')
def tag_hera_search(query, tag_type_in=None, is_online=None):
    """
    tag全文搜索接口
    只允许hera一类的后台应用使用
    绝对禁止高频调用！

    :param query:
    :param tag_type_in:
    :param is_online:
    :return:
    """
    qs = Tag.objects.filter(name__contains=query)
    if tag_type_in:
        qs = qs.filter(tag_type__in=tag_type_in)

    if is_online:
        qs = qs.filter(is_online=True)

    return [to_dict(t, fields=('id', 'name', 'tag_type')) for t in qs]


@bind_context('api/tag/user_tag')
def get_user_tag_ids(ctx):
    """
    获取用户的关联 tag
    :param ctx:
    :return:
    """
    user = get_user_from_context(ctx)
    if not user:
        return []
    tags = UserTagRelation._get_tags_obj(user.id, USER_TAG_CHOICES.FOLLOWED)
    return [tag.id for tag in tags]


@bind('api/tag/related_tags')
def get_related_tags(tag_ids):
    if not tag_ids:
        return []

    result = []
    qs = Tag.objects.filter(id__in=tag_ids)
    for self in qs:
        item = {
            'id': self.id,
            'parents': [],
            'resembles': map(attrgetter('id'), self.resemble_tags()),
            'childrens': []
        }

        # 一级标签
        if self.tag_type == TAG_TYPE.BODY_PART:
            objs = self.online_child_tags()
            objs = filter(lambda x: x.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM, objs)
            item['childrens'] = map(attrgetter('id'), objs)
        # 三级标签
        elif self.tag_type == TAG_TYPE.ITEM_WIKI:
            objs = self.online_parent_tags()
            objs = filter(lambda x: x.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM, objs)

            for obj in objs:
                resembles = map(attrgetter('id'), obj.resemble_tags())
                item['resembles'].extend(resembles)

            item['parents'] = map(attrgetter('id'), objs)

        result.append(item)

    return result


@bind('api/tag/related_tags_v2')
def get_related_tags_v2(tag_ids):
    if not tag_ids:
        return []

    result = []
    qs = Tag.objects.filter(id__in=tag_ids)
    for self in qs:
        item = {
            'id': self.id,
            'parents': [],
            'resembles': [{"tag_type": item.tag_type, "id": item.id} for item in self.resemble_tags()],
            'childrens': []
        }

        # 一级标签
        if self.tag_type == TAG_TYPE.BODY_PART:
            objs = self.online_child_tags()
            objs = filter(lambda x: x.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM, objs)
            item['childrens'] = [{"tag_type": o.tag_type, "id": o.id} for o in objs]
        # 三级标签
        elif self.tag_type == TAG_TYPE.ITEM_WIKI:
            objs = self.online_parent_tags()
            objs = filter(lambda x: x.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM, objs)

            for obj in objs:
                resembles = [{"tag_type": o.tag_type, "id": o.id} for o in obj.resemble_tags()]
                item['resembles'].extend(resembles)

            item['parents'] = [{"tag_type": o.tag_type, "id": o.id} for o in objs]

        result.append(item)

    return result


@bind('api/tag/get_tag_by_ids')
def get_tag_info_by_ids(ids):
    if isinstance(ids, list):
        tag_info = []
        for id in ids:
            obj = get_tag_by_id(id)
            tag_info.append({'id': obj.id, 'name': obj.name})
        return tag_info


@bind('api/tag/get_tag_id_by_name')
def get_tag_id_by_name(name):
    try:
        ids_list = Tag.objects.filter(name__contains=name).values_list('id', flat=True)
        ids = map(lambda x: int(x), ids_list)
        return ids
    except Exception as e:
        return None


@bind('api/only/second_tags')
def get_only_second_tags():
    query_data = Tag.objects.filter(tag_type=TAG_TYPE.BODY_PART_SUB_ITEM, is_online=True).all() \
        .values_list('id', 'name')

    data = list()
    if not query_data:
        return data

    if len(query_data) == 0:
        return data

    data = list({'id': str(item[0]), 'name': item[1]} for item in query_data)
    return data


@bind('api/tag/recommend')
def get_recommend_tags():
    recommend_types = [RECOMMEND_TYPE.CLASS_A, RECOMMEND_TYPE.CLASS_B]
    recommend_info = zip(recommend_types, settings.ALL_TAGS_RECOMMEND_NAME)
    result = []
    for recommend_type, name in recommend_info:
        tag_query = Tag.objects.filter(recommend_type=recommend_type, is_online=True)
        if tag_query.exists():
            tags = tag_query.order_by('ordering').annotate(type=F('tag_type'),
                                                           tag_id=F('id')).values(
                'name', 'tag_id', 'type')[:9]
            class_data = {
                'tag_id': -1,
                'name': name,
                'subtags': list(tags),
            }
            result.append(class_data)
    return result


@bind('api/tag/info_by_ids_v2')
def tags_info_v2(tag_ids):
    """
    add in 7.7.70
    主要目的：返回一些多余字段，不影响现有逻辑调用。如需要其他数据，请完善接口
    :param tag_ids:
    :return:
        list of tag info as {
            id, tag_type, name
        }
    """
    tags = get_tags_by_ids(tag_ids)
    return [to_dict(t, fields=('id', 'name', 'tag_type', 'recommend_type')) for t in tags]


@bind("api/tag/children_parent_by_ids")
@cache_page(60 * 10)
def list_children_parent_tags_by_ids(tag_ids, new_tag=False):

    if new_tag:
        mapping = AgileTagMapping.objects.filter(agile_tag_id__in=tag_ids)
    else:
        mapping = AgileTagMapping.objects.filter(old_tag_id__in=tag_ids)

    old_tag_ids = [] if new_tag else tag_ids
    agile_tag_ids = tag_ids if new_tag else []
    for item in mapping:
        old_tag_ids.append(item.old_tag_id)
        agile_tag_ids.append(item.agile_tag_id)

    tags = Tag.objects.filter(pk__in=old_tag_ids, is_online=True)
    agile_tags = AgileTag.objects.filter(pk__in=agile_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])

    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],
        "agile_tags": [to_dict(t, fields=('id', 'name', 'attribute')) for t in agile_tags],
    }


@bind("api/tag/info_by_polymer_id")
@cache_page(60 * 10)
def list_tags_by_polymer_id(polymer_id, simple=False):

    tag_ids = list(
        PolymerTag.objects.filter(polymer_id=polymer_id,
        is_online=True).values_list("tag_id", flat=True)
    )
    agile_tag_ids = list(
        PolymerAgileTag.objects.filter(polymer_id=polymer_id,
        is_online=True).values_list("agile_tag_id", flat=True)
    )

    if not simple:
        mapping = AgileTagMapping.objects.filter(Q(agile_tag_id__in=agile_tag_ids)|Q(old_tag_id__in=tag_ids))
        for item in mapping:
            tag_ids.append(item.old_tag_id)
            agile_tag_ids.append(item.agile_tag_id)

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

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

    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],
        "agile_tags": [to_dict(t, fields=('id', 'name', 'attribute')) for t in agile_tags],
    }

