#!/usr/bin/env python3
# -*- coding:utf-8 -*-

try:
    import sys
    import importlib.util

    importlib.reload(sys)
    import socket
    import types
    from abc import abstractmethod
    from gspylib.inspection.common import SharedFuncs
    from gspylib.inspection.common.CheckResult import LocalItemResult, ResultStatus
    from gspylib.inspection.common.Exception import CheckNAException
    from gspylib.os.gsnetwork import g_network
except ImportError as ie:
    raise Exception("[GAUSS-52200] : Unable to import module: %s." % str(ie))


def defaultAnalysis(self, itemResult):
    # Get the item result information
    itemResult.standard = self.standard
    itemResult.suggestion = self.suggestion
    itemResult.category = self.category
    itemResult.title = self.title

    vals = analysis_results(itemResult)

    analysisStrList = []
    _, oks, ngs, warnings, errors = classifyItemResult(itemResult)
    total = len(oks) + len(ngs) + len(warnings) + len(errors)

    rst = ResultStatus.OK
    okMsg, _ = countItemResult(oks)
    warningMsg, warningAnalysisList = countItemResult(warnings)
    failedMsg, failedAnalysisList = countItemResult(ngs)
    errorMsg, errorAnalysisList = countItemResult(errors)
    if len(warnings) > 0:
        rst = ResultStatus.WARNING
    if len(ngs) > 0:
        rst = ResultStatus.NG
    if len(errors) > 0:
        rst = ResultStatus.ERROR
    countMsg = "The item run on %s nodes. %s%s%s%s" % (total, okMsg, warningMsg, failedMsg, errorMsg)
    analysisStrList.append(countMsg)
    if errorAnalysisList:
        analysisStrList.extend(errorAnalysisList)
    if failedAnalysisList:
        analysisStrList.extend(failedAnalysisList)
    if warningAnalysisList:
        analysisStrList.extend(warningAnalysisList)
    if itemResult.name == 'CheckSysTable':
        value = [vals[key] for key in sorted(vals.keys())]
        analysisStrList.extend(value)
    itemResult.rst = rst
    itemResult.analysis = "\n".join(analysisStrList)
    return itemResult


def consistentAnalysis(self, itemResult):
    # check the rst in each node and make sure the var is consistence
    itemResult.standard = self.standard
    itemResult.suggestion = self.suggestion
    itemResult.category = self.category
    itemResult.title = self.title

    analysisStrList = []
    _, oks, ngs, warnings, errors = classifyItemResult(itemResult)
    total = len(oks) + len(ngs) + len(warnings) + len(errors)

    # The item run on %s nodes. success: %s  warning: %s ng:%s error:%
    rst = ResultStatus.OK
    if len(oks) == total:
        okMsg, okAnalysisList = countItemResult(oks, True)
    else:
        okMsg, okAnalysisList = countItemResult(oks)
    warningMsg, warningAnalysisList = countItemResult(warnings)
    failedMsg, failedAnalysisList = countItemResult(ngs)
    errorMsg, errorAnalysisList = countItemResult(errors)
    if len(okAnalysisList) > 0:
        okMsg += " (consistent) " if (len(okAnalysisList) == 1) else " (not consistent) "

    if len(warnings) > 0 and rst == ResultStatus.OK:
        rst = ResultStatus.WARNING
    if len(okAnalysisList) > 1:
        rst = ResultStatus.NG
        if itemResult.name in ["CheckDiskConfig", "CheckCpuCount", "CheckMemInfo", "CheckStack", "CheckKernelVer"]:
            rst = ResultStatus.WARNING
    if len(ngs) > 0:
        rst = ResultStatus.NG
    if len(errors) > 0:
        rst = ResultStatus.ERROR

    countMsg = "The item run on %s nodes. %s%s%s%s" % (total, okMsg, warningMsg, failedMsg, errorMsg)
    analysisStrList.append(countMsg)
    if errorAnalysisList:
        analysisStrList.extend(errorAnalysisList)
    if failedAnalysisList:
        analysisStrList.extend(failedAnalysisList)
    if warningAnalysisList:
        analysisStrList.extend(warningAnalysisList)
    if okAnalysisList:
        analysisStrList.extend(okAnalysisList)
    itemResult.rst = rst
    itemResult.analysis = "\n".join(analysisStrList)
    return itemResult


def analysis_results(itemResult):
    errors = []
    ngs = []
    warnings = []
    vals = {}
    for i in itemResult.getLocalItems():
        if i.rst == ResultStatus.OK or i.rst == ResultStatus.NA:
            if i.val:
                vals[i.host] = i.val
            continue
        elif i.rst == ResultStatus.ERROR:
            errors.append("%s : %s" % (i.host, i.val))
        elif i.rst == ResultStatus.WARNING:
            warnings.append("%s : %s" % (i.host, i.val))
        else:
            ngs.append("%s : %s" % (i.host, i.val))
    # Analysis results
    if len(ngs) > 0 or len(errors) > 0 or len(warnings) > 0:
        rst = ResultStatus.WARNING
        if len(errors) > 0:
            rst = ResultStatus.ERROR
        elif len(ngs) > 0:
            rst = ResultStatus.NG
        itemResult.rst = rst
        itemResult.analysis = "\n".join(ngs + errors + warnings)
    else:
        itemResult.rst = ResultStatus.OK
        itemResult.analysis = ""
    return vals


def getValsItems(vals):
    """

    :param vals:
    :return:
    """
    ret = {}
    for _, i_val in vals.items():
        try:
            i_val = eval(i_val)
        except Exception:
            i_val_info = i_val
        if isinstance(i_val_info, dict):
            for j_key, j_val in i_val_info.items():
                ret[j_key] = j_val

    return ret


def getCheckType(category):
    '''
    function : get check type
    input : category
    output : 1,2,3
    '''
    if not category:
        return 0
    if category == "cluster":
        return 1
    elif category == "database":
        return 3
    else:
        return 2


def classifyItemResult(itemResult):
    nas = []
    oks = []
    ngs = []
    wns = []
    ers = []
    # Summary results
    for i in itemResult.getLocalItems():
        if (i.rst == ResultStatus.OK):
            oks.append(i)
        if (i.rst == ResultStatus.NA):
            nas.append(i)
        if (i.rst == ResultStatus.NG):
            ngs.append(i)
        if (i.rst == ResultStatus.WARNING):
            wns.append(i)
        if (i.rst == ResultStatus.ERROR):
            ers.append(i)
    return (nas, oks, ngs, wns, ers)


def countItemResult(itemList, allNode=False):
    if (itemList is None or len(itemList) == 0):
        return ("", [])
    first = itemList[0]
    msgTitle = "default"
    if (first.rst == ResultStatus.WARNING):
        msgTitle = "warning"
    if (first.rst == ResultStatus.NG):
        msgTitle = "ng"
    if (first.rst == ResultStatus.ERROR):
        msgTitle = "error"
    if (first.rst == ResultStatus.OK):
        msgTitle = "success"
    countMsg = " %s: %s " % (msgTitle, len(itemList))

    defaultHosts = [first.host]
    diffs = []
    for i in itemList[1:]:
        if i.val == first.val:
            defaultHosts.append(i.host)
            continue
        else:
            diffs.append("The different[%s] value:\n%s" % (i.host, i.val))
    if (allNode):
        analysisStrList = ["The %s on all nodes value:\n%s" % (msgTitle, first.val)]
    else:
        analysisStrList = ["The %s%s value:\n%s" % (msgTitle, '[' + ",".join(defaultHosts) + ']', first.val)]
    if (len(diffs) > 0):
        analysisStrList.extend(diffs)
    return (countMsg, analysisStrList)


class BaseItem(object):
    '''
    base class of check item
    '''

    def __init__(self, name):
        '''
        Constructor
        '''
        self.name = name
        self.title = None
        self.set = False
        self.log = None
        self.suggestion = None
        self.standard = None
        self.threshold = {}
        self.category = 'other'
        self.permission = 'user'
        self.analysis = 'default'
        self.scope = 'all'
        self.cluster = None
        self.port = None
        self.user = None
        self.nodes = None
        self.mpprcFile = None
        self.thresholdDn = None
        self.context = None
        self.tmpPath = None
        self.outPath = None
        self.host = socket.gethostname()
        self.result = LocalItemResult(name, self.host)
        self.routing = None
        self.skipSetItem = []
        self.ipAddr = None
        # self cluster name not only lc
        self.LCName = None
        self.ShrinkNodes = None
        self.master_dn_list = None
        self.service_network_adapts = None

    @abstractmethod
    def preCheck(self):  # pylint: disable=E0202
        pass

    @abstractmethod
    def doSet(self):  # pylint: disable=E0202
        pass

    @abstractmethod
    def doCheck(self):
        '''
        check script for each item
        '''
        return None

    def initFrom(self, context):
        '''
        initialize the check item from context
        '''
        item = next(i for i in context.items if i['name'] == self.name)
        if item:
            self.title = self.__getLocaleAttr(item, 'title')
            self.suggestion = self.__getLocaleAttr(item, 'suggestion')
            self.standard = self.__getLocaleAttr(item, 'standard')
            if (item.__contains__('threshold')):
                self.category = item['category']
            if (item.__contains__('threshold')):
                self.threshold = item['threshold']
            # set pre check method
            self.setScope(item['scope'])
            # set post analysis method
            self.setAnalysis(item['analysis'])

        self.context = context
        self.cluster = context.cluster
        self.user = context.user
        self.nodes = context.nodes
        self.mpprcFile = context.mpprc
        self.result.checkID = context.checkID
        self.result.user = context.user
        self.tmpPath = context.tmpPath
        self.outPath = context.outPath
        self.set = context.set
        self.log = context.log
        self.routing = context.routing
        self.skipSetItem = context.skipSetItem
        self.all_network_info = g_network.getAllNetworkIp()
        self.LCName = context.LCName
        self.ShrinkNodes = context.ShrinkNodes
        self.master_dn_list = context.master_dn_list
        if not context.thresholdDn:
            self.thresholdDn = 90
        else:
            self.thresholdDn = context.thresholdDn
        # new host without cluster installed
        if (not self.user):
            self.host = socket.gethostname()
            self.result.host = socket.gethostname()

    def judge_status_by_flag(self, flag, info):
        """
        function: judge flag status
        input: flag
        output: NA
        """
        if flag == "Error":
            self.result.rst = ResultStatus.NG
        elif flag == "Warning":
            self.result.rst = ResultStatus.WARNING
        else:
            self.result.rst = ResultStatus.OK
            self.result.val = info

    def __getLocaleAttr(self, obj, attr, language='zh'):
        '''
        get attribute value for different language
        '''
        locAttr = str(attr) + '_' + language
        if (not obj.__contains__(locAttr) or obj[locAttr] == ""):
            return obj[str(attr) + '_' + 'zh']
        else:
            return obj[locAttr]

    def setScope(self, scope):
        # Choose execution node
        self.scope = scope
        # cn node to perform the check
        if (scope == 'cn'):
            self.preCheck = self.__cnPreCheck(self.preCheck)
        # Local implementation of the inspection
        elif (scope == 'local'):
            self.preCheck = self.__localPreCheck(self.preCheck)

    def setAnalysis(self, analysis):
        # Analyze the test results
        self.analysis = analysis
        # Consistency analysis for ap
        if (analysis == 'consistent'):
            self.postAnalysis = types.MethodType(consistentAnalysis, self)
        # Default analysis for ap
        elif (analysis == 'default'):
            self.postAnalysis = types.MethodType(defaultAnalysis, self)

    def runCheck(self, context, g_logger):
        '''
        main process for checking
        '''
        try:
            g_logger.debug("Start to run %s" % self.name)
            # initialization
            self.initFrom(context)
            self.preCheck()
            # Perform the inspection
            self.doCheck()
            if (self.set
                    and (self.result.rst == ResultStatus.NG or self.result.rst == ResultStatus.WARNING)
                    and self.name not in self.skipSetItem):
                self.doSet()
                self.doCheck()
            g_logger.debug("Finish to run %s" % self.name)
        except CheckNAException:
            self.result.rst = ResultStatus.NA
        # An internal error occurred while executing code
        except Exception as e:
            self.result.rst = ResultStatus.ERROR
            self.result.val = str(e)
            g_logger.debug("Exception occur when running %s:\n%s" % (self.name, str(e)))
        finally:
            # output result
            self.result.output(context.tmpPath)

    def __cnPreCheck(self, func):
        # cn Pre-check node
        def wrapper():
            if (not hasattr(self, 'cluster')):
                raise Exception("The class must have cluster attribute")
            if (not hasattr(self, 'host')):
                raise Exception("The class must have host attribute")
            if (not self.cluster):
                raise Exception("The cluster is None")
            dbNode = self.cluster.getDbNodeByName(self.host)
            # The specified node does not exist or is empty
            if (dbNode is None or dbNode == ""):
                raise Exception("The dbNode cannot be empty.")
            if self.cluster.isSingleInstCluster():
                if len(dbNode.datanodes) < 1 or dbNode.datanodes[0].instanceId not in self.master_dn_list:
                    raise CheckNAException("The node does not contains materDn instance")
                self.port = dbNode.datanodes[0].port
            else:
                # The specified CN node does not exist
                if (len(dbNode.coordinators) == 0):
                    raise CheckNAException("The node does not contains cn instance")
                # get cn port
                self.port = dbNode.coordinators[0].port
                self.cntype = dbNode.coordinators[0].instanceType
            return func()

        return wrapper

    def __localPreCheck(self, func):
        def wrapper():
            if not hasattr(self, 'cluster'):
                raise Exception("The class must have cluster attribute")
            if not hasattr(self, 'host'):
                raise Exception("The class must have host attribute")
            if not self.cluster:
                raise Exception("The cluster is None")
            db_node = self.cluster.getDbNodeByName(self.host)
            # The specified node does not exist or is empty
            if db_node is None or db_node == "":
                raise Exception("The dbNode cannot be empty.")

            if self.cluster.isSingleInstCluster():
                if len(db_node.datanodes) < 1 or db_node.datanodes[0].instanceId not in self.master_dn_list:
                    raise CheckNAException("The node does not contains materDn instance")
                self.port = db_node.datanodes[0].port
            else:
                # The specified CN node does not exist
                if len(db_node.coordinators) == 0:
                    raise CheckNAException("The node does not contains cn instance")
                # get cn port
                self.port = db_node.coordinators[0].port
                self.cntype = db_node.coordinators[0].instanceType
            return func()

        return wrapper


class CheckItemFactory(object):
    @staticmethod
    def createItem(name, path, scope='all', analysis='default'):
        spec = importlib.util.spec_from_file_location(name, path)
        mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mod)
        clazz = getattr(mod, name)
        checker = clazz()
        # set pre check method
        checker.setScope(scope)
        # set post analysis method
        checker.setAnalysis(analysis)
        return checker

    @staticmethod
    def createFrom(name, path, context):
        spec = importlib.util.spec_from_file_location(name, path)
        mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mod)
        clazz = getattr(mod, name)
        checker = clazz()
        checker.initFrom(context)
        return checker
