#!/usr/bin/env python3
# -*- coding:utf-8 -*-
try:
    import sys
    import importlib

    importlib.reload(sys)
    import os
    import json
    import socket
    import configparser
    from gspylib.common.DbClusterInfo import dbClusterInfo
    from gspylib.inspection.common import SharedFuncs
    from gspylib.inspection.common.CheckItem import BaseItem
    from gspylib.inspection.common.CheckResult import ResultStatus
except ImportError as ie:
    raise Exception("[GAUSS-52200] : Unable to import module: %s." % str(ie))

# master
MASTER_INSTANCE = 0
# standby
STANDBY_INSTANCE = 1
# dummy standby
DUMMY_STANDBY_INSTANCE = 2

# cn
INSTANCE_ROLE_COODINATOR = 3
# dn
INSTANCE_ROLE_DATANODE = 4

g_gucDist = {}
g_ignoreList = [
    'listen_addresses',
    'local_bind_address',
    'port',
    'cstore_buffers',
    'max_connections',
    'shared_buffers',
    'work_mem',
    'maintenance_work_mem',
    'data_replicate_buffer_size',
    'pooler_port',
    'log_directory',
    'audit_directory',
    'pgxc_node_name',
    'ssd_cache_dir',
    'enable_adio_function',
    'enable_cstore_ssd_cache',
    'unix_socket_directory',
    'unix_socket_permissions',
    'log_file_mode',
    'max_coordinators',
    'max_datanodes',
    'enable_nestloop',
    'enable_mergejoin',
    'comm_tcp_mode',
    'explain_perf_mode',
    'log_line_prefix',
    'max_active_statements',
    'comm_control_port',
    'comm_sctp_port',
    'replconninfo2',
    'replconninfo1',
    'ident_file',
    'config_file',
    'hba_file',
    'data_directory',
    'archive_command',
    'xc_maintenance_mode',
]
g_logicList = [
    'allow_concurrent_tuple_update',
    'prefetch_quantity',
    'backwrite_quantity',
    'cstore_prefetch_quantity',
    'cstore_backwrite_max_threshold',
    'cstore_backwrite_quantity',
    'fast_extend_file_size',
    'bgwriter_delay',
    'bgwriter_lru_maxpages',
    'bgwriter_flush_after',
    'autovacuum_naptime',
    'autovacuum_vacuum_threshold',
    'autovacuum_analyze_threshold',
    'autovacuum_vacuum_scale_factor',
    'autovacuum_analyze_scale_factor',
    'enable_stream_operator',
    'enable_data_replicate',
    'wal_keep_segments',
    'wal_sender_timeout',
    'wal_writer_delay',
    'checkpoint_segments',
    'checkpoint_timeout',
    'checkpoint_warning',
    'checkpoint_flush_after',
    'checkpoint_wait_timeout',
    'vacuum_cost_page_hit',
    'vacuum_cost_page_miss',
    'vacuum_cost_page_dirty',
    'vacuum_cost_limit',
    'vacuum_cost_delay',
    'autovacuum_vacuum_cost_delay',
    'autovacuum_vacuum_cost_limit',
    'full_page_writes',
    'fsync',
    'io_limits',
    'io_priority',
    'bulk_write_ring_size',
    'bulk_read_ring_size',
    'partition_mem_batch',
    'partition_max_cache_size',
    'temp_file_limit',
    'query_mem',
    'maintenance_work_mem',
    'synchronous_commit',
    'work_mem',
    'dynamic_memory_quota',
    'temp_buffers',
    'max_loaded_cudesc',
    'wal_receiver_status_interval',
    'wal_receiver_timeout',
    'wal_receiver_connect_timeout',
    'wal_receiver_connect_retries',
    'wal_receiver_buffer_size',
    'data_replicate_buffer_size',
    'max_connections',
    'max_files_per_process',
    'shared_buffers',
    'cstore_buffers',
    'UDFWorkerMemHardLimit',
    'walsender_max_send_size',
    'wal_buffers',
    'max_wal_senders',
    'max_replication_slots',
    'autovacuum_freeze_max_age',
    'autovacuum_max_workers',
    'track_activity_query_size',
    'event_source',
    'zhparser_dict_in_memory',
    'enable_memory_limit',
]


class CheckGUCConsistent(BaseItem):

    def __init__(self):
        super(CheckGUCConsistent, self).__init__(self.__class__.__name__)

    def preCheck(self):
        # check the threshold was set correctly
        if (not self.threshold.__contains__('version')):
            raise Exception("version can not be empty")
        self.version = self.threshold['version']

    def checkLogicCluster(self):
        clusterInfo = dbClusterInfo()
        staticConfigDir = os.path.join(self.cluster.appPath, "bin")
        cmd = "find %s -name *.cluster_static_config" % staticConfigDir
        output = SharedFuncs.runShellCmd(cmd)
        if output:
            for staticConfigFile in output.splitlines():
                clusterInfo.initFromStaticConfig(self.user, staticConfigFile, True)
                lcName = os.path.splitext(os.path.basename(staticConfigFile))[0]
                for dbnode in clusterInfo.dbNodes:
                    if (dbnode.name == socket.gethostname()):
                        return [lcName, dbnode]
            return ["", None]
        else:
            return ["", None]

    def checkInstanceGucValue(self, Instance, needm, lcName="", logicCluster=False):
        """
        get CN/DN instance guc parameters
        """
        global g_gucDist
        LCInstanceGucDist = {}
        lcInstance = {}
        sqlcmd = "select name,setting from pg_catalog.pg_settings;"
        InstanceGucDist = {}
        output = SharedFuncs.runSqlCmd(sqlcmd, self.user, "", Instance.port, self.tmpPath, "postgres", self.mpprcFile,
                                       needm)
        gucValueList = output.split('\n')
        for gucValue in gucValueList:
            if (len(gucValue.split('|')) == 2):
                (parameter, value) = gucValue.split('|')
                if (parameter == "transaction_read_only" and Instance.instanceRole == INSTANCE_ROLE_DATANODE):
                    continue
                if (parameter not in g_ignoreList):
                    if (not logicCluster):
                        InstanceGucDist[parameter] = value
                    else:
                        if (parameter not in g_logicList):
                            InstanceGucDist[parameter] = value
                        elif (lcName and parameter in g_logicList):
                            LCInstanceGucDist[parameter] = value
                        else:
                            continue
        if (lcName):
            instanceName = "%s_%s_%s" % (lcName, "DN", Instance.instanceId)
            lcInstance[instanceName] = LCInstanceGucDist
            return lcInstance
        if (Instance.instanceRole == INSTANCE_ROLE_COODINATOR):
            Role = "CN"
        elif (Instance.instanceRole == INSTANCE_ROLE_DATANODE):
            Role = "DN"
        instanceName = "%s_%s" % (Role, Instance.instanceId)
        g_gucDist[instanceName] = InstanceGucDist
        return lcInstance

    def doCheck(self):
        """
        """
        global g_gucDist
        DNidList = []
        result = []
        logicCluster = False
        nodeInfo = self.cluster.getDbNodeByName(self.host)
        CN = nodeInfo.coordinators
        masterDnList = SharedFuncs.getMasterDnNum(self.user, self.mpprcFile)
        for DnInstance in nodeInfo.datanodes:
            if DnInstance.instanceType != DUMMY_STANDBY_INSTANCE:
                DNidList.append(DnInstance)
        if len(CN) < 1 and len(DNidList) < 1:
            raise Exception("There is no CN instance and DN instance in the current node.")
        # get information of logicCluster on current node
        (lcName, dbnode) = self.checkLogicCluster()
        if dbnode:
            logicCluster = True
            for DnInstance in dbnode.datanodes:
                if DnInstance.instanceType != DUMMY_STANDBY_INSTANCE:
                    if DnInstance.instanceId in masterDnList:
                        needm = False
                    else:
                        needm = True
                    result.append(self.checkInstanceGucValue(DnInstance, needm, lcName, logicCluster))
            g_gucDist[lcName] = result
        # test database Connection
        for Instance in (CN + DNidList):
            if Instance == "" or Instance is None:
                continue
            sqlcmd = "select pg_catalog.pg_sleep(1);"
            if Instance.instanceRole == INSTANCE_ROLE_COODINATOR:
                needm = False
            elif Instance.instanceId in masterDnList:
                needm = False
            else:
                needm = True
            SharedFuncs.runSqlCmd(sqlcmd, self.user, "", Instance.port, self.tmpPath, 'postgres',
                                  self.mpprcFile, needm)
            self.checkInstanceGucValue(Instance, needm, "", logicCluster)

        self.result.val = json.dumps(g_gucDist)
        self.result.raw = str(g_gucDist)
        self.result.rst = ResultStatus.OK

    def postAnalysis(self, itemResult):
        errors = []
        ngs = []
        cnGucDist = {}
        dnGucDist = {}
        lcdnGucDist = {}
        itemResult.standard = self.standard
        for i in itemResult.getLocalItems():
            if i.rst == ResultStatus.ERROR:
                errors.append("%s: %s" % (i.host, i.val))
            if i.rst == ResultStatus.NG:
                ngs.append("%s: %s" % (i.host, i.val))
        if len(errors) > 0:
            itemResult.rst = ResultStatus.ERROR
            itemResult.analysis = "\n".join(errors)
            return itemResult
        if len(ngs) > 0:
            itemResult.rst = ResultStatus.NG
            itemResult.analysis = "\n".join(ngs)
            return itemResult
        for v in itemResult.getLocalItems():
            gucDist = json.loads(v.val)
            self.get_instance_guc_dist(gucDist, cnGucDist, dnGucDist, lcdnGucDist)
        if len(cnGucDist.keys()) > 0:
            itemResult.analysis += self.paramAnalysis(cnGucDist)
        itemResult.analysis += self.paramAnalysis(dnGucDist)

        for lcName in lcdnGucDist.keys():
            lcInstanceResult = lcdnGucDist[lcName]
            baseDn = lcInstanceResult[0]
            baseParameter = baseDn[list(baseDn.keys())[0]]
            for parameter in baseParameter.keys():
                keyValue = baseParameter[parameter]
                result_str = "\n%s:\n%s: %s\n" % (parameter, list(baseDn.keys())[0], keyValue)
                flag = True
                for otherDn in lcInstanceResult[1:]:
                    dnInstance = list(otherDn.keys())[0]
                    value = otherDn[dnInstance][parameter]
                    result_str = "%s%s: %s\n" % (result_str, dnInstance, value)
                    if value != keyValue:
                        flag = False
                if not flag:
                    itemResult.analysis += result_str

        if itemResult.analysis:
            itemResult.rst = ResultStatus.NG
        else:
            itemResult.rst = ResultStatus.OK
            itemResult.analysis = "All CN/DN instance guc value is consistent."
        return itemResult

    def paramAnalysis(self, gucDic):
        resultStr = ""
        for parameter in gucDic[list(gucDic.keys())[0]].keys():
            InstanceName = list(gucDic.keys())[0]
            keyValue = gucDic[InstanceName][parameter]
            result = "\n%s:\n%s: %s\n" % (parameter, InstanceName, keyValue)
            flag = True
            for dnInstance in list(gucDic.keys())[1:]:
                value = gucDic[dnInstance][parameter]
                result = "%s%s: %s\n" % (result, dnInstance, value)
                if value != keyValue:
                    flag = False
            if not flag:
                resultStr += result
        return resultStr

    @staticmethod
    def get_instance_guc_dist(gucDist, cnGucDist, dnGucDist, lcdnGucDist):
        for InstanceName in gucDist.keys():
            if InstanceName[:2] == 'CN':
                cnGucDist[InstanceName] = gucDist[InstanceName]
            elif InstanceName[:2] == 'DN':
                dnGucDist[InstanceName] = gucDist[InstanceName]
            else:
                if InstanceName in lcdnGucDist.keys():
                    lcdnGucDist[InstanceName].extend(gucDist[InstanceName])
                else:
                    lcdnGucDist[InstanceName] = gucDist[InstanceName]
