import os
import sys
import socket
import subprocess

from gspylib.common.GaussLog import GaussLog
from gspylib.common.Common import DefaultValue
from base_utils.os.net_util import NetUtil
from base_utils.template.xml_constant import XmlConstant


def check_illegal_character(user_put):
    for rac in DefaultValue.PATH_CHECK_LIST:
        flag = user_put.find(rac)
        if flag >= 0:
            GaussLog.printMessage("%s %s" % (user_put, XmlConstant.RESOURCE_DATA.get('invalid_character')))
            return False
    return True


def check_port(port, action='', database_port=''):
    if not str(port).isdigit():
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_num'))
        return False
    if int(port) > 65535 or int(port) < 1024:
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_port'))
        return False

    if action == 'cm':
        if port == database_port:
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('cm_port_repeat'))
            return False
        if int(port) in range(int(database_port), int(database_port) + 11):
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('cm_port_beyond'))
            return False
    return True


def check_database_dir(database_dir):
    # check illegal character
    if not check_illegal_character(database_dir):
        return False

    # check isabs path
    if not os.path.isabs(database_dir):
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_abs_dir'))
        return False
    
    database_dir = os.path.normpath(database_dir)

    # check path exists
    if os.path.exists(database_dir):
        if not os.path.isdir(database_dir):
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_dir'))
            return False

        # check permission
        if not os.access(database_dir, os.R_OK | os.W_OK):
            GaussLog.printMessage("%s %s" % (XmlConstant.RESOURCE_DATA.get('not_permission'), database_dir))
            return False
    else:
        cmd = "mkdir -p %s && rm -rf %s" % (database_dir, database_dir)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            GaussLog.printMessage("%s %s" % (database_dir, XmlConstant.RESOURCE_DATA.get('mkdir_dir_failed')))
            return False
    return True


def check_ip_hostname_valid(ip, hostname):
    if not NetUtil.isIpValid(ip):
        GaussLog.printMessage("%s %s" % (ip, XmlConstant.RESOURCE_DATA.get('invalid_ip')))
        return False
    if not check_illegal_character(ip):
        return False
    if not check_illegal_character(hostname):
        return False
    return True


def check_ip_node_count():
    if len(XmlConstant.PRI_STANDBY_IP.keys()) != XmlConstant.PRI_STANDBY_COUNT:
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('ip_hostname_not_match'))
        return False
    return True


def get_ip_hostname(user_input):
    pri_standby_ip = {}
    ip_lists = []
    hostname_lists = []
    ip_hostname = user_input.split(";")
    # ip_hostname remove empty elements
    ip_hostname = [tmp for tmp in ip_hostname if tmp]
    if (len(ip_hostname) != XmlConstant.PRI_STANDBY_COUNT):
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('ip_hostname_not_match'))
        return False
    for tmp in ip_hostname:
        if len(tmp.strip().split()) != 2:
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('ip_hostname_not_match'))
            return False
        ip = str(tmp.strip().split()[0])
        hostname = str(tmp.strip().split()[1])
        if not check_ip_hostname_valid(ip, hostname):
            return False
        pri_standby_ip[ip] = hostname
        ip_lists.append(ip)
        hostname_lists.append(hostname)

    XmlConstant.PRI_STANDBY_IP = pri_standby_ip
    XmlConstant.IP_LISTS = ip_lists
    XmlConstant.HOSTNAME_LISTS = hostname_lists
    if not check_ip_node_count():
        return False
    return True


class TemplateStatus:

    def work(self):
        pass


def check_xml_isabs(xml_dir):
    if os.path.isabs(xml_dir):
        target_xml = xml_dir
    else:
        target_xml = os.path.join(XmlConstant.get_current_dir(), xml_dir)
    return os.path.normpath(target_xml)


def check_xml_file_permission(target_xml):
    if os.path.exists(target_xml):
        if not os.path.isfile(target_xml):
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_xml_dir'))
            return False

        # check permission
        if not os.access(target_xml, os.R_OK | os.W_OK):
            GaussLog.printMessage("%s %s" % (XmlConstant.RESOURCE_DATA.get('not_permission'), target_xml))
            return False
    else:
        cmd = "touch %s && rm -rf %s" % (target_xml, target_xml)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            GaussLog.printMessage("%s %s" % (target_xml, XmlConstant.RESOURCE_DATA.get('mkdir_file_failed')))
            return False
    return True


def check_xml_dir_repeat(target_xml):
    cur_dir = XmlConstant.get_current_dir()
    files = []
    for tmp in XmlConstant.KEEP_FILES:
        file = os.path.normpath(os.path.join(cur_dir, tmp))
        files.append(file)
    if target_xml in files:
        GaussLog.printMessage("%s %s" % (XmlConstant.RESOURCE_DATA.get('invalid_xml_path'), target_xml))
        return False
    return True


def check_input_xml_info(xml_dir):
    # check illegal
    if not check_illegal_character(xml_dir):
        return False
    # get xml file's abs dir
    XmlConstant.TARGET_XML = check_xml_isabs(xml_dir)
    if not check_xml_file_permission(XmlConstant.TARGET_XML):
        return False
    if not check_xml_dir_repeat(XmlConstant.TARGET_XML):
        return False
    return True


class XmlStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            # 用户输入back回退到上个流程
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_xml_path')).strip()
            if user_input.lower() in ('back', 'b'):
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('not_back'))
                return XmlStatus()
            if not user_input:
                tmp_dir = os.path.join(XmlConstant.get_current_dir(), 'cluster.xml')
                if os.path.exists(tmp_dir):
                    os.remove(tmp_dir)
                XmlConstant.TARGET_XML = tmp_dir
                if not check_input_xml_info(tmp_dir):
                    continue
                return DatabaseInstallStatus()
            if not check_input_xml_info(user_input):
                continue
            return DatabaseInstallStatus()


class DatabaseInstallStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_database_path')).strip()
            if user_input.lower() in ('back', 'b'):
                return XmlStatus()
            if not user_input:
                XmlConstant.OPENGAUSS_INSTALL_DIR = XmlConstant.DATABASE_INSTALL_DIR
                if not check_database_dir(XmlConstant.OPENGAUSS_INSTALL_DIR):
                    continue
                return DataPortStatus()
            if not check_database_dir(user_input):
                continue
            XmlConstant.OPENGAUSS_INSTALL_DIR = os.path.normpath(user_input)
            return DataPortStatus()


class DataPortStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_database_port')).strip()
            if user_input.lower() in ('back', 'b'):
                return DatabaseInstallStatus()
            if not user_input:
                XmlConstant.DATABASE_PORT = XmlConstant.DEFAULT_DATABASE_PORT
                return PriStandbyStatus()
            if not check_port(user_input):
                continue
            XmlConstant.DATABASE_PORT = user_input
            return PriStandbyStatus()


class PriStandbyStatus(TemplateStatus):

    def work(self):
        XmlConstant.IS_PRI_STANDBY = False
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('choose_pri_standby'))
        XmlConstant.select_option(XmlConstant.RESOURCE_DATA.get('deploy_pri_standby'),
                                  XmlConstant.RESOURCE_DATA.get('deploy_single'))

        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_pri_standby')).strip()
            if user_input.lower() in ('back', 'b'):
                return DataPortStatus()
            if not user_input:
                XmlConstant.IS_PRI_STANDBY = True
                return DdesStatus()
            if not user_input.isdigit():
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_num'))
                continue
            if user_input == "1":
                XmlConstant.IS_PRI_STANDBY = True
                return DdesStatus()
            elif user_input == "2":
                XmlConstant.IS_PRI_STANDBY = False
                return PriStandbyCountStatus()
            else:
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_character'))
                continue


class DdesStatus(TemplateStatus):

    def work(self):
        XmlConstant.IS_DDES = False
        XmlConstant.IS_CM = False
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('choose_ddes'))
        XmlConstant.select_option(XmlConstant.RESOURCE_DATA.get('not_deploy'), XmlConstant.RESOURCE_DATA.get('deploy'))

        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_ddes')).strip()
            if user_input.lower() in ('back', 'b'):
                return PriStandbyStatus()
            if not user_input:
                XmlConstant.IS_DDES = False
                return CmStatus()
            if not user_input.isdigit():
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_num'))
                continue
            if user_input == "1":
                XmlConstant.IS_DDES = False
                return CmStatus()
            elif user_input == "2":
                XmlConstant.IS_DDES = True
                XmlConstant.IS_CM = True
                return DdesDssHomeStatus()
            else:
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_character'))
                continue


class DdesDssHomeStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('intput_dss_home')).strip()
            if user_input.lower() in ('back', 'b'):
                return DdesStatus()
            if not user_input:
                XmlConstant.DDES_INFO['dss_home'] = XmlConstant.DSS_HOME_DIR
                if not check_database_dir(XmlConstant.DDES_INFO['dss_home']):
                    continue
                return DdesDssVgNameStatus()
            if not check_database_dir(user_input):
                continue
            XmlConstant.DDES_INFO['dss_home'] = os.path.normpath(user_input)
            return DdesDssVgNameStatus()


class DdesDssVgNameStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('intput_ss_dss_vg_name')).strip()
            if user_input.lower() in ('back', 'b'):
                return DdesDssHomeStatus()
            if not user_input:
                XmlConstant.DDES_INFO['ss_dss_vg_name'] = XmlConstant.DSS_VG_NAME_DIR
                return DdesDssVgInfoStatus()
            XmlConstant.DDES_INFO['ss_dss_vg_name'] = user_input
            return DdesDssVgInfoStatus()


class DdesDssVgInfoStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_dss_vg_info')).strip()
            if user_input.lower() in ('back', 'b'):
                return DdesDssVgNameStatus()
            if not user_input:
                XmlConstant.DDES_INFO['dss_vg_info'] = XmlConstant.DSS_VG_INFO_DIR
                return DdesVotingStatus()
            XmlConstant.DDES_INFO['dss_vg_info'] = user_input
            return DdesVotingStatus()


class DdesVotingStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_voting_disk_path')).strip()
            if user_input.lower() in ('back', 'b'):
                return DdesDssVgInfoStatus()
            if not user_input:
                XmlConstant.DDES_INFO['votingDiskPath'] = XmlConstant.VOTING_DIR
                return DdesShareDiskStatus()
            XmlConstant.DDES_INFO['votingDiskPath'] = user_input
            return DdesShareDiskStatus()


class DdesShareDiskStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_share_disk_dir')).strip()
            if user_input.lower() in ('back', 'b'):
                return DdesVotingStatus()
            if not user_input:
                XmlConstant.DDES_INFO['shareDiskDir'] = XmlConstant.SHAREDISK_DIR
                return CmServerPortStatus()
            XmlConstant.DDES_INFO['shareDiskDir'] = user_input
            return CmServerPortStatus()


class CmStatus(TemplateStatus):

    def work(self):
        XmlConstant.IS_CM = False
        GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('choose_cm'))
        XmlConstant.select_option(XmlConstant.RESOURCE_DATA.get('deploy'), XmlConstant.RESOURCE_DATA.get('not_deploy'))

        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_cm')).strip()
            if user_input.lower() in ('back', 'b'):
                if XmlConstant.IS_DDES:
                    return DdesShareDiskStatus()
                else:
                    return DdesStatus()
            if not user_input:
                XmlConstant.IS_CM = True
                return CmServerPortStatus()
            if not user_input.isdigit():
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_num'))
                continue
            if user_input == "1":
                XmlConstant.IS_CM = True
                return CmServerPortStatus()
            elif user_input == "2":
                XmlConstant.IS_CM = False
                return PriStandbyCountStatus()
            else:
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_character'))
                continue


class CmServerPortStatus(TemplateStatus):

    def work(self):
        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('cm_port')).strip()
            if user_input.lower() in ('back', 'b'):
                if XmlConstant.IS_DDES:
                    return DdesShareDiskStatus()
                return CmStatus()
            if not user_input:
                XmlConstant.CM_SERVER_PORT = XmlConstant.DEFAULT_CM_SERVER_PORT
                if not check_port(XmlConstant.CM_SERVER_PORT, 'cm', XmlConstant.DATABASE_PORT):
                    continue
                return PriStandbyCountStatus()
            if not check_port(user_input, 'cm', XmlConstant.DATABASE_PORT):
                continue
            XmlConstant.CM_SERVER_PORT = user_input
            return PriStandbyCountStatus()


class PriStandbyCountStatus(TemplateStatus):

    def work(self):
        if not XmlConstant.IS_PRI_STANDBY:
            XmlConstant.PRI_STANDBY_COUNT = 1
            return PriStandbyIpStatus()

        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('max_nodes')).strip()
            if user_input.lower() in ('back', 'b'):
                if XmlConstant.IS_DDES or XmlConstant.IS_CM:
                    return CmServerPortStatus()
                if not XmlConstant.IS_CM:
                    return CmStatus()
                return PriStandbyStatus()
            if not user_input:
                XmlConstant.PRI_STANDBY_COUNT = 3
                return PriStandbyIpStatus()
            if not user_input.isdigit():
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_num'))
                continue
            if 2 <= int(user_input) <= 9:
                XmlConstant.PRI_STANDBY_COUNT = int(user_input)
                return PriStandbyIpStatus()

            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('invalid_character'))
            continue


def get_localhost_name():
    return socket.gethostname()


def get_localhost_ip():
    addr_info = socket.getaddrinfo(get_localhost_name(), None)
    for info in addr_info:
        # 从地址信息中提取 IPv4 或 IPv6 地址
        host_ip = info[NetUtil.ADDRESS_FAMILY_INDEX][NetUtil.IP_ADDRESS_INDEX]
    return host_ip


class PriStandbyIpStatus(TemplateStatus):

    def work(self):
        ip_lists = []
        hostname_lists = []
        if not XmlConstant.IS_PRI_STANDBY:
            hostname_lists.append(get_localhost_name())
            ip_lists.append(get_localhost_ip())
            XmlConstant.IP_LISTS = ip_lists
            XmlConstant.HOSTNAME_LISTS = hostname_lists
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('finish'))
            return

        for i in range(XmlConstant.TRIES):
            if i == 3:
                sys.exit(0)
            user_input = input(XmlConstant.RESOURCE_DATA.get('input_ip_hostname')).strip()
            if user_input.lower() in ('back', 'b'):
                return PriStandbyCountStatus()
            if not user_input:
                GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('ip_hostname_empty'))
                continue
            if not get_ip_hostname(user_input):
                continue
            GaussLog.printMessage(XmlConstant.RESOURCE_DATA.get('finish'))
            return
