# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2024 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.
# ----------------------------------------------------------------------------
# Description  : Generate_xml.py is generate xml base on cluster 
#############################################################################
import os
import pwd
import datetime
import subprocess
import copy
import xml.etree.ElementTree as ET
import xml.dom.minidom as minidom

from base_utils.os.env_util import EnvUtil
from gspylib.common.ErrorCode import ErrorCode
from base_utils.os.sshd_config import SshdConfig
from script.base_utils.os.file_util import FileUtil


class ClusterKey:
    # cluster label
    CLUSTER_NAME = "clusterName"
    NODENAMES = "nodeNames"
    GAUSS_DB_APP_PATH = "gaussdbAppPath"
    GAUSS_DB_LOG_PATH = "gaussdbLogPath"
    GAUSS_MPP_DB_PATH = "tmpMppdbPath"
    GAUSS_DB_TOOL_PATH = "gaussdbToolPath"
    CORE_PATH = "corePath"
    BAKCIP1S = "backIp1s"

    # ddes
    ENABLE_DSS = "enable_dss"
    DSS_HOME = "dss_home"
    SS_DSS_VG_NAME = "ss_dss_vg_name"
    DSS_VG_INFO = "dss_vg_info"
    VOTING_DISK_PATH = "votingDiskPath"
    SHARE_DISK_DIR = "shareDiskDir"
    DSS_SSL_ENABLE = "dss_ssl_enable"

    # dcf
    ENABLE_DCF = "enable_dcf"
    DCF_CONFIG = "dcf_config"

    # device labes
    # om
    NAME = "name"
    AZNAME = "azName"
    AZPRIORITY = "azPriority"
    BACKIP1 = "backIp1"
    SSHIP1 = "sshIp1"
    DATA_NUM = "dataNum"
    DATA_PORT_BASE = "dataPortBase"
    DATA_NODE1 = "dataNode1"
    DATA_NODE1_SYNCNUM = "dataNode1_syncNum"
    SSH_PORT = "sshPort"

    # cm
    CMS_NUM = "cmsNum"
    CM_SERVER_PORT_BASE = "cmServerPortBase"
    CM_SERVER_LISTEN_IP1 = "cmServerListenIp1"
    CM_SERVER_HA_IP1 = "cmServerHaIp1"
    CM_SERVER_LEVEL = "cmServerlevel"
    CM_SERVER_RELATION = "cmServerRelation"
    CM_SERVER_PORT_STANDBY = "cmServerPortStandby"
    CM_DIR = "cmDir"

    # cascadeRole
    CASCADEROLE = "cascadeRole"

    CLUSTER_LABEL = [CLUSTER_NAME, NODENAMES, GAUSS_DB_APP_PATH, GAUSS_DB_LOG_PATH, GAUSS_MPP_DB_PATH,
                     GAUSS_DB_TOOL_PATH, CORE_PATH, BAKCIP1S]

    DSS_LABEL = [ENABLE_DSS, DSS_HOME, SS_DSS_VG_NAME, DSS_VG_INFO, VOTING_DISK_PATH,
                 SHARE_DISK_DIR, DSS_SSL_ENABLE]

    DCF_LABEL = [ENABLE_DCF, DCF_CONFIG]

    DEVICE_LABEL_SN = [NAME, AZNAME, AZPRIORITY, BACKIP1, SSHIP1, DATA_NUM, DATA_PORT_BASE,
                       DATA_NODE1, DATA_NODE1_SYNCNUM, CASCADEROLE, SSH_PORT]

    CM_LABEL_SN = [CMS_NUM, CM_SERVER_PORT_BASE, CM_SERVER_LISTEN_IP1, CM_SERVER_HA_IP1,
                   CM_SERVER_LEVEL, CM_SERVER_RELATION, CM_SERVER_PORT_STANDBY, CM_DIR]


def check_cluster_node_count(cluster_info):
    node_count = len(cluster_info.dbNodes)
    if node_count < 0 or node_count > 9:
        raise Exception("The cluster supports a maximum of one primary node and eight backup nodes")

class GenerateXml:

    def __init__(self):
        self.tree = None
        self.root = None
        self.cluster_label = ""
        self.device_list_label = ""
        self.device_label = ""
        self.hostip_list = []
        self.hostname_list = []
        self.hostip_str = ""
        self.hostname_str = ""
        self.cm_flag = False

    def create_init_label(self):
        root = ET.Element("ROOT")
        cluster = ET.Element("CLUSTER")
        device_list = ET.Element("DEVICELIST")
        root.append(cluster)
        root.append(device_list)
        self.cluster_label = cluster
        self.device_list_label = device_list
        self.tree = ET.ElementTree(root)
        self.root = self.tree.getroot()

    def update_cluster_node_info(self, cluster_info, new_host_info):
        """
        function: update cluster new node info
        input  : cluster_info new_host_info
        output : NA
        """
        if not new_host_info:
            return
        for hostname, hostip in new_host_info.items():
            new_node = copy.deepcopy(cluster_info.dbNodes[-1])
            new_node.name = hostname
            new_node.backIps[0] = hostip
            new_node.sshIps[0] = hostip
            new_node.datanodes[0].instanceType = '1'
            cluster_info.dbNodes.append(new_node)

            # if has cm, need copy cmservers cmagents
            if self.cm_flag: 
                cm_server = new_node.cmservers[0]
                cm_server.hostname = hostname
                cm_server.listenIps[0] = hostip
                cm_server.haIps[0] = hostip
        
        check_cluster_node_count(cluster_info)

    def do_generate_xml(self, cluster_info, new_host_info=None):
        """
        function: Generate XML based on cluster
        input  : cluster_info new_host_info
        output : NA
        """
        # Add node information to the existing cluster information
        self.update_cluster_node_info(cluster_info, new_host_info)
        # if has cm
        self.has_cm(cluster_info)
        # get cluster info
        cluster_info_dict = self.get_cluster_info(cluster_info)
        # generate xml
        self.create_init_label()
        self.set_cluster_info(cluster_info_dict)
        self.set_device_info(cluster_info_dict)
        # output
        self.output_xml()

    def get_cluster_info(self, cluster_info):
        cluster_info_dict = {}
        # cluster
        self.gen_cluster(cluster_info, cluster_info_dict)
        # ddes
        self.gen_ddes(cluster_info, cluster_info_dict)
        # dcf 
        self.gen_dcf(cluster_info, cluster_info_dict)
        # om
        self.gen_om(cluster_info, cluster_info_dict)
        # cm
        self.gen_cm(cluster_info, cluster_info_dict)
        # cascadeRole
        self.gen_cascade_role(cluster_info, cluster_info_dict)

        cluster_info_dict[ClusterKey.NODENAMES] = self.hostname_str
        cluster_info_dict[ClusterKey.BAKCIP1S] = self.hostip_str

        return cluster_info_dict

    def gen_cluster(self, cluster_info, cluster_info_dict):
        gp_home = EnvUtil.getEnv('GPHOME')
        tmp_path = EnvUtil.getEnv("PGHOST")
        core_path = EnvUtil.getEnv("COREPATH")
        cluster_info_dict[ClusterKey.CLUSTER_NAME] = cluster_info.name
        cluster_info_dict[ClusterKey.GAUSS_DB_APP_PATH] = cluster_info.appPath
        cluster_info_dict[ClusterKey.GAUSS_DB_LOG_PATH] = cluster_info.logPath
        cluster_info_dict[ClusterKey.GAUSS_MPP_DB_PATH] = tmp_path
        cluster_info_dict[ClusterKey.GAUSS_DB_TOOL_PATH] = gp_home
        cluster_info_dict[ClusterKey.CORE_PATH] = core_path

    def gen_ddes(self, cluster_info, cluster_info_dict):
        username = pwd.getpwuid(os.getuid()).pw_name
        dss_home = EnvUtil.get_dss_home(username)
        dss_ssl = EnvUtil.get_dss_ssl_status(username)
        vg_name = EnvUtil.getEnv('VGNAME')
        if not dss_home:
            return

        cm_dir = cluster_info.dbNodes[0].cmDataDir
        cm_server_conf_file = os.path.normpath(os.path.join(cm_dir, 'cm_server/cm_server.conf'))
        vg_conf_file = os.path.normpath(os.path.join(dss_home, 'cfg', 'dss_vg_conf.ini'))

        voting_disk_path = ""
        share_disk_dir = ""
        if not os.path.exists(cm_server_conf_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % cm_server_conf_file)
        
        cmd = "cat %s | grep 'voting_disk_path' | awk -F '=' '{print $2}'" % cm_server_conf_file
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0 or not output.strip():
            raise Exception("Failed to get voting_disk_path from cm_server.cfg. Error: %s" % output)
        voting_disk_path = output.strip()

        cmd = "cat %s | grep 'share_disk_path' | awk -F '=' '{print $2}'" % cm_server_conf_file
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0 or not output.strip():
            raise Exception("Failed to get share_disk_path from cm_server.cfg. Error: %s" % output)
        share_disk_dir = output.strip()            

        dss_vg_info = ""
        if not os.path.exists(vg_conf_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % vg_conf_file)
        with open(vg_conf_file, 'r') as fd:
            lines = fd.readlines()
            lines = [line.strip() for line in lines]
            dss_vg_info = ",".join(lines)

        cluster_info_dict[ClusterKey.ENABLE_DSS] = "on"
        cluster_info_dict[ClusterKey.DSS_HOME] = dss_home
        cluster_info_dict[ClusterKey.SS_DSS_VG_NAME] = vg_name
        cluster_info_dict[ClusterKey.DSS_VG_INFO] = dss_vg_info
        cluster_info_dict[ClusterKey.VOTING_DISK_PATH] = voting_disk_path
        cluster_info_dict[ClusterKey.SHARE_DISK_DIR] = share_disk_dir
        cluster_info_dict[ClusterKey.DSS_SSL_ENABLE] = dss_ssl

    def gen_dcf(self, cluster_info, cluster_info_dict):
        if cluster_info.enable_dcf == "on":
            cluster_info_dict[ClusterKey.ENABLE_DCF] = cluster_info.enable_dcf
            cluster_info_dict[ClusterKey.DCF_CONFIG] = cluster_info.dcf_config

    def get_datanodes1_value(self, cluster_info):
        datanode = cluster_info.dbNodes[0].datanodes[0].datadir
        datanode1_list = []
        datanode1_list.append(datanode)
        for node in cluster_info.dbNodes:
            if node.datanodes[0].instanceType == 0:
                continue
            datanode1_list.append(node.name)
            datanode1_list.append(datanode)
        datanode1 = ",".join(datanode1_list)
        return datanode1

    def gen_om(self, cluster_info, cluster_info_dict):
        hostname_list = []
        hostip_list = []
        datanode1 = self.get_datanodes1_value(cluster_info)
        for node in cluster_info.dbNodes:
            hostname = node.name
            host_ip = node.backIps[0]
            hostname_list.append(hostname)
            hostip_list.append(host_ip)

            ssh_port = SshdConfig.get_ssh_port_for_host(hostname) or SshdConfig.DEFAULT_SSH_PORT
            instance_type = node.datanodes[0].instanceType
            cluster_info_dict[hostname] = {
                ClusterKey.NAME: hostname,
                ClusterKey.AZNAME: node.azName,
                ClusterKey.AZPRIORITY: str(node.azPriority),
                ClusterKey.BACKIP1: host_ip,
                ClusterKey.SSHIP1: node.sshIps[0],
                ClusterKey.SSH_PORT: str(ssh_port),
                "instance_type": str(instance_type)
            }

            if instance_type == 0:
                cluster_info_dict[hostname].update({
                    ClusterKey.DATA_NUM: "1",
                    ClusterKey.DATA_PORT_BASE: str(node.datanodes[0].port),
                    ClusterKey.DATA_NODE1: datanode1,
                    ClusterKey.DATA_NODE1_SYNCNUM: "0"
                })

        self.hostname_list = hostname_list
        self.hostip_list = hostip_list
        self.hostname_str = ",".join(self.hostname_list)
        self.hostip_str = ",".join(self.hostip_list)

    def has_cm(self, cluster_info):
        if cluster_info.cmscount > 0:
            self.cm_flag = True

    def gen_cm(self, cluster_info, cluster_info_dict):
        if self.cm_flag:
            for node in cluster_info.dbNodes:
                hostname = node.name
                port = node.cmservers[0].port
                cm_dir = node.cmDataDir
                instance_type = node.cmservers[0].instanceType
                if instance_type == 0:
                    cluster_info_dict[hostname]['cm'] = {
                        "cm_instance_type": instance_type,
                        ClusterKey.CMS_NUM: "1",
                        ClusterKey.CM_SERVER_PORT_BASE: str(port),
                        ClusterKey.CM_SERVER_LISTEN_IP1: self.hostip_str,
                        ClusterKey.CM_SERVER_HA_IP1: self.hostip_str,
                        ClusterKey.CM_SERVER_LEVEL: "1",
                        ClusterKey.CM_SERVER_RELATION: self.hostname_str,
                        ClusterKey.CM_DIR: cm_dir
                    }
                else:
                    cluster_info_dict[hostname]['cm'] = {
                        "cm_instance_type": instance_type,
                        ClusterKey.CM_SERVER_PORT_STANDBY: str(port),
                        ClusterKey.CM_DIR: cm_dir
                    }

    def gen_cascade_role(self, cluster_info, cluster_info_dict):
        for node in cluster_info.dbNodes:
            hostname = node.name
            cascade_role = node.cascadeRole
            if node.cascadeRole == "on":
                cluster_info_dict[hostname].update({
                    ClusterKey.CASCADEROLE: cascade_role
                })

    def set_dict_key(self, dict_obj, key, value):
        if key in dict_obj.keys():
            dict_obj.update({key: value})
        else:
            dict_obj[key] = value

    def set_cluster_info(self, cluster_info_dict):
        self.set_cluster_common_info(cluster_info_dict, ClusterKey.CLUSTER_LABEL)
        self.set_cluster_common_info(cluster_info_dict, ClusterKey.DSS_LABEL)
        self.set_cluster_common_info(cluster_info_dict, ClusterKey.DCF_LABEL)

    def set_cluster_common_info(self, cluster_info_dict, keys):
        for label in keys:
            if label in cluster_info_dict.keys() and cluster_info_dict.get(label):
                key = label
                value = cluster_info_dict.get(label)
                param = ET.Element("PARAM", name=key, value=value)
                self.cluster_label.append(param)

    def set_device_info(self, cluster_info_dict):
        for hostname in self.hostname_list:
            parent = ET.SubElement(self.device_list_label, "DEVICE", sn=hostname)
            for label in ClusterKey.DEVICE_LABEL_SN:
                if label in cluster_info_dict[hostname].keys() and cluster_info_dict[hostname].get(label):
                    key = label
                    value = cluster_info_dict[hostname].get(label)
                    ET.SubElement(parent, "PARAM", name=key, value=value)
            
            if not self.cm_flag:
                continue
            for label in ClusterKey.CM_LABEL_SN:
                if label in cluster_info_dict[hostname]['cm'].keys() and cluster_info_dict[hostname]['cm'].get(label):
                    key = label
                    value = cluster_info_dict[hostname]['cm'].get(label)
                    ET.SubElement(parent, "PARAM", name=key, value=value)

    def output_xml(self):
        user = pwd.getpwuid(os.getuid()).pw_name
        current_date = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        target_xml = "/home/%s/xml_output_%s.xml" % (user, str(current_date))
        # convert ElementTree tag tree to string
        xml_str = ET.tostring(self.root, encoding="UTF-8", method="xml")
        dom = minidom.parseString(xml_str)
        formatted_xml = dom.toprettyxml(encoding="UTF-8")
        FileUtil.createFileInSafeMode(target_xml)
        with open(target_xml, "wb") as f:
            f.write(formatted_xml)
        xml_tmp_file = "/home/%s/tmp_generate_xml" % user
        cmd = "echo '%s' > '%s'" % (target_xml, xml_tmp_file)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            raise Exception("Failed to write xml tmp file: %s" % output)