# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2020 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
#############################################################################
from gspylib.inspection.common import SharedFuncs

import imp
import types
from abc import abstractmethod
from gspylib.common.ErrorCode import ErrorCode
from gspylib.inspection.common.CheckResult import LocalItemResult, \
    ResultStatus
from gspylib.inspection.common.Exception import CheckNAException
from base_utils.os.net_util import NetUtil


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

    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 = ""

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

    rst = ResultStatus.OK
    okMsg, okAnalysisList = 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 = []
    nas, 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 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 = NetUtil.GetHostIpOrName()
        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

    @abstractmethod
    def preCheck(self):
        '''
        abstract precheck for check item
        '''
        pass

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

    @abstractmethod
    def postAnalysis(self, itemResult, category="", name=""):
        '''
        analysis the item result got from each node
        '''
        pass

    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.__getLocalIP(context.nodes)
        self.LCName = context.LCName
        self.ShrinkNodes = context.ShrinkNodes
        if not context.thresholdDn:
            self.thresholdDn = 90
        else:
            self.thresholdDn = context.thresholdDn
        # new host without cluster installed
        if (not self.user):
            self.host = NetUtil.GetHostIpOrName()
            self.result.host = NetUtil.GetHostIpOrName()

    def __getLocalIP(self, nodeList):
        for node in nodeList:
            if (SharedFuncs.is_local_node(node) and SharedFuncs.validate_ipv4(
                    node)):
                self.ipAddr = node
                return

    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(ErrorCode.GAUSS_530["GAUSS_53030"]
                                % "cluster attribute")
            if (not hasattr(self, 'host')):
                raise Exception(ErrorCode.GAUSS_530["GAUSS_53030"]
                                % "host attribute")
            if (not self.cluster):
                raise Exception(ErrorCode.GAUSS_530["GAUSS_53031"])
            dbNode = self.cluster.getDbNodeByName(self.host)
            # The specified node does not exist or is empty
            if (dbNode is None or dbNode == ""):
                raise Exception(ErrorCode.GAUSS_530["GAUSS_53013"]
                                % "The dbNode")
            if self.cluster.isSingleInstCluster():
                masterDn = SharedFuncs.getMasterDnNum(self.user,
                                                      self.mpprcFile)
                if len(dbNode.datanodes) < 1 or dbNode.datanodes[
                    0].instanceId not in masterDn:
                    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():
            return func()

        return wrapper


class CheckItemFactory(object):
    @staticmethod
    def createItem(name, path, scope='all', analysis='default'):
        mod = imp.load_source(name, path)
        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):
        mod = imp.load_source(name, path)
        clazz = getattr(mod, name)
        checker = clazz()
        checker.initFrom(context)
        return checker
