# coding=utf8
from __future__ import unicode_literals, absolute_import, print_function

import operator

from django.db import models
from django.db.models import Q
from django.db import transaction
from gm_types.gaia import TAG_ALERT_TYPE

from .types import TAG_TYPE, RECOMMEND_TYPE, PRIVATE_STATUS, TOPIC_TYPE
import api.models
from rpc.exceptions import RPCTagRelationCycleException
from rpc.tool.dict_mixin import to_dict
from django.utils import timezone
from gm_upload import IMG_TYPE, ImgUrlField

class TagRelationCycleError(RPCTagRelationCycleException):

    def __init__(self, message=None):
        super(TagRelationCycleError, self).__init__(message=message)


def dfs_closure(initial_set, key_func, adj_enum_func, adj_batch_enum_func, exclude_init):
    assert adj_batch_enum_func or adj_enum_func
    stack = []
    result_dict = {}

    def add(n):
        key = key_func(n)
        if key in result_dict:
            return
        stack.append(n)
        result_dict[key] = n

    def add_list(node_list):
        for n in node_list:
            add(n)

    add_list(initial_set)

    while len(stack) > 0:
        if adj_batch_enum_func:
            next_nodes = adj_batch_enum_func(stack)
            stack = []
        else:
            node = stack.pop()
            next_nodes = adj_enum_func(node)
        add_list(next_nodes)

    if exclude_init:
        for node in initial_set:
            del result_dict[key_func(node)]

    return result_dict


def dfs_child_closure(initial_set, exclude_init, is_online_only=None):
    initial_set = list(initial_set)
    qs = TagRelation.objects.all().select_related('child')

    if is_online_only:
        initial_set = [tag for tag in initial_set if tag.is_online]
        qs = qs.filter(child__is_online=True)

    adj_batch_enum_func = lambda tag_list: [
        rel.child
        for rel in qs.filter(
            parent_id__in=[
                tag.id for tag in tag_list
            ]
        )
    ]

    return dfs_closure(
        initial_set=initial_set,
        key_func=operator.attrgetter('id'),
        # adj_enum_func=lambda tag: tag.child_tags(),
        adj_enum_func=None,
        adj_batch_enum_func=adj_batch_enum_func,
        exclude_init=exclude_init,
    )


def dfs_parent_closure(initial_set, exclude_init, is_online_only=None):
    initial_set = list(initial_set)
    qs = TagRelation.objects.all().select_related('parent')

    if is_online_only:
        initial_set = [tag for tag in initial_set if tag.is_online]
        qs = qs.filter(parent__is_online=True)

    adj_batch_enum_func = lambda tag_list: [
        rel.parent
        for rel in qs.filter(
            child_id__in=[
                tag.id for tag in tag_list
            ]
        )
    ]

    return dfs_closure(
        initial_set=initial_set,
        key_func=operator.attrgetter('id'),
        # adj_enum_func=lambda tag: tag.parent_tags(),
        adj_enum_func=None,
        adj_batch_enum_func=adj_batch_enum_func,
        exclude_init=exclude_init,
    )


def dfs_child_relation_closure(initial_tag_ids, fetch_related_tags=False):
    qs = TagRelation.objects.all()
    if fetch_related_tags:
        qs = qs.select_related('parent', 'child')

    return dfs_closure(
        initial_set=list(qs.filter(parent_id__in=initial_tag_ids)),
        key_func=lambda rel: (rel.parent_id, rel.child_id),
        adj_enum_func=None,
        adj_batch_enum_func=lambda rel_list: qs.filter(
            parent_id__in=[
                rel.child_id
                for rel in rel_list
            ]
        ),
        exclude_init=False,
    )


def dfs_parent_relation_closure(initial_tag_ids, fetch_related_tags=False):
    qs = TagRelation.objects.all()
    if fetch_related_tags:
        qs = qs.select_related('parent', 'child')

    return dfs_closure(
        initial_set=list(qs.filter(child_id__in=initial_tag_ids)),
        key_func=lambda rel: (rel.parent_id, rel.child_id),
        adj_enum_func=None,
        adj_batch_enum_func=lambda rel_list: qs.filter(
            child_id__in=[
                rel.parent_id
                for rel in rel_list
            ]
        ),
        exclude_init=False,
    )


"""
def get_all_ancestors(tag, tag_type=None):
    ancestors = []
    parents = tag.parents(tag_type)
    if parents is not None:
        ancestors.extend(parents)
        for parent in parents:
            ancestors.extend(get_all_ancestors(parent, tag_type))
    return ancestors
"""

"""
def get_all_successors(tag, tag_type=None):
    successors = []
    children = tag.children(tag_type)
    if children is not None:
        successors.extend(children)
        for child in children:
            successors.extend(get_all_successors(child, tag_type))
    return successors
"""


class TagQuerySet(models.QuerySet):

    def delete(self):
        raise Exception("Tag is not deletable.")

    def set_online(self, is_online=True):
        return self.update(is_online=is_online)


class TagManager(models.Manager.from_queryset(TagQuerySet)):
    pass


class Tag(models.Model):

    class Meta:
        verbose_name = "标签"
        verbose_name_plural = "标签"
        app_label = 'api'

    objects = TagManager()

    name = models.CharField(
        verbose_name='名称',
        max_length=64,
        null=False,
        blank=False,
        db_index=True,
        unique=True,
    )
    tag_type = models.CharField(
        verbose_name='标签类型',
        max_length=4,
        null=False,
        blank=False,
        choices=TAG_TYPE,
    )
    is_online = models.BooleanField(
        null=False, default=True, verbose_name='是否上线')

    recommend_type = models.CharField(
        max_length=1, choices=RECOMMEND_TYPE, verbose_name='推荐类型',
        default=RECOMMEND_TYPE.NOT_RECOMMEND,
    )
    banner_url = ImgUrlField(img_type=IMG_TYPE.TAG,
                             max_length=120, verbose_name='banner', blank=True, default='')
    show_diary = models.BooleanField(verbose_name='显示日记本', default=False)
    show_wiki = models.BooleanField(verbose_name='显示介绍/百科', default=False)
    show_service = models.BooleanField(verbose_name='显示美购', default=False)
    show_similar = models.BooleanField(verbose_name='显示同类话题组', default=False)
    is_display = models.BooleanField(verbose_name='是否显示该数据', default=False)
    ordering = models.IntegerField(
        default=99999, verbose_name="展示顺序", help_text="小的排在前，大的排在后")  # TODO: rename to rank?
    description = models.TextField(
        max_length=1024, verbose_name='', blank=True, default='')
    icon_url = ImgUrlField(
        img_type=IMG_TYPE.TAG,
        max_length=120, verbose_name='icon url', blank=True, default='')
    free_to_add = models.BooleanField(verbose_name='用户可自由添加', default=True)
    recommended_to_follow = models.BooleanField(
        verbose_name='是否推荐用户关注', default=False)
    managers = models.ManyToManyField(
        'Person', related_name="tags")  # api_tag_managers diary_tags
    re_purchase_days = models.IntegerField(verbose_name=u'复购天数', default=0)

    resembles = models.ManyToManyField('self', verbose_name=u'近义关系')
    is_multiple = models.BooleanField(verbose_name='是否多选', default=False)
    is_std = models.BooleanField(verbose_name='是否标准化', default=False)

    is_move = models.BooleanField(verbose_name='是否可移动', default=False)

    def __unicode__(self):
        return "{:d}:{:s}/{:s}{}".format(
            self.id,
            self.name,
            TAG_TYPE.getDesc(self.tag_type),
            '' if self.is_online else ' (offline)',
        )

    def set_online(self, is_online=True):
        self.is_online = is_online

    @property
    def children_relations(self):
        return TagRelation.objects.filter(parent=self, is_deleted=False)

    @property
    def parents_relations(self):
        return TagRelation.objects.filter(child=self, is_deleted=False)

    def children_count(self):
        return self.children_relations.count()

    def parents_count(self):
        return self.parents_relations.count()

    def child_tags(self):
        children_relations = self.children_relations.select_related('child')
        children = [r.child for r in children_relations]
        return children

    def online_child_tags(self):
        children_relations = self.children_relations.select_related('child')
        children_relations = children_relations.filter(child__is_online=True)
        children = [r.child for r in children_relations]
        return children

    def child_tags_with_condition_is_online(self, is_online, ordering=False):
        if is_online:
            tags_list = self.online_child_tags()
        else:
            tags_list = self.child_tags()
        if not ordering:
            return tags_list

        tags_list_info = [
            {
                'ordering': tag.ordering,
                'id': tag.id,
                'obj': tag,
            }
            for tag in tags_list if tag.tag_type in (
                    TAG_TYPE.BODY_PART_SUB_ITEM,
                    TAG_TYPE.BODY_PART,
                    TAG_TYPE.ITEM_WIKI
            )
        ]
        tags_list_info = sorted(
            tags_list_info,
            key=lambda k: (k['ordering'], k['id'])
        )
        return [tag['obj'] for tag in tags_list_info]

    def parent_tags(self):
        parents_relations = self.parents_relations.select_related('parent')
        parents = [r.parent for r in parents_relations]
        return parents

    def online_parent_tags(self):
        parents_relations = self.parents_relations.select_related('parent')
        parents_relations = parents_relations.filter(parent__is_online=True)
        parents = [r.parent for r in parents_relations]
        return parents

    def _child_tags_closure_dict(self, exclude_self):
        return dfs_child_closure(initial_set=[self], exclude_init=exclude_self)

    def _parent_tags_closure_dict(self, exclude_self):
        return dfs_parent_closure(initial_set=[self], exclude_init=exclude_self)

    def _child_tag_closure(self, exclude_self):
        return self._child_tags_closure_dict(exclude_self=exclude_self).values()

    def _parent_tags_closure(self, exclude_self):
        return self._parent_tags_closure_dict(exclude_self=exclude_self).values()

    def is_ancestor_of(self, other, exclude_self):
        assert isinstance(other, Tag)
        ancestor_dict = other._parent_tags_closure_dict(
            exclude_self=exclude_self)
        return self.id in ancestor_dict

    def is_descendant_of(self, other, exclude_self):
        assert isinstance(other, Tag)
        return other.is_ancestor_of(other=self, exclude_self=exclude_self)

    @property
    def related_tags(self):
        _related_tags = [self.id]
        [_related_tags.append(tag.id) for tag in self.child_tags()]
        return _related_tags

    def get_tag_info_with_zone(self, zone):
        if not zone.is_online:
            return self.get_tag_info_no_zone()

        return {
            'id': self.id,
            'name': self.name,
            'banner_url': zone.campaign_banner or self.banner_url,  # 活动圈子banner图
            'icon_url': zone.icon_image or self.icon_url,
            'show_wiki': self.show_wiki,
            'show_diary': self.show_diary,
            'show_service': self.show_service,
            'description': zone.campaign_descr or self.description or zone.campaign_descr_richtext,  # 活动圈子
            'is_online': self.is_online,
            'free_to_add': self.free_to_add,
            'related_zone': zone.get_related_tag(),
            'is_operation_tag': True,
            'is_activity_zone': False if self.tag_type in [
                TAG_TYPE.BODY_PART,
                TAG_TYPE.BODY_PART_SUB_ITEM,
                TAG_TYPE.ITEM_WIKI
            ] else True,
            'activity_zone_header': zone.title or zone.sentence_descr,  # 活动圈子  图片和 topics中间的那段文字
            'show_wiki_diary_service': zone.is_openrelated,  # 是否打开项目百科,相关日记,相关美购
            'zone_descript': zone.sentence_descr,  # 圈子一句话描述
            'topup': zone.get_overhead_topic(),  # 项目圈子置顶贴
            'zone_img': zone.icon_image,  # 圈子 下拉框  里面的图片（ 一句话描述旁边的那个）,
            'wiki_id': self.get_wiki_by_tag(),
            'has_zone': True,
            'campaign_descr_richtext': zone.campaign_descr_richtext,
        }

    def get_wiki_by_tag(self):
        _Item_wiki = api.models.ItemWiki
        try:
            wiki = _Item_wiki.objects.get(tag=self)
        except _Item_wiki.DoesNotExist, _Item_wiki.MultipleObjectsReturned:
            return None
        return wiki.id

    def get_tag_info_no_zone(self):
        return {
            'id': self.id,
            'name': self.name,
            'banner_url': self.banner_url,
            'icon_url': self.icon_url,
            'show_wiki': self.show_wiki,
            'show_diary': self.show_diary,
            'show_service': self.show_service,
            'description': self.description,
            'is_online': self.is_online,
            'free_to_add': self.free_to_add,
            'is_operation_tag': False,
            'has_zone': False,
            'tag_type': self.tag_type,
        }

    def get_tag_info(self):
        """get tag's detail information such as, banner, description, etc."""
        return self.get_tag_info_no_zone()

    def get_tag_infoV2(self):
        """get tag's detail information such as, banner, description, etc.
           6.0 新版
        """
        if hasattr(self,'zone'):
            return self.get_tag_info_with_zone(self.zone)
        else:
            return self.get_tag_info_no_zone()

    def to_dict(self):
        data = to_dict(
            self, fields=['name', 'tag_type', 'is_online', 'ordering', 'id'])
        return data

    def resemble_add(self, *objs):
        self.resembles.add(*objs)

    def resemble_del(self, *objs):
        self.resembles.remove(*objs)

    def resemble_tags(self):
        return list(self.resembles.all())

    def resemble_save(self, objs):
        new_tags = set(objs)
        old_tags = set(self.resemble_tags())
        add_tags = new_tags - old_tags
        del_tags = old_tags - new_tags
        self.resemble_add(*add_tags)
        self.resemble_del(*del_tags)


class TagRelationQuerySet(models.QuerySet):

    def bulk_create(self, objs, batch_size=None):
        raise Exception("TagRelationQuerySet.bulk_create is forbidden to use.")

    def update_or_create(self, defaults=None, **kwargs):
        raise Exception(
            "TagRelationQuerySet.update_or_create is forbidden to use.")

    def update(self, **kwargs):
        raise Exception("TagRelationQuerySet.update is forbidden to use.")

    def delete(self):
        raise Exception(
            "TagRelationQuerySet.delete is forbidden to use. Use TagRelationQuerySet.soft_delete instead.")

    def soft_delete(self):
        return super(TagRelationQuerySet, self).update(is_deleted=True)


class TagRelationManager(models.Manager.from_queryset(TagRelationQuerySet)):

    def get_queryset(self):
        return super(TagRelationManager, self).get_queryset().filter(is_deleted=False)


class TagRelation(models.Model):

    class Meta:
        verbose_name = "标签关系"
        verbose_name_plural = "标签关系"
        unique_together = [
            ('parent', 'child'),
        ]
        app_label = 'api'

    objects = TagRelationManager()
    manager = models.Manager()

    parent = models.ForeignKey(
        Tag, null=False, related_name='all_children_relations')
    child = models.ForeignKey(
        Tag, null=False, related_name='all_parents_relations')
    is_deleted = models.BooleanField(null=False, default=False)

    def __unicode__(self):
        return "{:s} -> {:s}{:s}".format(
            unicode(self.parent),
            unicode(self.child),
            '' if not self.is_deleted else ' (deleted)',
        )

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        assert isinstance(self.parent, Tag)
        assert isinstance(self.child, Tag)

        with transaction.atomic(using=using):

            if not self.is_deleted:
                parent = self.parent
                child = self.child

                if parent.id is None:
                    raise ValueError("parent not saved")
                if child.id is None:
                    raise ValueError("child not saved")

                if child.is_ancestor_of(parent, exclude_self=False):
                    raise TagRelationCycleError(
                        "cycle detected, parent is {}, child is {}.".format(parent, child))

                self.parent = parent
                self.child = child

            super(TagRelation, self).save(
                force_insert=force_insert,
                force_update=force_update,
                using=using,
                update_fields=update_fields,
            )

    save.alters_data = True

    def delete(self, using=None):
        raise Exception(
            "TagRelation.delete is forbidden to use. Use TagRelation.soft_delete instead.")

    def soft_delete(self, using=None):
        self.is_deleted = True
        self.save(using=using)

    soft_delete.alters_data = True

    @classmethod
    def add_relation(cls, parent, child):
        rel, created = cls.objects.get_or_create(parent=parent, child=child)
        if rel.is_deleted:
            rel.is_deleted = False
            rel.save()
        return rel

    @classmethod
    def del_relation(cls, parent, child):
        cls.objects.filter(parent=parent, child=child).soft_delete()

    @classmethod
    def add_relation_by_id(cls, parent_id, child_id):
        rel, _ = cls.manager.update_or_create(
            parent_id=parent_id, child_id=child_id, defaults={'is_deleted': 0})
        return rel

    @classmethod
    def del_relation_by_id(cls, parent_id, child_id):
        cls.objects.filter(parent_id=parent_id,
                           child_id=child_id).soft_delete()

    @classmethod
    def get_top_tags(cls, tag_ids):
        if not tag_ids:
            return {}

        tags_relation = cls.objects.filter(child__id__in=tag_ids, is_deleted=False).select_related("parent")
        first = [x.parent for x in tags_relation if x.parent.tag_type == TAG_TYPE.BODY_PART]
        second = [x for x in tags_relation if x.parent.tag_type == TAG_TYPE.BODY_PART_SUB_ITEM]
        tags_relation = cls.objects.filter(child__id__in=[i.parent.id for i in second], is_deleted=False)
        #第二次取得一级标签（）
        second_first = [x.parent for x in tags_relation if x.parent.tag_type == TAG_TYPE.BODY_PART]

        return {
            'first': first,
            'second': second_first,
        }

    """
    @classmethod
    def add_tagrelation(cls, parent_id, child_id):
        result = {'error': -1}
        try:
            cls.objects.get(parent_id=parent_id, child_id=child_id)
            result['msg'] = 'relation already exist'
        except cls.DoesNotExist:
            relation = cls(parent_id=parent_id, child_id=child_id)
            relation.save()
            result['error'] = 0
            result['msg'] = 'success'
        return result
    """

    """
    @classmethod
    def delete_all_tagrelations(cls):
        result = {'error': -1}
        try:
            cls.objects.all().delete()
            result['error'] = 0
            result['msg'] = 'success'
        except cls.DoesNotExist:
            result['msg'] = 'relation does not exist'

        return result
    """

    """
    @classmethod
    def delete_tagrelation(cls, ids):
        result = {'error': -1}
        try:
            cls.objects.filter(id__in=ids).delete()
            result['error'] = 0
            result['msg'] = 'success'
        except cls.DoesNotExist:
            result['msg'] = 'relation does not exist'
        return result
    """

    """
    @classmethod
    def update_tagrelation(cls, id, parent_id, child_id):
        result = {'error': -1}
        try:
            relation = cls.objects.filter(id=id).update(parent_id=parent_id, child_id=child_id)
            result['error'] = 0
            result['msg'] = 'success'
        except cls.DoesNotExist:
            result['msg'] = 'relation does not exist'
        return result
    """

    @classmethod
    def get_page_tagrelations_info(cls, page, each_page_amount, query):
        """
        deprecated
        """
        if query:
            tag_ids = list(Tag.objects.filter(
                name__contains=query).values_list('id', flat=True))
            tagrelations_amount = cls.objects.filter(
                Q(parent_id__in=tag_ids) | Q(child_id__in=tag_ids)).count()
        else:
            tagrelations_amount = cls.objects.all().count()

        f = lambda a, b: a / b + (0 if a % b == 0 else 1)
        max_page_number = f(tagrelations_amount, each_page_amount)

        page = 1 if page < 1 else page
        page = max_page_number if page > max_page_number else page
        has_prev = page > 1
        hsa_next = page < max_page_number

        return {
            "current": page,
            "has_prev": has_prev,
            "has_next": hsa_next,
            "max": max_page_number,
            "range": [p + 1 for p in range(max_page_number)],
        }

    @classmethod
    def get_page_tagrelations(cls, page, each_page_amount, query):
        """
        deprecated
        """
        tagrelations = []
        offset = (page - 1) * each_page_amount
        limit = offset + each_page_amount

        if query:
            tag_ids = list(Tag.objects.filter(
                name__contains=query).values_list('id', flat=True))
            objects = cls.objects.filter(Q(parent_id__in=tag_ids) | Q(
                child_id__in=tag_ids))[offset:limit]
        else:
            objects = cls.objects.all()[offset:limit]

        for object in objects:
            parent = object.parent
            child = object.child
            relation = {
                'id': object.id,
                'parenttag': {
                    'id': parent.id,
                    'name': parent.name,
                    'type': TAG_TYPE.getDesc(parent.tag_type),
                    'type_id': parent.tag_type,
                },
                'childtag': {
                    'id': child.id,
                    'name': child.name,
                    'type': TAG_TYPE.getDesc(child.tag_type),
                    'type_id': child.tag_type,
                },
            }
            tagrelations.append(relation)

        return tagrelations


class TagAlert(models.Model):

    class Meta:
        verbose_name = "标签提醒"
        verbose_name_plural = "标签提醒"
        app_label = 'api'

    tag = models.ForeignKey(Tag, verbose_name=u'关联的标签')
    is_online = models.BooleanField(verbose_name=u'是否上线', default=False)
    type = models.IntegerField(verbose_name=u'提醒的类型', choices=TAG_ALERT_TYPE)
    title = models.CharField(verbose_name='类型标题', max_length=256)
    service_tag = models.ForeignKey(
        Tag, verbose_name='推荐美购', related_name='service_tag')
    days = models.IntegerField(verbose_name=u'天数', default=1)
    create_time = models.DateTimeField(
        verbose_name=u'起始时间', default=timezone.now)

    def get_alert_detail(self):
        data = {'title': self.title, 'tagalertitems': []}
        for tai in self.tagalertitem_set.all():
            data['tagalertitems'].append(tai.to_dict())
        return data


class TagAlertItem(models.Model):

    class Meta:
        verbose_name = "标签提醒item"
        verbose_name_plural = "标签提醒item"
        app_label = 'api'

    tagalert = models.ForeignKey(TagAlert, verbose_name='关联的标签提醒')
    title = models.CharField(verbose_name='标题', max_length=256)
    image_url = ImgUrlField(img_type=IMG_TYPE.TAG, verbose_name='图片', max_length=512)
    content = models.TextField(verbose_name=u'提醒内容')

    def to_dict(self):
        data = to_dict(self, fields=['title', 'image_url', 'content'])
        return data


class OldTagCategory(models.Model):

    class Meta:
        verbose_name = u'老标签大类'
        db_table = 'api_old_tag_category'

    name = models.CharField(verbose_name=u'大类名称', max_length=128, db_index=True, unique=True)


class TagCategoryRelation(models.Model):

    class Meta:
        verbose_name = u'老标签大类关联'
        db_table = 'api_tag_category_relation'

    tag = models.ForeignKey(Tag, verbose_name=u'标签ID')
    tag_category = models.ForeignKey(OldTagCategory, verbose_name=u"标签大类id")
