# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making BK-LOG 蓝鲸日志平台 available.
Copyright (C) 2021 THL A29 Limited, a Tencent company.  All rights reserved.
BK-LOG 蓝鲸日志平台 is licensed under the MIT License.
License for BK-LOG 蓝鲸日志平台:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import re
from collections import defaultdict
from django.conf import settings
from django.db import transaction
from django.utils import timezone
from django.utils.translation import ugettext as _

from apps.api import TransferApi
from apps.constants import UserOperationTypeEnum, UserOperationActionEnum
from apps.iam import ResourceEnum, Permission
from apps.log_trace.handlers.proto.proto import Proto
from apps.utils.bk_data_auth import BkDataAuthHandler
from apps.log_esquery.utils.es_route import EsRoute
from apps.log_search.tasks.mapping import sync_single_index_set_mapping_snapshot
from apps.feature_toggle.handlers.toggle import feature_switch
from apps.utils.log import logger
from apps.log_search.exceptions import (
    IndexSetDoseNotExistException,
    ResultTableIdDuplicateException,
    ScenarioNotSupportedException,
    IndexCrossClusterException,
    IndexListDataException,
    IndexTraceNotAcceptException,
    SearchUnKnowTimeField,
    UnauthorizedResultTableException,
    IndexSetSourceException,
    BaseSearchParamNotExistException,
)
from apps.log_search.models import ProjectInfo, LogIndexSet, LogIndexSetData, Scenario, UserIndexSetConfig
from apps.utils import APIModel
from apps.utils.local import get_request_username, get_request_app_code
from apps.log_search.constants import GlobalCategoriesEnum, EsHealthStatus, COMMON_LOG_INDEX_RE, BKDATA_INDEX_RE
from apps.log_databus.handlers.storage import StorageHandler
from apps.log_databus.constants import STORAGE_CLUSTER_TYPE
from apps.api import BkLogApi
from apps.log_search.handlers.search.mapping_handlers import MappingHandlers
from apps.log_search.constants import TimeFieldTypeEnum, TimeFieldUnitEnum, DEFAULT_TIME_FIELD
from apps.decorators import user_operation_record
from apps.utils.thread import MultiExecuteFunc


class IndexSetHandler(APIModel):
    def __init__(self, index_set_id=None):
        super().__init__()
        self.index_set_id = index_set_id

    @staticmethod
    def get_index_set_for_storage(storage_cluster_id):
        return LogIndexSet.objects.filter(storage_cluster_id=storage_cluster_id)

    @staticmethod
    def add_field_config_record(index_set_id, display_fields, sort_list, scope="default"):
        """
        添加index字段配置
        :param index_set_id:
        :param display_fields:
        :param sort_list:
        :param scope
        :return:
        """
        if not index_set_id or not display_fields:
            raise BaseSearchParamNotExistException()
        user_name = get_request_username()
        condition = {"index_set_id": index_set_id, "created_by": user_name, "scope": scope}
        current_time = timezone.now()
        kwargs = {
            "display_fields": display_fields,
            "updated_at": current_time,
            "updated_by": user_name,
            "sort_list": sort_list,
        }
        if UserIndexSetConfig.objects.filter(**condition).exists():
            UserIndexSetConfig.objects.filter(**condition).update(**kwargs)
            is_add = False
        else:
            UserIndexSetConfig.objects.create(
                index_set_id=index_set_id, display_fields=display_fields, scope=scope, sort_list=sort_list
            )
            is_add = True

        # add user_operation_record
        try:
            log_index_set_obj = LogIndexSet.objects.get(index_set_id=index_set_id)
            operation_record = {
                "username": get_request_username(),
                "biz_id": 0,
                "project_id": log_index_set_obj.project_id,
                "record_type": UserOperationTypeEnum.SEARCH,
                "record_sub_type": log_index_set_obj.scenario_id,
                "record_object_id": index_set_id,
                "action": UserOperationActionEnum.CREATE if is_add else UserOperationActionEnum.UPDATE,
                "params": {
                    "index_set_id": index_set_id,
                    "display_fields": display_fields,
                    "sort_list": sort_list,
                    "scope": scope,
                },
            }
        except LogIndexSet.DoesNotExist:
            logger.exception(f"LogIndexSet --> {index_set_id} 不存在")
        else:
            user_operation_record.delay(operation_record)

        return list()

    @classmethod
    def get_user_index_set(cls, project_id, scenarios=None):
        index_sets = LogIndexSet.get_index_set(scenarios=scenarios, project_id=project_id)
        return index_sets

    @classmethod
    def post_list(cls, index_sets):
        """
        补充存储集数据分类、数据源、集群名称字段
        :param index_sets:
        :return:
        """

        # 获取集群列表，集群ID和集群名称的对应关系
        cluster_map = {}

        # 如果有启动日志采集接入，则获取集群列表信息
        if feature_switch("scenario_log") and settings.RUN_VER != "tencent":
            cluster_map = IndexSetHandler.get_cluster_map()
        scenario_choices = dict(Scenario.CHOICES)

        project_ids = {index["project_id"] for index in index_sets}
        project_infos = ProjectInfo.objects.filter(project_id__in=list(project_ids)).values("project_id", "bk_biz_id")
        project_mapping = {info["project_id"]: info["bk_biz_id"] for info in project_infos}

        multi_execute_func = MultiExecuteFunc()
        for _index in index_sets:
            _index["category_name"] = GlobalCategoriesEnum.get_display(_index["category_id"])
            _index["scenario_name"] = scenario_choices.get(_index["scenario_id"])
            _index["storage_cluster_name"] = ",".join(
                {storage_name for storage_name in cluster_map.get(_index["storage_cluster_id"], "").split(",")}
            )

            normal_idx = [idx for idx in _index["indexes"] if idx["apply_status"] == LogIndexSetData.Status.NORMAL]

            if _index["scenario_id"] == Scenario.BKDATA:
                multi_execute_func.append(
                    _index["index_set_id"],
                    BkLogApi.cluster,
                    {
                        "scenario_id": Scenario.BKDATA,
                        "indices": ",".join([index.get("result_table_id") for index in _index.get("indexes", [])]),
                    },
                )

            if len(normal_idx) == len(_index["indexes"]):
                _index["apply_status"] = LogIndexSet.Status.NORMAL
                _index["apply_status_name"] = _("正常")
            else:
                _index["apply_status"] = LogIndexSet.Status.PENDING
                _index["apply_status_name"] = _("审批中")

            # 补充业务ID信息
            _index["bk_biz_id"] = project_mapping.get(_index["project_id"], 0)

            if not _index["time_field"]:
                time_field = None
                if _index["indexes"]:
                    time_field = _index["indexes"][0].get("time_field")
                if _index["scenario_id"] in [Scenario.BKDATA, Scenario.LOG] and not time_field:
                    time_field = DEFAULT_TIME_FIELD
                _index["time_field_type"] = TimeFieldTypeEnum.DATE.value
                _index["time_field_unit"] = TimeFieldUnitEnum.MILLISECOND.value
                _index["time_field"] = time_field
        result = multi_execute_func.run()
        for _index in index_sets:
            if _index["scenario_id"] == Scenario.BKDATA:
                _index["storage_cluster_id"] = result.get(_index["index_set_id"], {}).get("storage_cluster_id")
                _index["storage_cluster_name"] = ",".join(
                    {
                        storage_name
                        for storage_name in result.get(_index["index_set_id"], {})
                        .get("storage_cluster_name", "")
                        .split(",")
                    }
                )

        return index_sets

    @staticmethod
    def get_cluster_map():
        """
        集群ID和集群名称映射关系
        :return:
        """
        cluster_data = TransferApi.get_cluster_info()
        cluster_map = {}
        for cluster_obj in cluster_data:
            cluster_map.update(
                {cluster_obj["cluster_config"]["cluster_id"]: cluster_obj["cluster_config"]["cluster_name"]}
            )
        return cluster_map

    @classmethod
    @transaction.atomic()
    def create(
        cls,
        index_set_name,
        project_id,
        storage_cluster_id,
        scenario_id,
        view_roles,
        indexes,
        category_id=None,
        collector_config_id=None,
        is_trace_log=False,
        time_field=None,
        time_field_type=None,
        time_field_unit=None,
        bk_app_code=None,
        username="",
    ):
        # 创建索引
        index_set_handler = cls.get_index_set_handler(scenario_id)
        view_roles = []
        if not bk_app_code:
            bk_app_code = get_request_app_code()
        index_set = index_set_handler(
            index_set_name,
            project_id,
            storage_cluster_id,
            view_roles,
            indexes=indexes,
            category_id=category_id,
            collector_config_id=collector_config_id,
            is_trace_log=is_trace_log,
            time_field=time_field,
            time_field_type=time_field_type,
            time_field_unit=time_field_unit,
            bk_app_code=bk_app_code,
            username=username,
        ).create_index_set()

        # add user_operation_record
        operation_record = {
            "username": get_request_username(),
            "biz_id": 0,
            "project_id": project_id,
            "record_type": UserOperationTypeEnum.INDEX_SET,
            "record_sub_type": scenario_id,
            "record_object_id": index_set.index_set_id,
            "action": UserOperationActionEnum.CREATE,
            "params": {
                "index_set_name": index_set_name,
                "project_id": project_id,
                "storage_cluster_id": storage_cluster_id,
                "scenario_id": scenario_id,
                "view_roles": view_roles,
                "indexes": indexes,
                "category_id": category_id,
                "collector_config_id": collector_config_id,
                "is_trace_log": is_trace_log,
                "time_field": time_field,
                "time_field_type": time_field_type,
                "time_field_unit": time_field_unit,
                "bk_app_code": bk_app_code,
            },
        }
        user_operation_record.delay(operation_record)

        return index_set

    def update(
        self,
        index_set_name,
        view_roles,
        indexes,
        category_id=None,
        is_trace_log=False,
        storage_cluster_id=None,
        time_field=None,
        time_field_type=None,
        time_field_unit=None,
        bk_app_code=None,
    ):
        index_set_handler = self.get_index_set_handler(self.scenario_id)
        view_roles = []
        index_set = index_set_handler(
            index_set_name,
            self.data.project_id,
            storage_cluster_id,
            view_roles,
            indexes=indexes,
            category_id=category_id,
            is_trace_log=is_trace_log,
            time_field=time_field,
            time_field_type=time_field_type,
            time_field_unit=time_field_unit,
            bk_app_code=bk_app_code,
        ).update_index_set(self.data)

        # add user_operation_record
        operation_record = {
            "username": get_request_username(),
            "biz_id": 0,
            "project_id": self.data.project_id,
            "record_type": UserOperationTypeEnum.INDEX_SET,
            "record_sub_type": self.data.scenario_id,
            "record_object_id": self.data.index_set_id,
            "action": UserOperationActionEnum.UPDATE,
            "params": {
                "index_set_name": index_set_name,
                "view_roles": view_roles,
                "indexes": indexes,
                "category_id": category_id,
                "is_trace_log": is_trace_log,
                "storage_cluster_id": storage_cluster_id,
                "time_field": time_field,
                "time_field_type": time_field_type,
                "time_field_unit": time_field_unit,
                "bk_app_code": bk_app_code,
            },
        }
        user_operation_record.delay(operation_record)

        return index_set

    def delete(self, index_set_name=None):
        if index_set_name:
            index_set = LogIndexSet.objects.get(index_set_id=self.index_set_id)
            index_set.index_set_name = index_set_name
            index_set.save()

        index_set_handler = self.get_index_set_handler(self.scenario_id)
        index_set_handler(
            self.data.index_set_name,
            self.data.project_id,
            self.data.storage_cluster_id,
            self.data.view_roles,
            action="delete",
        ).delete_index_set(self.data)

        # add user_operation_record
        operation_record = {
            "username": get_request_username(),
            "biz_id": 0,
            "project_id": self.data.project_id,
            "record_type": UserOperationTypeEnum.INDEX_SET,
            "record_sub_type": self.data.scenario_id,
            "record_object_id": self.data.index_set_id,
            "action": UserOperationActionEnum.DESTROY,
            "params": "",
        }
        user_operation_record.delay(operation_record)

        return

    def stop(self):
        """
        暂停索引集
        """
        index_set = LogIndexSet.objects.get(index_set_id=self.index_set_id)
        index_set.is_active = False
        index_set.save()

    def start(self):
        """
        启动索引集
        """
        index_set = LogIndexSet.objects.get(index_set_id=self.index_set_id)
        index_set.is_active = True
        index_set.save()

    def add_index(self, bk_biz_id, time_filed, result_table_id, result_table_name=None):
        """
        添加索引到索引集内
        """
        # 判断索引集是否已加入此索引
        logger.info("[index_set_data][{}]add_index => {}".format(self.index_set_id, result_table_id))
        if LogIndexSetData.objects.filter(
            bk_biz_id=bk_biz_id or None, result_table_id=result_table_id, index_set_id=self.index_set_id
        ):
            raise ResultTableIdDuplicateException(
                ResultTableIdDuplicateException.MESSAGE.format(result_table_id=result_table_id)
            )

        # 创建索引集详情
        LogIndexSetDataHandler(
            self.data,
            bk_biz_id,
            time_filed,
            result_table_id,
            storage_cluster_id=self.storage_cluster_id,
            result_table_name=result_table_name,
            bk_username=get_request_username(),
        ).add_index()
        return True

    def delete_index(self, bk_biz_id, time_filed, result_table_id):
        """
        删除索引集
        """
        LogIndexSetDataHandler(
            self.data, bk_biz_id, time_filed, result_table_id, storage_cluster_id=self.storage_cluster_id
        ).delete_index()

    def bizs(self):
        if self.data.scenario_id in [Scenario.ES]:
            return []

        # 返回业务列表
        biz_ids = (
            LogIndexSetData.objects.filter(index_set_id=self.index_set_id)
            .exclude(bk_biz_id=None)
            .values_list("bk_biz_id", flat=True)
        )

        if not biz_ids:
            return []
        return ProjectInfo.get_bizs(biz_ids)

    def indices(self):
        index_set_obj: LogIndexSet = self._get_data()
        index_set_data = index_set_obj.get_indexes(has_applied=True)
        scenario_id = index_set_obj.scenario_id
        storage_cluster_id = index_set_obj.storage_cluster_id
        index_list: list = [x.get("result_table_id") for x in index_set_data]
        if scenario_id == Scenario.ES:
            multi_execute_func = MultiExecuteFunc()
            for index in index_list:

                def get_indices(i):
                    return EsRoute(
                        scenario_id=scenario_id, storage_cluster_id=storage_cluster_id, indices=i
                    ).cat_indices()

                multi_execute_func.append(index, get_indices, index)
            result = multi_execute_func.run()
            return self._indices_result(result, index_list)
        ret = defaultdict(list)
        indices = EsRoute(
            scenario_id=scenario_id, storage_cluster_id=storage_cluster_id, indices=",".join(index_list)
        ).cat_indices()
        indices = StorageHandler.sort_indices(indices)
        index_list.sort(key=lambda s: len(s), reverse=True)
        for index_es_info in indices:
            index_es_name: str = index_es_info.get("index")
            for index_name in index_list:
                if self._match_rt_for_index(index_es_name, index_name.replace(".", "_"), scenario_id):
                    ret[index_name].append(index_es_info)
                    break
        return self._indices_result(ret, index_list)

    def _match_rt_for_index(self, index_es_name: str, index_name: str, scenario_id: str) -> bool:
        index_re = re.compile(COMMON_LOG_INDEX_RE.format(index_name))
        if scenario_id == Scenario.BKDATA:
            index_re = re.compile(BKDATA_INDEX_RE.format(index_name))
        if index_re.match(index_es_name):
            return True
        return False

    def _indices_result(self, indices_for_index: dict, index_list: list):
        result = []
        for index in index_list:
            if index in indices_for_index:
                indices_info = indices_for_index[index]
                result.append(
                    {
                        "result_table_id": index,
                        "stat": {
                            "health": self._get_health(indices_info),
                            "pri": self._get_sum("pri", indices_info),
                            "rep": self._get_sum("rep", indices_info),
                            "docs.count": self._get_sum("docs.count", indices_info),
                            "docs.deleted": self._get_sum("docs.deleted", indices_info),
                            "store.size": self._get_sum("store.size", indices_info),
                            "pri.store.size": self._get_sum("store.size", indices_info),
                        },
                        "details": indices_info,
                    }
                )
                continue
            result.append(
                {
                    "result_table_id": index,
                    "stat": {
                        "health": "--",
                        "pri": "--",
                        "rep": "--",
                        "docs.count": "--",
                        "docs.deleted": "--",
                        "store.size": 0,
                        "pri.store.size": 0,
                    },
                    "details": "--",
                }
            )
        return {"total": len(index_list), "list": result}

    def mark_favorite(self):
        index_set_obj: LogIndexSet = self._get_data()
        index_set_obj.mark_favorite(get_request_username())

    def cancel_favorite(self):
        index_set_obj: LogIndexSet = self._get_data()
        index_set_obj.cancel_favorite(get_request_username())

    @staticmethod
    def _get_health(src: list):
        has_red_health_list = [item.get("health", "") == EsHealthStatus.RED.value for item in src]
        has_yellow_health_list = [item.get("health", "") == EsHealthStatus.YELLOW.value for item in src]
        if any(has_red_health_list):
            return EsHealthStatus.RED.value
        if any(has_yellow_health_list):
            return EsHealthStatus.YELLOW.value
        return EsHealthStatus.GREEN.value

    @staticmethod
    def _get_sum(key: str, src: list) -> int:
        return sum([int(item.get(key, 0)) for item in src])

    def _get_data(self):
        try:
            obj = LogIndexSet.objects.get(index_set_id=self.index_set_id)
        except LogIndexSet.DoesNotExist:
            raise IndexSetDoseNotExistException()
        return obj

    @property
    def scenario_id(self):
        return self.data.scenario_id

    @property
    def storage_cluster_id(self):
        return self.data.storage_cluster_id

    @property
    def source_object(self):
        if not self.storage_cluster_id:
            return None
        cluster_info = StorageHandler(cluster_id=self.storage_cluster_id).get_cluster_info_by_id()
        return {
            "scenario_id": self.scenario_id,
            "domain_name": cluster_info.get("cluster_config").get("domain_name"),
            "cluster_id": cluster_info.get("cluster_config").get("cluster_id"),
            "cluster_name": cluster_info.get("cluster_config").get("cluster_name"),
            "port": cluster_info.get("cluster_config").get("port"),
            "username": cluster_info.get("auth_info").get("username"),
            "password": cluster_info.get("auth_info").get("password"),
        }

    @classmethod
    def get_index_set_handler(cls, scenario_id):
        try:
            return {
                Scenario.BKDATA: BkDataIndexSetHandler,
                Scenario.ES: EsIndexSetHandler,
                Scenario.LOG: LogIndexSetHandler,
            }[scenario_id]
        except KeyError:
            raise ScenarioNotSupportedException(ScenarioNotSupportedException.MESSAGE.format(scenario_id=scenario_id))

    @classmethod
    def get_storage_by_table_list(cls, indexes):
        """
        采集接入场景：通过所选的索引列表校验集群是否一致，且返回集群信息
        :param indexes:
        :return:
        """
        table_list = []
        for _index in indexes:
            if not _index.get("result_table_id"):
                raise IndexCrossClusterException()
            table_list.append(_index.get("result_table_id"))
        if not table_list:
            raise IndexListDataException()

        # 调用接口查询结果表集群信息
        table_str = ",".join(table_list)
        storage_info = TransferApi.get_result_table_storage(
            {"result_table_list": table_str, "storage_type": STORAGE_CLUSTER_TYPE}
        )

        # 校验所有结果表查询出的集群是否一致
        cluster_id = ""
        for _table in table_list:
            table_cluster_id = storage_info.get(_table, {}).get("cluster_config", {}).get("cluster_id")
            if not cluster_id:
                cluster_id = table_cluster_id
            if not table_cluster_id or table_cluster_id != cluster_id:
                raise IndexCrossClusterException()

        return cluster_id

    @classmethod
    def replace(
        cls,
        index_set_name,
        scenario_id,
        view_roles,
        indexes,
        bk_app_code,
        project_id=None,
        storage_cluster_id=None,
        category_id=None,
        collector_config_id=None,
        is_trace_log=False,
        time_field=None,
        time_field_type=None,
        time_field_unit=None,
    ):

        # 检查索引集是否存在
        index_set_obj = LogIndexSet.objects.filter(index_set_name=index_set_name).first()
        if index_set_obj and index_set_obj.source_app_code != bk_app_code:
            raise IndexSetSourceException()

        index_set_handler = cls.get_index_set_handler(scenario_id)
        view_roles = []

        # 更新索引集
        if index_set_obj:
            index_set = index_set_handler(
                index_set_name,
                project_id,
                storage_cluster_id,
                view_roles,
                indexes=indexes,
                category_id=category_id,
                is_trace_log=is_trace_log,
                time_field=time_field,
                time_field_type=time_field_type,
                time_field_unit=time_field_unit,
                bk_app_code=bk_app_code,
            ).update_index_set(index_set_obj)

            # add user_operation_record
            operation_record = {
                "username": get_request_username(),
                "biz_id": 0,
                "project_id": project_id,
                "record_type": UserOperationTypeEnum.INDEX_SET,
                "record_sub_type": scenario_id,
                "record_object_id": index_set_obj.index_set_id,
                "action": UserOperationActionEnum.REPLACE_UPDATE,
                "params": {
                    "index_set_name": index_set_name,
                    "project_id": project_id,
                    "view_roles": view_roles,
                    "indexes": indexes,
                    "category_id": category_id,
                    "is_trace_log": is_trace_log,
                    "storage_cluster_id": storage_cluster_id,
                    "time_field": time_field,
                    "time_field_type": time_field_type,
                    "time_field_unit": time_field_unit,
                    "bk_app_code": bk_app_code,
                    "scenario_id": scenario_id,
                },
            }
            user_operation_record.delay(operation_record)

            return index_set

        # 创建索引集
        if scenario_id == Scenario.BKDATA:
            storage_cluster_id = None
        elif scenario_id == Scenario.ES:
            pass
        else:
            storage_cluster_id = cls.get_storage_by_table_list(indexes)
        index_set = index_set_handler(
            index_set_name,
            project_id,
            storage_cluster_id,
            view_roles,
            indexes,
            category_id,
            collector_config_id,
            is_trace_log=is_trace_log,
            time_field=time_field,
            time_field_type=time_field_type,
            time_field_unit=time_field_unit,
            bk_app_code=bk_app_code,
        ).create_index_set()

        # add user_operation_record
        operation_record = {
            "username": get_request_username(),
            "biz_id": 0,
            "project_id": project_id,
            "record_type": UserOperationTypeEnum.INDEX_SET,
            "record_sub_type": scenario_id,
            "record_object_id": index_set.index_set_id,
            "action": UserOperationActionEnum.REPLACE_CREATE,
            "params": {
                "index_set_name": index_set_name,
                "project_id": project_id,
                "view_roles": view_roles,
                "indexes": indexes,
                "category_id": category_id,
                "collector_config_id": collector_config_id,
                "is_trace_log": is_trace_log,
                "storage_cluster_id": storage_cluster_id,
                "time_field": time_field,
                "time_field_type": time_field_type,
                "time_field_unit": time_field_unit,
                "bk_app_code": bk_app_code,
                "scenario_id": scenario_id,
            },
        }
        user_operation_record.delay(operation_record)

        return index_set


class BaseIndexSetHandler(object):
    scenario_id = None

    def __init__(
        self,
        index_set_name,
        project_id,
        storage_cluster_id,
        view_roles,
        indexes=None,
        category_id=None,
        collector_config_id=None,
        is_trace_log=None,
        time_field=None,
        time_field_type=None,
        time_field_unit=None,
        action=None,
        bk_app_code=None,
        username="",
    ):
        super().__init__()

        self.index_set_name = index_set_name
        self.project_id = project_id
        self.storage_cluster_id = storage_cluster_id
        self.view_roles = view_roles

        self.is_trace_log = is_trace_log

        if not indexes:
            indexes = []
        self.indexes = indexes
        self.bkdata_project_id = None
        self.index_set_obj = None
        self.collector_config_id = collector_config_id
        self.category_id = category_id
        self.bk_app_code = bk_app_code
        self.username = username

        # time_field
        self.time_field = time_field
        self.time_field_type = time_field_type
        self.time_field_unit = time_field_unit
        if self.scenario_id == Scenario.BKDATA:
            self.time_field = DEFAULT_TIME_FIELD
            self.time_field_type = TimeFieldTypeEnum.DATE.value
            self.time_field_unit = TimeFieldUnitEnum.MILLISECOND.value
        elif self.scenario_id == Scenario.LOG:
            self.time_field = DEFAULT_TIME_FIELD
            self.time_field_type = TimeFieldTypeEnum.DATE.value
            self.time_field_unit = TimeFieldUnitEnum.MILLISECOND.value
        elif self.scenario_id == Scenario.ES and action != "delete":
            if not self.time_field:
                raise SearchUnKnowTimeField()
            elif self.time_field_type in [TimeFieldTypeEnum.LONG.value]:
                if not self.time_field_unit:
                    raise SearchUnKnowTimeField()

    def create_index_set(self):
        """
        创建索引集
        """
        self.pre_create()
        logger.info("[create_index_set]pre_create index_set_name=>{}".format(self.index_set_name))

        index_set = self.create()
        logger.info("[create_index_set]create index_set_name=>{}".format(self.index_set_name))

        self.post_create(index_set)
        logger.info("[create_index_set]post_create index_set_name=>{}".format(self.index_set_name))
        return index_set

    def update_index_set(self, index_set_obj):
        """
        更新索引集
        :param index_set_obj: 索引集对象
        """
        self.index_set_obj = index_set_obj

        self.pre_update()
        logger.info("[update_index_set]pre_create index_set_name=>{}".format(self.index_set_name))

        index_set = self.update()
        logger.info("[update_index_set]create index_set_name=>{}".format(self.index_set_name))

        self.post_update(index_set)
        logger.info("[update_index_set]post_create index_set_name=>{}".format(self.index_set_name))
        return index_set

    def delete_index_set(self, index_set_obj):
        """
        删除索引集
        :param index_set_obj: 索引集对象
        """
        self.index_set_obj = index_set_obj

        self.pre_delete()
        self.delete()

    def pre_create(self):
        """
        创建前检查
        1.检查trace结构是否符合
        :return:
        """
        # 检查索引是否字段冲突
        # bkdata可能还没有授权导致无法获取mapping
        MappingHandlers(
            ",".join([index.get("result_table_id") for index in self.indexes]),
            -1,
            self.scenario_id,
            self.storage_cluster_id,
        ).check_fields_not_conflict()
        if self.is_trace_log:
            self.is_trace_log_pre_check()

    def create(self):
        # 创建索引集
        self.index_set_obj = LogIndexSet.objects.create(
            index_set_name=self.index_set_name,
            project_id=self.project_id,
            scenario_id=self.scenario_id,
            view_roles=self.view_roles,
            bkdata_project_id=self.bkdata_project_id,
            collector_config_id=self.collector_config_id,
            storage_cluster_id=self.storage_cluster_id,
            category_id=self.category_id,
            is_trace_log=self.is_trace_log,
            time_field=self.time_field,
            time_field_type=self.time_field_type,
            time_field_unit=self.time_field_unit,
            source_app_code=self.bk_app_code,
        )
        logger.info(
            "[create_index_set][{}]index_set_name => {}, indexes => {}".format(
                self.index_set_obj.index_set_id, self.index_set_name, len(self.indexes)
            )
        )

        # 创建索引集的同时添加索引
        for index in self.indexes:
            IndexSetHandler(index_set_id=self.index_set_obj.index_set_id).add_index(
                index["bk_biz_id"],
                index.get("time_field"),
                index["result_table_id"],
            )

        # 更新字段快照
        sync_single_index_set_mapping_snapshot.delay(self.index_set_obj.index_set_id)
        return self.index_set_obj

    def post_create(self, index_set):
        # 新建授权
        Permission(username=self.username).grant_creator_action(
            resource=ResourceEnum.INDICES.create_simple_instance(
                index_set.index_set_id, attribute={"name": index_set.index_set_name}
            ),
            creator=index_set.created_by,
        )

        return True

    def pre_update(self):
        MappingHandlers(
            ",".join([index.get("result_table_id") for index in self.indexes]),
            -1,
            self.scenario_id,
            self.storage_cluster_id,
        ).check_fields_not_conflict()
        if self.is_trace_log:
            self.is_trace_log_pre_check()

    def update(self):
        self.index_set_obj.category_id = self.category_id
        self.index_set_obj.index_set_name = self.index_set_name
        self.index_set_obj.view_roles = self.view_roles
        self.index_set_obj.storage_cluster_id = self.storage_cluster_id
        self.index_set_obj.is_trace_log = self.is_trace_log

        # 更新 is_active字段
        self.index_set_obj.is_active = True

        # 时间字段更新
        self.index_set_obj.time_field = self.time_field
        self.index_set_obj.time_field_type = self.time_field_type
        self.index_set_obj.time_field_unit = self.time_field_unit
        self.index_set_obj.save()

        # 需移除的索引
        to_delete_indexes = [
            index
            for index in self.index_set_obj.indexes
            if index["result_table_id"] not in [index["result_table_id"] for index in self.indexes]
        ]

        for index in to_delete_indexes:
            IndexSetHandler(index_set_id=self.index_set_obj.index_set_id).delete_index(
                index["bk_biz_id"], index.get("time_field"), index["result_table_id"]
            )

        # 需新增的索引
        to_append_indexes = [
            index
            for index in self.indexes
            if index["result_table_id"] not in [index["result_table_id"] for index in self.index_set_obj.indexes]
        ]
        for index in to_append_indexes:
            IndexSetHandler(index_set_id=self.index_set_obj.index_set_id).add_index(
                index["bk_biz_id"],
                index.get("time_field"),
                index["result_table_id"],
            )

        # 更新字段快照
        sync_single_index_set_mapping_snapshot.delay(self.index_set_obj.index_set_id)

        return self.index_set_obj

    def post_update(self, index_set):
        self.post_create(index_set)
        return index_set

    def pre_delete(self):
        pass

    def delete(self):
        self.index_set_obj.delete()

    def post_delete(self, index_set):
        # @TODO 调用auth模块删除权限
        pass

    def is_trace_log_pre_check(self):
        if self.is_trace_log:
            rt_list = [index.get("result_table_id", "") for index in self.indexes]
            mapping_list: list = BkLogApi.mapping(
                {
                    "indices": ",".join(rt_list),
                    "scenario_id": self.scenario_id,
                    "storage_cluster_id": self.storage_cluster_id,
                    "bkdata_authentication_method": "user",
                }
            )
            property_dict: dict = MappingHandlers.find_property_dict_first(mapping_list)
            field_list: list = MappingHandlers.get_all_index_fields_by_mapping(property_dict)
            trace_proto_type = Proto.judge_trace_type(field_list)
            if trace_proto_type is not None:
                return True
            raise IndexTraceNotAcceptException()
        return False


class BkDataIndexSetHandler(BaseIndexSetHandler):
    scenario_id = Scenario.BKDATA

    def __init__(self, *args, **kwargs):
        super(BkDataIndexSetHandler, self).__init__(*args, **kwargs)

        # 是否需要跳转到数据平台进行授权
        self.need_authorize = False

    def pre_create(self):
        super(BkDataIndexSetHandler, self).pre_create()
        self.check_rt_authorization_for_user()
        self.auto_authorize_rt()

    def pre_update(self):
        super(BkDataIndexSetHandler, self).pre_update()
        self.check_rt_authorization_for_user()
        self.auto_authorize_rt()

    def auto_authorize_rt(self):
        """
        自动授权
        """
        BkDataAuthHandler.authorize_result_table_to_token(
            result_tables=[index["result_table_id"] for index in self.indexes]
        )

    def check_rt_authorization_for_user(self):
        """
        判断用户是否拥有所有RT的管理权限
        """
        unauthorized_result_tables = BkDataAuthHandler(username=self.username).filter_unauthorized_rt_by_user(
            result_tables=[index["result_table_id"] for index in self.indexes]
        )
        if unauthorized_result_tables:
            raise UnauthorizedResultTableException(
                UnauthorizedResultTableException.MESSAGE.format(result_tables=",".join(unauthorized_result_tables))
            )

    def check_rt_authorization_for_token(self, index_set):
        result_tables = [index["result_table_id"] for index in self.indexes]
        unauthorized_result_tables = BkDataAuthHandler().filter_unauthorized_rt_by_token(result_tables=result_tables)

        LogIndexSetData.objects.filter(index_set_id=index_set.index_set_id).exclude(
            apply_status=LogIndexSetData.Status.ABNORMAL
        ).update(apply_status=LogIndexSetData.Status.NORMAL)

        if unauthorized_result_tables:
            # 如果存在没有权限的RT，要把状态设置为审批中
            LogIndexSetData.objects.filter(
                index_set_id=index_set.index_set_id,
                result_table_id__in=unauthorized_result_tables,
            ).update(apply_status=LogIndexSetData.Status.PENDING)

    def post_create(self, index_set):
        super(BkDataIndexSetHandler, self).post_create(index_set)
        self.check_rt_authorization_for_token(index_set)


class EsIndexSetHandler(BaseIndexSetHandler):
    scenario_id = Scenario.ES


class LogIndexSetHandler(BaseIndexSetHandler):
    scenario_id = Scenario.LOG


class LogIndexSetDataHandler(object):
    def __init__(
        self,
        index_set_data,
        bk_biz_id,
        time_filed,
        result_table_id,
        result_table_name=None,
        storage_cluster_id=None,
        apply_status=LogIndexSetData.Status.NORMAL,
        bk_username=None,
    ):
        self.index_set_data = index_set_data
        self.bk_biz_id = bk_biz_id
        self.time_field = time_filed
        self.result_table_id = result_table_id
        self.result_table_name = result_table_name
        self.storage_cluster_id = storage_cluster_id
        self.apply_status = apply_status
        self.bk_username = get_request_username() or bk_username

    def add_index(self):
        """
        往索引集添加索引
        """
        obj, created = LogIndexSetData.objects.get_or_create(  # pylint: disable=unused-variable
            defaults={
                "time_field": self.time_field,
                "result_table_name": self.result_table_name,
                "apply_status": self.apply_status,
            },
            index_set_id=self.index_set_data.index_set_id,
            bk_biz_id=self.bk_biz_id or None,
            result_table_id=self.result_table_id,
            is_deleted=False,
        )
        return obj

    def delete_index(self):
        """
        删除索引
        """
        LogIndexSetData.objects.filter(
            index_set_id=self.index_set_data.index_set_id,
            result_table_id=self.result_table_id,
            bk_biz_id=self.bk_biz_id,
        ).delete()
