# -*- coding:utf-8 -*-

from __future__ import unicode_literals

from flask import current_app

from api.extensions import cache
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.models.cmdb import Attribute
from api.models.cmdb import CIType
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import RelationType


class AttributeCache(object):
    PREFIX_ID = 'Field::ID::{0}'
    PREFIX_NAME = 'Field::Name::{0}'
    PREFIX_ALIAS = 'Field::Alias::{0}'

    @classmethod
    def get(cls, key):
        if key is None:
            return
        attr = cache.get(cls.PREFIX_NAME.format(key))
        attr = attr or cache.get(cls.PREFIX_ID.format(key))
        attr = attr or cache.get(cls.PREFIX_ALIAS.format(key))

        if attr is None:
            attr = Attribute.get_by(name=key, first=True, to_dict=False)
            attr = attr or Attribute.get_by_id(key)
            attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
            if attr is not None:
                cls.set(attr)

        return attr

    @classmethod
    def set(cls, attr):
        cache.set(cls.PREFIX_ID.format(attr.id), attr)
        cache.set(cls.PREFIX_NAME.format(attr.name), attr)
        cache.set(cls.PREFIX_ALIAS.format(attr.alias), attr)

    @classmethod
    def clean(cls, attr):
        cache.delete(cls.PREFIX_ID.format(attr.id))
        cache.delete(cls.PREFIX_NAME.format(attr.name))
        cache.delete(cls.PREFIX_ALIAS.format(attr.alias))


class CITypeCache(object):
    PREFIX_ID = "CIType::ID::{0}"
    PREFIX_NAME = "CIType::Name::{0}"
    PREFIX_ALIAS = "CIType::Alias::{0}"

    @classmethod
    def get(cls, key):
        if key is None:
            return
        ct = cache.get(cls.PREFIX_NAME.format(key))
        ct = ct or cache.get(cls.PREFIX_ID.format(key))
        ct = ct or cache.get(cls.PREFIX_ALIAS.format(key))
        if ct is None:
            ct = CIType.get_by(name=key, first=True, to_dict=False)
            ct = ct or CIType.get_by_id(key)
            ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
            if ct is not None:
                cls.set(ct)

        return ct

    @classmethod
    def set(cls, ct):
        cache.set(cls.PREFIX_NAME.format(ct.name), ct)
        cache.set(cls.PREFIX_ID.format(ct.id), ct)
        cache.set(cls.PREFIX_ALIAS.format(ct.alias), ct)

    @classmethod
    def clean(cls, key):
        ct = cls.get(key)
        if ct is not None:
            cache.delete(cls.PREFIX_NAME.format(ct.name))
            cache.delete(cls.PREFIX_ID.format(ct.id))
            cache.delete(cls.PREFIX_ALIAS.format(ct.alias))


class RelationTypeCache(object):
    PREFIX_ID = "RelationType::ID::{0}"
    PREFIX_NAME = "RelationType::Name::{0}"

    @classmethod
    def get(cls, key):
        if key is None:
            return
        ct = cache.get(cls.PREFIX_NAME.format(key))
        ct = ct or cache.get(cls.PREFIX_ID.format(key))
        if ct is None:
            ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
            if ct is not None:
                cls.set(ct)

        return ct

    @classmethod
    def set(cls, ct):
        cache.set(cls.PREFIX_NAME.format(ct.name), ct)
        cache.set(cls.PREFIX_ID.format(ct.id), ct)

    @classmethod
    def clean(cls, key):
        ct = cls.get(key)
        if ct is not None:
            cache.delete(cls.PREFIX_NAME.format(ct.name))
            cache.delete(cls.PREFIX_ID.format(ct.id))


class CITypeAttributesCache(object):
    """
    key is type_id or type_name
    """

    PREFIX_ID = "CITypeAttributes::TypeID::{0}"
    PREFIX_NAME = "CITypeAttributes::TypeName::{0}"

    PREFIX_ID2 = "CITypeAttributes2::TypeID::{0}"
    PREFIX_NAME2 = "CITypeAttributes2::TypeName::{0}"

    @classmethod
    def get(cls, key):
        if key is None:
            return

        attrs = cache.get(cls.PREFIX_NAME.format(key))
        attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
        if not attrs:
            attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)

            if not attrs:
                ci_type = CIType.get_by(name=key, first=True, to_dict=False)
                if ci_type is not None:
                    attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)

            if attrs is not None:
                cls.set(key, attrs)

        return attrs

    @classmethod
    def get2(cls, key):
        """
        return [(type_attr, attr), ]
        :param key:
        :return:
        """
        if key is None:
            return

        attrs = cache.get(cls.PREFIX_NAME2.format(key))
        attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
        if not attrs:
            attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)

            if not attrs:
                ci_type = CIType.get_by(name=key, first=True, to_dict=False)
                if ci_type is not None:
                    attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)

            if attrs is not None:
                attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
                cls.set2(key, attrs)

        return attrs

    @classmethod
    def set(cls, key, values):
        ci_type = CITypeCache.get(key)
        if ci_type is not None:
            cache.set(cls.PREFIX_ID.format(ci_type.id), values)
            cache.set(cls.PREFIX_NAME.format(ci_type.name), values)

    @classmethod
    def set2(cls, key, values):
        ci_type = CITypeCache.get(key)
        if ci_type is not None:
            cache.set(cls.PREFIX_ID2.format(ci_type.id), values)
            cache.set(cls.PREFIX_NAME2.format(ci_type.name), values)

    @classmethod
    def clean(cls, key):
        ci_type = CITypeCache.get(key)
        attrs = cls.get(key)
        if attrs is not None and ci_type:
            cache.delete(cls.PREFIX_ID.format(ci_type.id))
            cache.delete(cls.PREFIX_NAME.format(ci_type.name))

        attrs2 = cls.get2(key)
        if attrs2 is not None and ci_type:
            cache.delete(cls.PREFIX_ID2.format(ci_type.id))
            cache.delete(cls.PREFIX_NAME2.format(ci_type.name))


class CITypeAttributeCache(object):
    """
    key is type_id  & attr_id
    """

    PREFIX_ID = "CITypeAttribute::TypeID::{0}::AttrID::{1}"

    @classmethod
    def get(cls, type_id, attr_id):
        attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id))
        attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id))
        attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)

        if attr is not None:
            cls.set(type_id, attr_id, attr)

        return attr

    @classmethod
    def set(cls, type_id, attr_id, attr):
        cache.set(cls.PREFIX_ID.format(type_id, attr_id), attr)

    @classmethod
    def clean(cls, type_id, attr_id):
        cache.delete(cls.PREFIX_ID.format(type_id, attr_id))


class CMDBCounterCache(object):
    KEY = 'CMDB::Counter'

    @classmethod
    def get(cls):
        result = cache.get(cls.KEY) or {}

        if not result:
            result = cls.reset()

        return result

    @classmethod
    def set(cls, result):
        cache.set(cls.KEY, result, timeout=0)

    @classmethod
    def reset(cls):
        customs = CustomDashboardManager.get()
        result = {}
        for custom in customs:
            if custom['category'] == 0:
                res = cls.sum_counter(custom)
            elif custom['category'] == 1:
                res = cls.attribute_counter(custom)
            else:
                res = cls.relation_counter(custom.get('type_id'),
                                           custom.get('level'),
                                           custom.get('options', {}).get('filter', ''),
                                           custom.get('options', {}).get('type_ids', ''))

            if res:
                result[custom['id']] = res

        cls.set(result)

        return result

    @classmethod
    def update(cls, custom, flush=True):
        result = cache.get(cls.KEY) or {}
        if not result:
            result = cls.reset()

        if custom['category'] == 0:
            res = cls.sum_counter(custom)
        elif custom['category'] == 1:
            res = cls.attribute_counter(custom)
        else:
            res = cls.relation_counter(custom.get('type_id'),
                                       custom.get('level'),
                                       custom.get('options', {}).get('filter', ''),
                                       custom.get('options', {}).get('type_ids', ''))

        if res and flush:
            result[custom['id']] = res
            cls.set(result)

        return res

    @staticmethod
    def relation_counter(type_id, level, other_filer, type_ids):
        from api.lib.cmdb.search.ci_relation.search import Search as RelSearch
        from api.lib.cmdb.search import SearchError
        from api.lib.cmdb.search.ci import search

        query = "_type:{}".format(type_id)
        s = search(query, count=1000000)
        try:
            type_names, _, _, _, _, _ = s.search()
        except SearchError as e:
            current_app.logger.error(e)
            return

        type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names]

        s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
        try:
            stats = s.statistics(type_ids)
        except SearchError as e:
            current_app.logger.error(e)
            return

        id2name = dict(type_id_names)
        type_ids = set()
        for i in (stats.get('detail') or []):
            for j in stats['detail'][i]:
                type_ids.add(j)
        for type_id in type_ids:
            _type = CITypeCache.get(type_id)
            id2name[type_id] = _type and _type.alias

        result = dict(summary={}, detail={})
        for i in stats:
            if i == "detail":
                for j in stats['detail']:
                    if id2name[j]:
                        result['detail'][id2name[j]] = stats['detail'][j]
                        result['detail'][id2name[j]] = dict()
                        for _j in stats['detail'][j]:
                            result['detail'][id2name[j]][id2name[_j]] = stats['detail'][j][_j]
            elif id2name.get(i):
                result['summary'][id2name[i]] = stats[i]

        return result

    @staticmethod
    def attribute_counter(custom):
        from api.lib.cmdb.search import SearchError
        from api.lib.cmdb.search.ci import search

        custom.setdefault('options', {})
        type_id = custom.get('type_id')
        attr_id = custom.get('attr_id')
        type_ids = custom['options'].get('type_ids') or (type_id and [type_id])
        attr_ids = list(map(str, custom['options'].get('attr_ids') or (attr_id and [attr_id])))
        other_filter = custom['options'].get('filter')
        other_filter = "({})".format(other_filter) if other_filter else ''

        if custom['options'].get('ret') == 'cis':
            query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
            s = search(query, fl=attr_ids, ret_key='alias', count=100)
            try:
                cis, _, _, _, _, _ = s.search()
            except SearchError as e:
                current_app.logger.error(e)
                return

            return cis

        result = dict()
        # level = 1
        query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
        s = search(query, fl=attr_ids, facet=[attr_ids[0]], count=1)
        try:
            _, _, _, _, _, facet = s.search()
        except SearchError as e:
            current_app.logger.error(e)
            return
        for i in (list(facet.values()) or [[]])[0]:
            result[i[0]] = i[1]
        if len(attr_ids) == 1:
            return result

        # level = 2
        for v in result:
            query = "_type:({}),{},{}:{}".format(";".join(map(str, type_ids)), other_filter, attr_ids[0], v)
            s = search(query, fl=attr_ids, facet=[attr_ids[1]], count=1)
            try:
                _, _, _, _, _, facet = s.search()
            except SearchError as e:
                current_app.logger.error(e)
                return
            result[v] = dict()
            for i in (list(facet.values()) or [[]])[0]:
                result[v][i[0]] = i[1]

        if len(attr_ids) == 2:
            return result

        # level = 3
        for v1 in result:
            if not isinstance(result[v1], dict):
                continue
            for v2 in result[v1]:
                query = "_type:({}),{},{}:{},{}:{}".format(";".join(map(str, type_ids)), other_filter,
                                                           attr_ids[0], v1, attr_ids[1], v2)
                s = search(query, fl=attr_ids, facet=[attr_ids[2]], count=1)
                try:
                    _, _, _, _, _, facet = s.search()
                except SearchError as e:
                    current_app.logger.error(e)
                    return
                result[v1][v2] = dict()
                for i in (list(facet.values()) or [[]])[0]:
                    result[v1][v2][i[0]] = i[1]

        return result

    @staticmethod
    def sum_counter(custom):
        from api.lib.cmdb.search import SearchError
        from api.lib.cmdb.search.ci import search

        custom.setdefault('options', {})
        type_id = custom.get('type_id')
        type_ids = custom['options'].get('type_ids') or (type_id and [type_id])
        other_filter = custom['options'].get('filter') or ''

        query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
        s = search(query, count=1)
        try:
            _, _, _, _, numfound, _ = s.search()
        except SearchError as e:
            current_app.logger.error(e)
            return

        return numfound
