#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
Copyright (c): 2021, Huawei Tech. Co., Ltd.
Description  : A utility to uninstall gds or roach_client.
"""
try:
    import os
    import sys
    import getopt
    import re
    import pwd

    sys.path.append(os.path.split(os.path.realpath(__file__))[0] + "/../../script/")
    from gspylib.common.ErrorCode import ErrorCode
    from gspylib.common.GaussLog import GaussLog
    from gspylib.os.gsfile import g_file
    from common_tools import GDSUtils
except ImportError as e:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(e))

# APP_NAME: gds or roach_client
APP_NAME = __file__[(0 if __file__.rfind("/") == -1 else __file__.rfind("/") + 1):__file__.rfind("_")]

WORK_PATH = os.path.dirname(os.path.realpath(__file__))

# global variables
uninstall_hosts = []
gds_user = ''
user_group = ''
root_passwd = ''
gds_user_passwd = ''
do_uninstall_user = False
do_uninstall_group = False
install_dir = {}


def usage():
    """
Usage:
    %s_uninstall --host [/path/to/hostfile | ipaddr1,ipaddr2...] –U gds_user [--delete-user | --delete-user-and-group]
Common options:
    --host          IP addresses of nodes where %s to be uninstalled.
                    Two styles of setting are supported:
                    (1) A String consisting of IP addresses separated with commas, like 192.168.1.100,192.168.1.101.
                    (2) An absolute path to the ip list file, like /opt/sample_user/host.list,\
                     and the content format is as follows:
                        192.168.1.100
                        192.168.1.101
    -U              The OS user of %s.
    --delete-user   Delete %s user, and the user name is from '-U'.
    --delete-user-and-group
                    Delete user and its group, the user name is from '-U'.\
                     Delete its group only if the group has this user only.
    -V              Show version information.
    -h|--help       Show help information.
    """
    print(usage.__doc__ % ((APP_NAME,) * 4))


def parse_command_line():
    """
    function: Check parameter from command line.
    """
    global gds_user
    global uninstall_hosts
    global do_uninstall_user
    global do_uninstall_group

    try:
        opts, args = getopt.getopt(sys.argv[1:], "VhU:", ["host=", "help", "delete-user", "delete-user-and-group"])

        if (len(args) > 0):
            GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50000"] % str(args[0]))

        for (key, value) in opts:
            if key in ("-h", "--help"):
                usage()
                sys.exit(0)
            elif key == "-U":
                gds_user = value
            elif key == "-V":
                GDSUtils.get_version_info(APP_NAME)
                sys.exit(0)
            elif key == "--host":
                uninstall_hosts = GDSUtils.get_hosts_from_param(value)
            elif key == "--delete-user":
                do_uninstall_user = True
            elif key == "--delete-user-and-group":
                do_uninstall_user = True
                do_uninstall_group = True
            else:
                GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50000"] % key)
    except Exception as e:
        usage()
        GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50015"] % str(e))


def check_parameters():
    """
    function: check -U parameter, namely gds_user and gds_user_group.
    """
    global user_group

    if gds_user == "":
        GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50001"] % 'U' + ".")
    if ":" in gds_user:
        GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % 'U')

    # check group info
    if gds_user == "root":
        GaussLog.exitWithError("[GAUSS-50301] : The user/group cannot be a root user/group.")

    # check if gds_user exists and if it is a super user
    try:
        if pwd.getpwnam(gds_user).pw_uid == 0:
            GaussLog.exitWithError("[GAUSS-50301] : The user/group cannot be a root user/group.")
    except Exception:
        # user does not exists.
        GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50300"] % gds_user)


def get_and_verify_password():
    """
    function: get and verify password for root/gds_user.
    """
    global root_passwd
    global gds_user_passwd

    try:
        # get root password
        root_passwd = GDSUtils.get_user_password("root")
        # check the input password
        root_passwd = GDSUtils.two_more_chances_for_passwd(uninstall_hosts, "root", root_passwd)

        # get gds_user password
        gds_user_passwd = GDSUtils.get_user_password("%s user [%s]" % (APP_NAME, gds_user))
        # check the input password
        gds_user_passwd = GDSUtils.two_more_chances_for_passwd(uninstall_hosts, gds_user, gds_user_passwd)
    except Exception as e:
        if str(e).find("[GAUSS-") != -1:
            raise e
        raise Exception(ErrorCode.GAUSS_535["GAUSS_53514"] % str(e))


def check_exec_result(exec_result):
    """
    function: check parallelly execution result during uninstallation, when encounting error messages then throw.
    """
    if not exec_result:
        return
    raise_str = ""
    for key in exec_result:
        if exec_result[key]:
            raise_str += "On host %s: %s" % (key, exec_result[key]) + "\n"
    if raise_str:
        raise Exception(raise_str)


def stop_service_on_one_node(client, ip):
    """
    function: stop app processes on a node with 'ip' address.
    """
    cmd = "ps ux | grep -v grep | grep -w %s" % APP_NAME
    status, output = GDSUtils.paramiko_cmd_raw(client, cmd)
    if not status:
        GaussLog.exitWithError(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " With output: " + output.strip())
    cmd_stop = ""
    for line in output.strip().splitlines():
        match_obj = re.search(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:(\d{1,5})', line)
        if match_obj:
            port = match_obj.group(1)
            cmd_stop += "%s_ctl stop --host %s -p %s;" % (APP_NAME, ip, port)
    if cmd_stop:
        GDSUtils.paramiko_cmd_raw(client, cmd_stop, True)


def get_cmd_of_clean_install_dir(ip):
    """
    function: return command to delete install dir.
    """
    return g_file.SHELL_CMD_DICT["deleteDir"] % (install_dir[ip], install_dir[ip])


def get_cmd_of_stop_cron_service():
    """
    function: get command of removing crontab right of gds_user
    Note: Is it possible that on some platfroms the config file is '/etc/cron.d/cron.allow'?
    """
    cron_allow_file = "/etc/cron.allow"
    pattern = "^\\s*%s\\s*$" % gds_user
    cmd = "sed -i '/%s/d' %s" % (pattern, cron_allow_file)
    return cmd


def get_cmd_of_clean_user_profile():
    """
    function: clean user profile for execution, including:
                GDS_INSTALL_DIR
                PYTHONLIB
                LD_LIBRARY_PATH
                PATH
    """
    user_profile = GDSUtils.EXEC_ENV_FILE
    env_profile = "\$%s/bin/%s_env" % (GDSUtils.ENV_NAME_GDS_INSTALL_DIR, APP_NAME)

    # In case the .bashrc does not exist, we use '&&' to link two command segments.
    cmd = 'ls %s && (' % user_profile
    # delete $GDS_INSTALL_DIR in .bashrc
    cmd += 'sed -i "/^\\s*export\\s*%s=.*$/d" %s; ' % (GDSUtils.ENV_NAME_GDS_INSTALL_DIR, user_profile)
    # delete 'source xxx_env' in .bashrc
    cmd += 'sed -i "/^\\s*source\\s*%s$/d" %s; ' % (env_profile.replace('/', '\/'), user_profile)
    cmd += "cat %s)" % user_profile
    return cmd


def do_preuninstall_on_one_node(ip):
    """
    fucntion: pre-uninstall app deployed on a node, serveral steps will go:
              (1) kill processes use 'xxx_ctl'
              (2) rollback .bashrc to origin
    """
    GaussLog.printMessage('Starting to do preuninstall on node %s.' % ip)
    ret = {ip: ""}
    try:
        client = GDSUtils.connect_paramiko_ssh_host(ip, gds_user, gds_user_passwd)
        # get install dir
        status, output = GDSUtils.paramiko_cmd_raw(client, "echo $GDS_INSTALL_DIR")
        if not status or len(output.strip()) == 0:
            ret[ip] = "Cannot find install dir."
            return ret
        install_dir[ip] = output.strip()

        # kill processes
        stop_service_on_one_node(client, ip)

        GaussLog.printMessage('Successfully preuninstalled on node %s.' % ip)
        return ret
    except Exception as e:
        GaussLog.printMessage('Failed to do preuninstall on node %s.' % ip)
        ret[ip] = str(e)
        return ret
    finally:
        try:
            client.close()
        except Exception:
            pass


def preuninstall_parallelly():
    """
    function: do pre-uninstall parallelly
    """
    exec_result = GDSUtils.exec_command_remote_parallelly(do_preuninstall_on_one_node,
                                                          uninstall_hosts,
                                                          GDSUtils.TIMEOUT * 3)
    check_exec_result(exec_result)


def remove_allow_user(client):
    """
    function: Remove the specific user from 'AllowUsers' in /etc/ssh/sshd_config
    input : paramiko.SSHClient
    output: string
    """
    sshd_config = "/etc/ssh/sshd_config"
    res = ""
    try:
        cmd = "cat %s | grep -E '\\<AllowUsers\\>'" % sshd_config
        status, output = GDSUtils.paramiko_cmd_raw(client, cmd)
        # Not found, or there is an error.
        if not status:
            if output is None or len(output.strip()) == 0:
                res = "No 'AllowUsers' configuration found in %s" % sshd_config
            else:
                # Error occurred, but there is no need to report the exception.
                res = "Failed to get 'AllowUsers' from %s" % sshd_config
            return res

        allow_user_line_before = output.strip()
        user_list = allow_user_line_before.split()
        if gds_user not in user_list:
            return res
        user_list.remove(gds_user)
        allow_user_line_removed = ' '.join(user_list)
        cmd = "sed -i 's/%s/%s/g' %s" % (allow_user_line_before, allow_user_line_removed, sshd_config)
        status, output = GDSUtils.paramiko_cmd_raw(client, cmd)
        # Not found, or there is an error.
        if not status:
            res = "Failed to remove user '%s' from 'AllowUsers' in %s. Command: %s, Error: %s" \
                  % (gds_user, sshd_config, cmd, output)
        return res
    except Exception as e:
        res = "Failed to remove user '%s' from 'AllowUsers' in %s. Error: %s" % (gds_user, sshd_config, str(e))
        return res


def do_uninstall_on_one_node(ip):
    """
    function: uninstall app, including:
              (1) delete install dir
              (2) according to 'do_uninstall_user' and 'do_uninstall_group' to delete user and group.
    """
    GaussLog.printMessage('Starting to do uninstall on node %s.' % ip)
    is_successed = False
    try:
        client = GDSUtils.connect_paramiko_ssh_host(ip, "root", root_passwd)
        # remove install dir
        status, output = GDSUtils.paramiko_cmd_raw(client, get_cmd_of_clean_install_dir(ip))
        if not status:
            return {ip: ErrorCode.GAUSS_502['GAUSS_50209'] % install_dir[ip]}

        if not do_uninstall_user:
            is_successed = True
            return {ip: ''}

        # get gid/groupname of gds_user
        status, output = GDSUtils.paramiko_cmd_raw(client, "id -g %s; id -gn %s" % (gds_user, gds_user))
        if not status:
            return {ip: ErrorCode.GAUSS_503['GAUSS_50314'] % gds_user}
        gds_user_gid, gds_user_gname = tuple(output.strip().splitlines())
        # delete user
        status, output = GDSUtils.paramiko_cmd_raw(client, "userdel -rf %s >> /dev/null 2>&1" % gds_user)
        if not status:
            return {ip: ErrorCode.GAUSS_503['GAUSS_50314'] % gds_user}
        # remove sshd right
        remove_allow_user(client)

        # remove crontab right
        GDSUtils.paramiko_cmd_raw(client, get_cmd_of_stop_cron_service())

        # delete group
        if not do_uninstall_group:
            is_successed = True
            return {ip: ''}

        cmd = "cat /etc/passwd | awk -F : '{print $1 \" \" $4}' | grep -w '%s$'" % gds_user_gid
        status, output = GDSUtils.paramiko_cmd_raw(client, cmd)
        if not status:
            return {ip: ErrorCode.GAUSS_503['GAUSS_50313'] % gds_user_gname}
        if len(output.strip()) > 0:
            GaussLog.printMessage("Warning: There are other users in the group %s on %s, skip the step delete-group."
                                  % (gds_user_gname, ip))
            is_successed = True
            return {ip: ''}

        cmd = "groupdel %s" % gds_user_gname
        status, output = GDSUtils.paramiko_cmd_raw(client, cmd)
        if not status:
            if "group '%s' does not exist" % gds_user_gname not in output.strip():
                return {ip: ErrorCode.GAUSS_503['GAUSS_50313'] % gds_user_gname}

        is_successed = True
        return {ip: ''}
    except Exception as e:
        return {ip: str(e)}
    finally:
        try:
            client.close()
        except Exception:
            pass
        if is_successed:
            GaussLog.printMessage('Successfully uninstalled on node %s.' % ip)
        else:
            GaussLog.printMessage('Failed to do uninstall on node %s.' % ip)


def uninstall_parallelly():
    """
    function: uninstall parallelly on host list.
    """
    exec_result = GDSUtils.exec_command_remote_parallelly(do_uninstall_on_one_node,
                                                          uninstall_hosts,
                                                          GDSUtils.TIMEOUT * 3)
    check_exec_result(exec_result)


def clean_user_profile(ip):
    # clean user profile: .bashrc
    if do_uninstall_user:
        return {ip: ""}
    result = GDSUtils.exec_command_by_paramiko_on_one_node(ip,
                                                           get_cmd_of_clean_user_profile(),
                                                           gds_user,
                                                           gds_user_passwd)
    return result


def clean_user_profile_parallelly():
    """
    function: clean user profile parallelly
    """
    try:
        exec_result = GDSUtils.exec_command_remote_parallelly(clean_user_profile,
                                                              uninstall_hosts,
                                                              GDSUtils.TIMEOUT)
        check_exec_result(exec_result)
    except Exception as e:
        GaussLog.printMessage(str(e))


def output_hint(step):
    """
    function: print hint indicating begin or end.
    """
    # print hint when starting
    if step == "begin":
        GaussLog.printMessage("============Start to uninstall %s============" % APP_NAME)
        return

    # print hint when ending
    GaussLog.printMessage("============Successfully uninstalled============")


if __name__ == '__main__':
    """
    main function
    """
    # check if user is root
    if os.getuid() != 0:
        GaussLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50104"])
    try:
        GDSUtils.load_gds_env()
        parse_command_line()
        check_parameters()
        get_and_verify_password()
        output_hint("begin")
        preuninstall_parallelly()
        uninstall_parallelly()
        clean_user_profile_parallelly()
        output_hint("end")
    except Exception as e:
        GaussLog.printMessage(str(e))
