# -*- coding: UTF-8 -*-
import datetime
from collections import defaultdict

from django.db.models import Q
from django.db import IntegrityError, transaction
from django.conf import settings
from gm_types.gaia import TAG_ALERT_TYPE
from gm_types.error import ERROR

from hera.queries.tagattrs import TagAttrsDQ
from rpc.decorators import bind_context
from rpc.exceptions import RPCIntegrityError, RPCNotFoundException, GaiaRPCFaultException
from rpc.tool.dict_mixin import to_dict
from rpc.tool.log_tool import info_logger
from api.models import Tag, TAG_TYPE, BodyPartSubItem, TagRelation, Person, TagAttr, AttrOptions
from api.models.tag import TagAlert, TagAlertItem, OldTagCategory, TagCategoryRelation
from wiki.models import ItemTag, CollectTag
from hera.datatables import TagDT, TagAlertDT
from hera.helpers import check_tags_type_for_relate
from rpc.tool.datatables import DataTable
from api.models.itemwiki import TagWiki
from ..queries.tag import TagDQ, TagAlertDQ, TagAlertItemDQ
from agile.models.tag import TagV3 as Tag_3_0
from agile.models.tag import TagMapOldTag
from rpc.context import get_rpc_remote_invoker
from rpc.tool.log_tool import logging_exception


uri_pre = 'hera/tag'


@bind_context(uri_pre + '/get')
def tag_detail(ctx, tag_id, options=None):
    try:
        tag = Tag.objects.get(id=tag_id)
    except:
        raise RPCNotFoundException
    if options is None:
        options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    tag_data = to_dict(tag, **options)
    tag_data['description'] = tag.description
    tag_data['parent'] = [parent_tag.id for parent_tag in tag.parent_tags()]
    tag_data['child'] = [child_tag.id for child_tag in tag.child_tags()]
    tag_data['new_tags'] = [item.tag_id for item in TagMapOldTag.objects.filter(old_tag_id=tag_id)]
    tag_data['managers'] = [p.user.id for p in tag.managers.all()]
    item_wiki = TagWiki.objects.filter(tag_id=tag.id)
    tag_data['item_wiki'] = item_wiki[0].itemwiki_id if item_wiki else None
    tag_data['resembles'] = [t.id for t in tag.resemble_tags()]
    tag_data['polymer_id'] = getattr(tag.polymer.first(), 'id', '')
    new_item_wiki = ItemTag.objects.filter(tag_id=tag.id)
    tag_data['new_item_wiki'] = new_item_wiki[0].item_id if new_item_wiki else None
    new_collect_wiki = CollectTag.objects.filter(tag_id=tag.id)
    tag_data['new_collect_wiki'] = new_collect_wiki[0].collect_id if new_collect_wiki else None
    return tag_data


@bind_context(uri_pre + '/edit')
@transaction.atomic
def tag_edit(ctx, tag_id=None, tag_info=None):
    if tag_info is None:
        return None
    parent = tag_info.pop('parent')
    child = tag_info.pop('child')
    new_tags = tag_info.pop('new_tags')
    managers = tag_info.pop('managers', [])
    item_wiki = tag_info.pop('item_wiki')
    polymer_id = tag_info.pop('polymer_id')
    resemble_ids = tag_info.pop('resembles', [])
    new_item_wiki = tag_info.pop('new_item_wiki')
    new_collect_wiki = tag_info.pop('new_collect_wiki')
    if new_collect_wiki and new_item_wiki:
        raise GaiaRPCFaultException(error=ERROR.UNIVERSAL,
                                    message='只能关联一个新百科或者新百科聚合', data=None)
    if tag_id is None:
        try:
            tag = Tag.objects.create(**tag_info)
            tag_id = tag.id
        except IntegrityError:
            raise RPCIntegrityError
    else:
        try:
            tag_count = Tag.objects.filter(name=tag_info['name']).exclude(id=tag_id).count()
            if tag_count > 0:
                raise IntegrityError
            tag = Tag.objects.get(id=tag_id)
        except IntegrityError:
            raise RPCIntegrityError
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise RPCNotFoundException
        for k, v in tag_info.iteritems():
            setattr(tag, k, v)
        tag.save()

    TagRelation.objects.filter(child_id=tag_id).exclude(parent_id__in=parent).soft_delete()
    for parent_id in parent:
        parent = Tag.objects.get(id=parent_id)
        check_tags_type_for_relate(parent, tag)
        TagRelation.add_relation_by_id(parent_id, tag_id)

    TagRelation.objects.filter(parent_id=tag_id).exclude(child_id__in=child).soft_delete()
    for child_id in child:
        child = Tag.objects.get(id=child_id)
        check_tags_type_for_relate(tag, child)
        TagRelation.add_relation_by_id(tag_id, child_id)

    old_tags = list(TagMapOldTag.objects.filter(old_tag_id=tag_id).values_list("tag_id", flat=True))
    new_tags = list(map(int, new_tags))
    TagMapOldTag.objects.filter(old_tag_id=tag_id).exclude(tag_id__in=new_tags).delete()
    for new_tag_id in new_tags:
        TagMapOldTag.add_relation_by_id(tag_id, new_tag_id)

    # 标签调整，同步对应的内容上的标签
    try:
        delete_tag_ids = list(set(old_tags) - set(new_tags))
        delete_relations = [
            {"tag_id": tag.id, "tag_v3_id": tag_v3_id}
            for tag_v3_id in delete_tag_ids
        ]
        delete2old_tag_ids = defaultdict(list)
        if delete_tag_ids:
            for item in TagMapOldTag.objects.filter(tag_id__in=delete_tag_ids):
                if item.old_tag_id == tag.id:
                    continue
                delete2old_tag_ids[item.tag_id].append(item.old_tag_id)

        add_relations = [
            {"tag_id": tag.id, "tag_v3_id": tag_v3_id}
            for tag_v3_id in (set(new_tags) - set(old_tags))
        ]
        if delete_relations or add_relations:
            get_rpc_remote_invoker()["mimas/tag_mapping/sync"](
                add_relations=add_relations,
                delete_relations=delete_relations,
                delete2old_tag_ids=delete2old_tag_ids,
            ).unwrap()
    except:
        logging_exception()

    new_managers = set(Person.objects.filter(user_id__in=managers))
    old_managers = set(tag.managers.all())
    tag.managers.add(*(new_managers - old_managers))
    tag.managers.remove(*(old_managers - new_managers))
    tag.save()

    tag.polymer = [polymer_id]

    # 处理近义关系
    rsb_tags = Tag.objects.filter(id__in=resemble_ids)
    tag.resemble_save(rsb_tags)

    # 每个tag只能归属于一个百科或者百科聚合
    ItemTag.objects.filter(tag_id=tag.id).delete()
    CollectTag.objects.filter(tag_id=tag.id).delete()

    if new_item_wiki:
        ItemTag.objects.create(item_id=new_item_wiki, tag_id=tag.id)
    if new_collect_wiki:
        CollectTag.objects.create(collect_id=new_collect_wiki, tag_id=tag.id)

    try:
        tagwiki = TagWiki.objects.get(tag_id=tag.id)
        tagwiki.itemwiki_id = item_wiki
        tagwiki.save()
    except:
        tagwiki = TagWiki.objects.create(tag_id=tag.id, itemwiki_id=item_wiki)
    return tag.id


@bind_context(uri_pre + '/query')
def tag_query(ctx, options):
    dqobj = TagDQ()
    return dqobj.process(**options)


@bind_context(uri_pre + '/list')
def tag_info(ctx, req_data):
    dtobj = TagDT(Tag)
    return dtobj.process(req_data)


@bind_context(uri_pre + '/listupdate')
def tag_listupdate(ctx, items):
    info = []
    for obj in items:
        tag = Tag.objects.get(pk=obj['id'])
        tag.is_online = obj['is_online']
        tag.save()
        info.append(obj['id'])
    return info


@bind_context(uri_pre + '/choices')
def tag_choices(ctx, q='', page=1, num=30, initial=None, tag_type=None, parent_tag=None):
    page = int(page)
    num = int(num)

    if initial is not None:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    else:
        qry = Q(id__contains=q) | Q(name__contains=q)
    if tag_type is not None:
        if isinstance(tag_type, (list, tuple)):
            qry &= Q(tag_type__in=tag_type)
        else:
            qry &= Q(tag_type=tag_type)
    if parent_tag:
        # 给定二级标签, 过滤出三级标签
        qry &= Q(id__in=[
            tag.id for tag in Tag.objects.get(id=parent_tag).online_child_tags()
        ])
    qry &= Q(is_online=True)
    query = Tag.objects.using(settings.SLAVE_DB_NAME).filter(qry).order_by('-id')
    total_count = query.count()
    start_pos = (page - 1) * num
    start_pos = start_pos if start_pos >= 0 else 0
    results = [
        {
            'id': obj.id,
            'text': u'{}:{}/{}'.format(
                obj.id, obj.name, TAG_TYPE.getDesc(obj.tag_type)),
        } for obj in query[start_pos: start_pos + num]
        ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('hera/tag_3_0/choices')
def tag_3_0_choices(ctx, q='', page=1, num=30, initial=None, tag_type=None, parent_tag=None):
    page = int(page)
    num = int(num)

    if initial is not None:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    else:
        qry = Q(id__contains=q) | Q(name__contains=q)

    qry &= Q(is_online=True)
    if tag_type:
        qry &= Q(tag_type=int(tag_type))
    query = Tag_3_0.objects.using(settings.SLAVE_DB_NAME).filter(qry).order_by('-id')
    total_count = query.count()
    start_pos = (page - 1) * num
    start_pos = start_pos if start_pos >= 0 else 0

    results = [
        {
            'id': obj.id,
            'text': u'{}:{}'.format(obj.id, obj.name),
        } for obj in query[start_pos: start_pos + num]
    ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('hera/bodypartsubitem/choices')
def bodypartsubitem_choices(ctx, q='', page=1, num=30, initial=None):
    page = int(page)
    num = int(num)

    if initial is not None:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    else:
        qry = Q(id__contains=q) | Q(name__contains=q)
    qry &= Q(is_deleted=False)
    query = BodyPartSubItem.objects.using(settings.SLAVE_DB_NAME).filter(qry).order_by('-id')
    total_count = query.count()
    start_pos = (page - 1) * num
    start_pos = start_pos if start_pos >= 0 else 0
    results = [
        {
            'id': obj.id,
            'text': u'{}:{}({})'.format(obj.id, obj.name, obj.body.name if obj.body else ''),
        } for obj in query[start_pos: start_pos + num]
        ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context(uri_pre + '/alert_query')
def tagalert_query(ctx, options):
    dqobj = TagAlertDQ()
    return dqobj.process(**options)


@bind_context(uri_pre + '/alert_list')
def tag_alert_datatable(ctx, req_data):
    dtobj = TagAlertDT(TagAlert)
    return dtobj.process(req_data)


@bind_context(uri_pre + '/alert_get')
def tag_alert_detail(ctx, id, options=None):
    try:
        tag_alert = TagAlert.objects.get(id=id)
    except:
        raise RPCNotFoundException
    if options is None:
        options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    tag_alert_data = to_dict(tag_alert, **options)
    return tag_alert_data


@bind_context(uri_pre + '/alert_edit')
def tag_alert_edit(ctx, id=None, alert_info=None):
    if alert_info is None:
        return None

    alert_info['service_tag_id'] = alert_info.pop('service_tag')
    if id is None:
        # 术前提醒和术后提醒类型只能有一个
        alert_type = int(alert_info.get('type'))
        tag_id = alert_info.get('tag_id')
        if alert_type in [TAG_ALERT_TYPE.PRE_OPERATION, TAG_ALERT_TYPE.AFTER_OPERATION]:
            tag_alert = TagAlert.objects.filter(tag_id=tag_id, type=alert_type)
            if tag_alert:
                message = TAG_ALERT_TYPE.getDesc(alert_type) + u'只能有一个'
                raise GaiaRPCFaultException(error=1, message=message, data=None)
        try:
            tag_alert = TagAlert.objects.create(**alert_info)
        except IntegrityError:
            raise RPCIntegrityError
    else:
        try:
            tag_alert = TagAlert.objects.get(id=id)
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise RPCNotFoundException
        for k, v in alert_info.iteritems():
            setattr(tag_alert, k, v)
        tag_alert.save()

    return tag_alert.id


@bind_context(uri_pre + '/alertitem_query')
def alertitem_query(ctx, options):
    dqobj = TagAlertItemDQ()
    return dqobj.process(**options)


@bind_context(uri_pre + '/alertitem_list')
def tagalertitem_datatable(ctx, req_data):
    dtobj = DataTable(TagAlertItem)
    return dtobj.process(req_data)


@bind_context(uri_pre + '/alertitem_get')
def tagalertitem_detail(ctx, id, options=None):
    try:
        tagalertitem = TagAlertItem.objects.get(id=id)
    except:
        raise RPCNotFoundException
    if options is None:
        options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    tagalertitem_data = to_dict(tagalertitem, **options)
    return tagalertitem_data


@bind_context(uri_pre + '/alertitem_edit')
def tagalertitem_edit(ctx, id=None, alertitem_info=None):
    if alertitem_info is None:
        return None

    if id is None:
        try:
            tagalertitem = TagAlertItem.objects.create(**alertitem_info)
        except IntegrityError:
            raise RPCIntegrityError
    else:
        try:

            tagalertitem = TagAlertItem.objects.get(id=id)
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise RPCNotFoundException
        for k, v in alertitem_info.iteritems():
            setattr(tagalertitem, k, v)
        tagalertitem.save()

    return tagalertitem.id


@bind_context(uri_pre + '/get_from_ids')
def tag_get_from_ids(ctx, ids):
    tag_data = []
    tags = Tag.objects.filter(id__in=ids)
    for tag in tags:
        data = {
            'tag_id': tag.id,
            'tag_name': tag.name,
        }
        tag_data.append(data)

    return tag_data


@bind_context(uri_pre + '/tag_attrs_query')
def tag_attrs_query(ctx, options):
    init_q = Q(tag_type__in=('2', '3'))
    dqobj = TagAttrsDQ(init_q=init_q)
    return dqobj.process(**options)


@bind_context(uri_pre + '/get_attr_by_tagid')
def get_attr_bytagid(ctx, tag_id):
    tagattrs = TagAttr.objects.filter(tag_id=tag_id)
    result_data = []
    for tagattr in tagattrs:
        dict_res = {'name': tagattr.name, 'id': tagattr.id, 'is_online': tagattr.is_online, 'attr-item': []}
        attroptions = AttrOptions.objects.filter(tag_attr__id=tagattr.id)
        for attroption in attroptions:
            dict_attr = {'item-name': attroption.name, 'item-id': attroption.id, 'is_online': attroption.is_online}
            dict_res['attr-item'].append(dict_attr)
        result_data.append(dict_res)

    return result_data


@bind_context(uri_pre + '/edit_options')
def edit_options(ctx, name, attr_id=None, attr_father_id=None):
    """
    修改或新增小属性
    """
    if not name:
        return None
    if attr_id:
        attr = AttrOptions.objects.get(pk=attr_id)
        attr.name = name
        attr.save()
    else:
        attr = AttrOptions.objects.create(
            name=name,
            is_online=True,
            created_time=datetime.datetime.now(),
            tag_attr_id=int(attr_father_id)
        )
    return attr.id


@bind_context(uri_pre + '/set_option_online')
def set_option_online(ctx, attr_id, is_online):
    """
    设置小属性上下线
    """
    attr_option = AttrOptions.objects.get(pk=int(attr_id))
    attr_option.is_online = is_online
    attr_option.save()


@bind_context(uri_pre + '/edit_attr')
def edit_attr(ctx, name, tag_id, attr_id=None):
    """
    修改或新增大属性
    :param tag_id:
    """
    if not name:
        return None
    if attr_id:
        attr = TagAttr.objects.get(pk=attr_id)
        attr.name = name
        attr.save()
    else:
        attr = TagAttr.objects.create(
            name=name,
            is_online=True,
            created_time=datetime.datetime.now(),
            tag_id=tag_id
        )
    return attr.id


@bind_context(uri_pre + '/set_attr_online')
@transaction.atomic
def set_attr_online(ctx, attr_id, is_online):
    """
    设置大属性上下线
    """
    attr_option = TagAttr.objects.get(pk=int(attr_id))
    attr_option.is_online = is_online
    attr_option.save()
    AttrOptions.objects.filter(tag_attr__id=attr_id).update(is_online=False)


@bind_context(uri_pre+'/category/get')
def category_get_by_id(ctx, category_id):
    result = {}
    if not category_id:
        return result

    try:
        category = OldTagCategory.objects.get(id=category_id)
    except OldTagCategory.DoesNotExist:
        return result
    related_tags = list(TagCategoryRelation.objects.filter(
        tag_category_id=category_id
    ).values_list('tag_id', flat=True))

    return {
        'id': category.id,
        'name': category.name,
        'related_tags': related_tags,
    }


@bind_context(uri_pre+'/category/edit')
def category_edit(ctx, category_id, category_data):
    result = {
        'error': 0,
        'message': '创建成功',
        'data': ''
    }
    category_info = {
        'name': category_data.get('name')
    }
    if not category_id:
        try:
            category = OldTagCategory.objects.create(**category_info)
        except IntegrityError:
            raise RPCIntegrityError(message='名称不能重复')
    else:
        category = OldTagCategory.objects.get(id=category_id)
        try:
            for key, value in category_info.items():
                setattr(category, key, value)
            category.save()
        except IntegrityError:
            result['error'] = -1
            result['message'] = '名称不能重复'
            result['data'] = category.id
            return result
    # 关联标签
    related_tags = list(TagCategoryRelation.objects.filter(
        tag_category_id=category.id,
    ).values_list('tag_id', flat=True))
    tag_ids = category_data.get('related_tags', [])
    tag_ids = map(int, tag_ids)
    create_ids = set(tag_ids) - set(related_tags)
    del_ids = set(related_tags) - set(tag_ids)
    related_objs = [
        TagCategoryRelation(
            tag_id=_id,
            tag_category_id=category.id,
        ) for _id in create_ids
    ]
    TagCategoryRelation.objects.filter(tag_id__in=del_ids).delete()
    TagCategoryRelation.objects.bulk_create(related_objs)
    result['data'] = category.id

    return result
