# -*- coding: utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import traceback
from IPy import IP

import utils.common.log as logger
from utils.security.crypt import decrypt
from utils.common.ssh_util import Ssh as sshc
from utils.business.vm_util import can_vm_pinged_to
from utils.common.ssh_util import Ssh


def ping_all_ip(bmc_info_list, gateway, inner_gateway=None):
    success_bmc_ip_lst = []
    if len(bmc_info_list) == 0:
        return success_bmc_ip_lst
    try:
        for bmc_info in bmc_info_list:
            fail_list = list()
            if not can_vm_pinged_to(bmc_info['manageIp']):
                logger.error("Failed to ping manageIp[%s] from HCC Turnkey node" % bmc_info['manageIp'])
                fail_list.append("(bmcIP:%s, manageIp:%s)" % (bmc_info.get('bmc_ip'), bmc_info.get('manageIp')))
                continue
            creuser = decrypt(bmc_info["creuser"]).split(",")
            try:
                ssh_client = Ssh.ssh_create_client(bmc_info.get("manageIp"), creuser[2], creuser[3])
            except Exception as e:
                logger.info(str(e))
                continue
            # 检查目标节点BMC IP是否一致
            if not check_bmc_ip(bmc_info):
                fail_list.append("(bmcIP:%s, manageIp:%s)" % (bmc_info.get('bmc_ip'), bmc_info.get('manageIp')))
                continue
            storage_and_iscsi_network_connectivity_check(bmc_info, bmc_info_list, fail_list, ssh_client)

            gateway_connectivity_check(bmc_info, fail_list, gateway, ssh_client)

            inner_network_connectivity_check(bmc_info, bmc_info_list, fail_list, ssh_client)

            inner_gateway_connectivity_check(bmc_info, fail_list, inner_gateway, ssh_client)

            Ssh.ssh_close(ssh_client)
            if fail_list:
                logger.error("Connectivity check fail, fail list:%s" % fail_list)
            else:
                success_bmc_ip_lst.append(bmc_info)
        return success_bmc_ip_lst
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
        raise e


def inner_gateway_connectivity_check(bmc_info, fail_list, inner_gateway, ssh_client):
    logger.info('inner gateway connectivity check')
    if inner_gateway:
        logger.info("from node[%s] ping inner gateway[%s]" % (bmc_info['manageIp'], inner_gateway))
        if not client_can_pinged_to(ssh_client, inner_gateway):
            node = "(bmcIP:%s, manageIp:%s, storageInnerIp:%s)" % (
                bmc_info.get('bmc_ip'), bmc_info.get('manageIp'), bmc_info.get('storageInnerIp'))
            err_msg = "Failed to ping storageInnerIP[%s] from node%s" % (inner_gateway, node)
            logger.error(err_msg)
            fail_list.append(node)


def inner_network_connectivity_check(bmc_info, bmc_info_list, fail_list, ssh_client):
    logger.info('inner network connectivity check')
    ping_ok_count = 0
    for sub_bmc_info in bmc_info_list:
        if ping_ok_count > 2:
            break
        storage_inner_ip = sub_bmc_info.get('storageInnerIp')
        if storage_inner_ip:
            # 如果待ping节点为本地节点，则ping网关
            if bmc_info['manageIp'] == sub_bmc_info['manageIp']:
                continue
            logger.info("from node [%s] ping inner storageIp[%s]" % (bmc_info['manageIp'],
                                                                     storage_inner_ip))
            if not client_can_pinged_to(ssh_client, storage_inner_ip):
                node = "(bmcIP:%s, manageIp:%s, storageInnerIp:%s)" \
                       % (bmc_info.get('bmc_ip'), bmc_info.get('manageIp'), bmc_info.get('storageInnerIp'))
                err_msg = "Failed to ping storageInnerIP[%s] from node%s" % (storage_inner_ip, node)
                logger.error(err_msg)
                fail_list.append(node)
            else:
                ping_ok_count += 1
        else:
            ping_ok_count += 1
            logger.info("node [%s] has not inner ip" % (sub_bmc_info['manageIp']))


def gateway_connectivity_check(bmc_info, fail_list, gateway, ssh_client):
    logger.info('gateway connectivity check')
    if gateway:
        logger.info("from node[%s] ping gateway[%s]" % (bmc_info['manageIp'], gateway))
        if not client_can_pinged_to(ssh_client, gateway):
            node = "(bmcIP:%s, manageIp:%s, storageIp:%s)" % (bmc_info.get('bmc_ip'), bmc_info.get('manageIp'), gateway)
            logger.error("Failed to ping storageIP[%s] from node%s" % (gateway, node))
            fail_list.append(node)


def storage_and_iscsi_network_connectivity_check(bmc_info, bmc_info_list, fail_list, ssh_client):
    logger.info('storage and iscsi network connectivity check')
    ping_ok_count = 0
    for sub_bmc_info in bmc_info_list:
        if ping_ok_count > 2:
            break
        if bmc_info['manageIp'] == sub_bmc_info['manageIp']:
            continue
        storage_ip = sub_bmc_info['storageIp']
        logger.info("from node [%s] ping storageIp[%s]" % (bmc_info['manageIp'], storage_ip))
        if not client_can_pinged_to(ssh_client, storage_ip):
            node = "(bmcIP:%s, manageIp:%s, storageIp:%s)" % (bmc_info.get('bmc_ip'),
                                                              bmc_info.get('manageIp'), storage_ip)
            err_msg = "Failed to ping storageIP[%s] from node%s" % (storage_ip, node)
            logger.error(err_msg)
            fail_list.append(node)
        iscsi_business_ip = sub_bmc_info.get("iscsi_business_plane_ip")
        if iscsi_business_ip:
            if not client_can_pinged_to(ssh_client, iscsi_business_ip):
                node = "(bmcIP:%s, manageIp:%s, iscsi_business_plane_ip:%s)" % (
                    bmc_info.get('bmc_ip'), bmc_info.get('manageIp'), iscsi_business_ip)
                err_msg = "Failed to ping iscsi_business_plane_ip[%s] from node%s" % (iscsi_business_ip,
                                                                                      node)
                logger.error(err_msg)
                fail_list.append(node)
        else:
            ping_ok_count += 1


def create_ssh_root_client(host_ip, user, passwd, root_pwd):
    try:
        ssh_client = sshc.ssh_create_client(host_ip, user, passwd)
    except Exception as e:
        create_err_msg = "Failed to connect host using user fsp, omip:%s, err:%s" % (host_ip, str(e))
        logger.error(create_err_msg)
        raise Exception(create_err_msg) from e
    try:
        create_ssh_root_client_realize(ssh_client, root_pwd)
    except Exception as e:
        switch_err_msg = "Failed to switch to root user, omip:%s, err:%s" % (host_ip, str(e))
        logger.error(switch_err_msg)
        if ssh_client:
            sshc.ssh_close(ssh_client)
        raise Exception(switch_err_msg) from e
    return ssh_client


def create_ssh_root_client_realize(ssh_client, root_pwd):
    cmd_res = sshc.ssh_send_command(ssh_client, 'su -', 'assword:', 20)
    logger.info("exe cmd[su root] result:%s" % (str(cmd_res)))

    cmd_res = sshc.ssh_send_command(ssh_client, root_pwd, '#', 20)
    logger.info("exe cmd[#] result:%s" % (str(cmd_res)))

    cmd_res = sshc.ssh_send_command(ssh_client, 'TMOUT=0', '#', 20)
    logger.info("exe cmd[TMOUT=0] result:%s" % (str(cmd_res)))


def get_bmc_ip(ssh_client):
    cmd_ipmi = "ipmitool lan print | awk -F: '/^IP Address\ +:/{print $2}'"
    std_out = sshc.ssh_send_command(ssh_client, cmd_ipmi, '#', 20)
    logger.info("Run clear cmd[%s], std_output:\n%s" % (cmd_ipmi, std_out))

    if str(std_out).find('Could not open device at /dev/ipmi0 or /dev/ipmi/0 or /dev/ipmidev/0') >= 0:
        cmd_restart_ipmi = "id;systemctl restart ipmi"
        std_out = sshc.ssh_send_command(ssh_client, cmd_restart_ipmi, '#', 20)
        logger.info("Run clear cmd[%s], std_output:\n%s" % (cmd_restart_ipmi, std_out))

        std_out = sshc.ssh_send_command(ssh_client, cmd_ipmi, '#', 20)
        logger.info("Run clear cmd[%s], std_output:\n%s" % (cmd_ipmi, std_out))

    if str(std_out).find('Could not open device at /dev/ipmi0 or /dev/ipmi/0 or /dev/ipmidev/0') >= 0:
        current_node_bmc_ip = None
    else:
        current_node_bmc_ip = std_out[-2].strip(' ').strip('\n')
    return current_node_bmc_ip


def check_bmc_ip(bmc_info):
    user_info = decrypt(bmc_info["creuser"]).split(",")
    ssh_client = create_ssh_root_client(bmc_info['manageIp'], user_info[2], user_info[3], user_info[1])
    current_node_bmc_ip = get_bmc_ip(ssh_client)
    target_node_bmc_ip = bmc_info["bmc_ip"]
    logger.info("This is the current node BMC IP[%s] and the target node BMC IP[%s]."
                % (current_node_bmc_ip, target_node_bmc_ip))
    if current_node_bmc_ip and current_node_bmc_ip != target_node_bmc_ip:
        err_msg = "The BMC IP[%s] of the current node is inconsistent with the BMC IP[%s] of the node" \
                  " in the installation system." % (current_node_bmc_ip, target_node_bmc_ip)
        logger.info(err_msg)
        return False
    return True


def client_can_pinged_to(client, host, normal=True):
    """
    return the host can pinged to or not
    :param client: PingSSHclient object
    :param host: the host ip of the vm
    :return:
    True ---can be pinged to
    False ---can not be pinged to
    """
    res = False
    try:
        ping_act = "ping6" if IP(host).version() == 6 else "ping"
    except Exception as e:
        logger.error('try ping host:%s failed:%s' % (host, str(e)))
        logger.error(traceback.format_exc())
        return res
    cmd = '%s -W 1 -c %d %s' % (ping_act, 3, host)
    try:
        res = ping_ip_cyclic(client, cmd, host, normal)
    except Exception as e:
        logger.error('try ping host:%s failed:%s' % (host, str(e)))
        logger.error(traceback.format_exc())
        return res
    return res


def ping_ip_cyclic(client, cmd, host, normal):
    if normal:
        ping_times = 3
    else:
        ping_times = 10
    for _ in range(0, ping_times):
        rsp = Ssh.ssh_exec_command_return(client, cmd)
        logger.info('localhost: %s, ping host:%s, response: %s' % (client.get("ip"), host, rsp))
        if str(rsp).find('ttl=') >= 0:
            logger.info("Succeed to ping host[%s]" % host)
            return True
        else:
            continue
    return False
