# -*- coding:utf-8 -*-
"""
Function: upgrade_bmc_firmware.py moudle. This moudle mainly involves the
Upgrade firmware, for example iBMC and CPLD function.
Copyright Information: xFusion Digital Technologies Co.,
 Ltd. All Rights Reserved © 2017-2021
"""
import os
import zipfile
import sys
from xml.dom import minidom
from scripts import common_function

INVALID_VALUE = None


def upgradebmcfirmware_init(parser, parser_dict):
    """
    Function Description:Initialize BMC firmware upgrade subcommands
    Parameter:parser object: action object
    parser_dict dict: dictionary format of subcommand and ArgumentParser
    Modify: 2019.4.28 The help information is optimized.
    Return Value: subcommand
    """
    sub_parser = parser.add_parser('upgradefw',
                                   help='''upgrade firmware''')

    sub_parser.add_argument('-F', dest='file',
                            required=False,
                            help='the local path and file name of upload file,'
                                 'file extensions should be ".hpm"')
    sub_parser.add_argument('-PC', dest='preserveConfig',
                            required=False,
                            help='This upgrade parameter is used to preserve the configuration')

    image_uri = {common_function.REQUIRED_FLAG: False,
                 common_function.HELP_INFO: 'path of the firmware upgrade'
                                            ' package that can be stored only'
                                            ' on remote file servers.'
                                            ' The path format is server'
                                            ' IP address/directory/package'
                                            ' name for interactive command,'
                                            ' protocol://username:password@ip/'
                                            'directory/filename for non-interactive command.'}
    args_pro = {common_function.REQUIRED_FLAG: False}
    args_acc = {common_function.REQUIRED_FLAG: False}
    common_function.unpack_remote_server_path(sub_parser, image_uri,
                                              args_pro, args_acc)

    parser_dict['upgradefw'] = sub_parser

    return 'upgradefw'


def check_parameter(parser, args):
    """
    Function Description:check CLI command
    Parameter:args object:CLI command
    parser object:subcommand ArgumentParser object
    """
    if args.file is not None:
        if not os.path.exists(args.file):
            parser.error('Failure: the file does not exist')
        if not os.path.isfile(args.file):
            parser.error('Failure: invalid file')
        if str(args.file).split('.')[-1] != 'hpm' and str(args.file).split('.')[-1] != 'zip':
            parser.error(
                'Failure: update firmware file type should be \'.hpm\' or \'.zip\'')
    else:
        if args.imageuri is None:
            parser.error('-i are required.')
        if args.imageuri is not None and common_function.verify_remote_server_info(args.imageuri) \
                and (args.protocol is not None or args.accessuser is not None):
            parser.error("-i contains all of the information of the remote server."
                         " you do not need -PRO -U any more.")


def is_interactive(args):
    """
    Function Description:check CLI command
    Parameter:args object:CLI command
    parser object:subcommand ArgumentParser object
    """
    if args.protocol is not None or args.accessuser is not None:
        return True
    return False


pro_dict = {
    'pro_hpm': "",
    'pro_version': "",
}


def get_info_from_zip(package_name):
    if str(package_name).split('.')[-1] == 'zip':
        # 解压缩到指定的文件目录
        extract_path = package_name.split('.')[0]
        # 需要解压的文件
        zip_obj = zipfile.ZipFile(package_name, 'r', zipfile.ZIP_DEFLATED)
        # 全部解压
        zip_obj.extractall(extract_path)
        zip_obj.close()
        for file_name in os.listdir(extract_path):
            if file_name.find('.hpm') != -1:
                pro_dict['pro_hpm'] = extract_path + "/" + file_name
            if file_name.find('version.xml') != -1:
                pro_dict['pro_version'] = extract_path + "/" + file_name
        return pro_dict.get("pro_hpm")
    else:
        return package_name


def get_attribute_value():
    if pro_dict.get("pro_version"):
        dom_tree = minidom.parse(pro_dict.get("pro_version").replace('\\', '/'))
        collection = dom_tree.documentElement
        active_mode = collection.getElementsByTagName("ActiveMode")[0].childNodes[0].data
        module = collection.getElementsByTagName("Module")[0].childNodes[0].data
        return {
            'active_mode': active_mode,
            'module': module
        }
    return {
        'active_mode': "",
        'module': ""
    }


message_dict = {
    "InsufficientPrivilege": "The current BMC user has insufficient permissions. Please use administrator user.",
    "FirmwareUpgrading": "The request failed. Check whether an upgrade task is in progress."
                         " Submit the request again later.",
    "FirmwareUpgradeError": "Collect BMC logs for troubleshooting.",
    "FileDownloadTaskOccupied": "Check whether a file transfer task is in progress."
                                " Submit the request again later.",
    "TaskLimitExceeded": "Check whether the number of concurrent tasks has reached "
                         "the upper limit. Submit the request again later.",
    "InternalError": "The request failed due to an internal service error."
                     "Collect BMC logs for troubleshooting.",
    "FileTransferErrorDesc": "Check whether the size of the firmware package "
                             "is larger than the available storage capacity. Check the firmware package size "
                             "and ensure that there is sufficient storage capacity."
                             "\n1. Log in to the BMC using SSH."
                             "\n2. Run the df -h command."
                             "\n3. Check whether the available space of the /tmp directory is sufficient.",
    "AuthorizationFailed": "The user name or password is incorrect, or your account is locked. "
                           "Log in to the BMC web page using the correct user name and password. "
                           "Use the tool to perform the upgrade after the login is successful."
}


def print_error_message(module, message_id):
    if message_id in message_dict.keys():
        common_function.print_info("Failure: %s Update Fail. " % module + message_dict.get(message_id))
    else:
        common_function.print_info("Failure: %s Update Timeout." % module)


def is_sys_psu_upgrade_success(sys_power_group, gpu_power_group, psu_message):
    # 判断系统电源是否升级成功
    if len(sys_power_group) == 0 and len(gpu_power_group):
        return False
    for sys_power in sys_power_group:
        tip = "%s firmware upgrade succeeded." % sys_power.replace('U', '')
        if tip not in psu_message:
            return False
    for gpu_power in gpu_power_group:
        gpu_tip = "%s firmware upgrade failed, the PSU model does not support" \
                  " the upgrade or does not match the upgrade file." % gpu_power.replace('U', '')
        if gpu_tip not in psu_message:
            return False
    return True


def is_gpu_psu_upgrade_success(sys_power_group, gpu_power_group, psu_message):
    # 判断GPU电源是否升级成功
    if len(sys_power_group) == 0 and len(gpu_power_group):
        return False
    for gpu_power in gpu_power_group:
        tip = "%s firmware upgrade succeeded." % gpu_power.replace('U', '')
        if tip not in psu_message:
            return False
    for sys_power in sys_power_group:
        sys_tip = "%s firmware upgrade failed, the PSU model does not support" \
                  " the upgrade or does not match the upgrade file." % sys_power.replace('U', '')
        if sys_tip not in psu_message:
            return False
    return True


def is_psu_upgrade_successful(task_resp, client, module):
    psu_message = task_resp['resource']['Messages']['Message']
    # 获取系统电源和GPU电源
    slotid = client.get_slotid()
    if slotid is None:
        return False
    psu_resp = client.get_resource("/redfish/v1/Chassis/%s/Power" % slotid)
    manufacturer = list(psu_resp.get("resource", {}).get("Oem", {}).keys())[0]
    sys_power_group = []
    gpu_power_group = []
    for psu_detail in psu_resp.get("resource", {}).get('PowerSupplies', {}):
        psu_group_name = psu_detail.get('Oem', {}).get(manufacturer, {}).get('AssociatedPowerControl', '')
        if 'SysPowerGroup' == psu_group_name:
            sys_power_group.append(psu_detail.get("Name", ""))
        elif 'GPUPowerGroup' == psu_group_name:
            gpu_power_group.append(psu_detail.get("Name", ""))
    if (is_sys_psu_upgrade_success(sys_power_group, gpu_power_group, psu_message)
            or is_gpu_psu_upgrade_success(sys_power_group, gpu_power_group, psu_message)):
        common_function.print_info('Success: %s Update Pass.' % module)
        return True
    return False


def upgradebmcfirmware(client, args):
    """
    Function Description:BMC firmware upgrade
    Parameter:client refishClient:class object
    args object:CLI command
    Modify: 2019.1.17 Default timeout parameter normalization.
    2019.4.26 Change the name of the variable that obtains the progress result.
    """
    args.file = get_info_from_zip(args.file)
    args.imageuri = get_info_from_zip(args.imageuri)
    version_dict = get_attribute_value()
    module = version_dict.get("module") if version_dict.get("module") else "Firmware"
    resp = post_simple_update(client, args)

    if resp is None:
        return None

    if resp.get('status_code', '') == 202:
        get_resp = client.print_task_prog(resp,
                                          maxtime=common_function.MAX_TIMEOUT,
                                          flag="upgradefw")
        if get_resp is None:
            return None
        if get_resp == 'Exception':
            taskid = resp.get('resource', {}).get('@odata.id', {})
            task_resp = client.get_resource(taskid)
            if task_resp is None:
                return None
            if task_resp['status_code'] != 200:
                return task_resp
            message_id = task_resp['resource']['Messages']['MessageId'].split(".")[-1]
            if "FirmwareUpgradeError" == message_id and "PSU" in task_resp['resource']['Messages']['Message']\
                    and is_psu_upgrade_successful(task_resp, client, module):
                return resp
            print_error_message(module, message_id)
            sys.exit(144)
        if get_resp == 'Completed':
            # 判断是否立即生效
            if version_dict.get("active_mode") != 'Immediately' and version_dict.get("active_mode"):
                common_function.print_info('Success: The %s upgrade task is pending, image is saved in BMCFLASH, '
                                           'waiting to trigger the upgrade. trigger: powercycle, poweroff.' % module)
            else:
                common_function.print_info('Success: %s Update Pass.' % module)

    else:
        message_id_data = resp.get('message', {}).get('error', {}).get(
            '@Message.ExtendedInfo', {})[0].get('MessageId', '').split('.')
        message_id = message_id_data[len(message_id_data) - 1]
        print_error_message(module, message_id)

    return resp


def post_simple_update(client, args):
    """
    功能描述：post url
    参数： client (redfish_client):redfish接口
           parser (ArgumentParser):subcommand argparser.
           Export error messages when parameters are incorrect
           args (list):parameter list
    返回值：None
    异常描述：None
    """
    url = "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"
    payload = get_payload_dict(client, args)
    if payload is None:
        return payload

    # 2021.3.3 Modify: Changing the default timeout interval.
    resp = client.create_resource(url, payload,
                                  timeout=common_function.TIMEOUT_UPDATE)
    return resp


def get_payload_dict(client, args):
    """
    功能描述：urest 带内命令删除带内session
    参数： client (redfish_client):redfish接口
           parser (ArgumentParser):subcommand argparser.
           Export error messages when parameters are incorrect
           args (list):parameter list
    返回值：None
    异常描述：None
    """
    payload = {}
    if args.file is not None:
        if str(args.file).split('/')[-1] == args.file:
            filename = str(args.file).split("\\")[-1]
        else:
            filename = str(args.file).split('/')[-1]
        ret = upload_file(client, args, filename)
        if ret is False:
            return None
        payload['ImageURI'] = ('/tmp/web/' + filename)
        if args.preserveConfig:
            payload['PreserveConfig'] = eval(args.preserveConfig)
    elif args.imageuri is not None:
        remote_path = common_function.splice_remote_server_path(args)
        payload['ImageURI'] = remote_path
        if args.protocol is not None:
            payload['TransferProtocol'] = args.protocol.upper()
        else:
            payload['TransferProtocol'] = get_non_interactive_command_protocol(args).upper()
    return payload


def get_non_interactive_command_protocol(args):
    """
    Function Description: get non-interactive command protocol
    Parameter: args object:CLI command
    """
    protocol = ""
    for pro in common_function.PROTOCOL_LIST:
        if args.imageuri.startswith(pro):
            protocol = pro
    return protocol


def connect_redfish(client):
    url = "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"
    resp = client.get_resource(url)
    if resp is None:
        return False
    message_id_data = resp['message']['error']['@Message.ExtendedInfo'][0]['MessageId'].split('.')
    message_id = message_id_data[len(message_id_data) - 1]
    if "AuthorizationFailed" == message_id:
        return False
    return True


def upload_file(client, args, filesname):
    """
    #==========================================================================
    #   @Method:  upload file
    #   @Param:
    #   @Return:
    #   @author:
    #==========================================================================
    """
    if not connect_redfish(client):
        return INVALID_VALUE
    url_upload = "/redfish/v1/UpdateService/FirmwareInventory"
    file_obj = None
    try:
        with open(args.file, 'rb') as file_obj:
            files = {'imgfile': (filesname, file_obj, "multipart/form-data",
                                 {'user_name': (client.username)})}
            # 2021.3.3 Modify: Changing the default timeout interval.
            resp = client.create_resource(url_upload,
                                          files=files,
                                          timeout=(common_function.
                                                   TIMEOUT_UPDATE))
    except IOError:
        print("Failure: Failed to open the uploaded file. "
              "Please try again.")
        sys.exit(common_function.UREST_STATUS_CODE_127)
    finally:
        if file_obj:
            file_obj.close()

    if resp is None:
        return False

    if resp['status_code'] != 202:
        raise common_function.CustomError(resp)

    return None


def err_400_proc(resp):
    """
    #=====================================================================
    #   @Method:  When the response code is 400, process error messages
    #   @Param:   resp, redfish response result
    #   @Return:
    #   @author:
    #=====================================================================
    """
    error_message_dict = {
        'ActionParameterValueFormatError':
            'Failure: the value for -i is of a different '
            'format than the parameter can accept',
        'FirmwareUpgrading':
            'Failure: a file transfer task is being performed '
            'or an upgrade operation is in progress',
        'FileDownloadTaskOccupied':
            'Failure: other file is transfering, '
            'current upgrade request failed',
        'TaskLimitExceeded':
            'Failure: the asynchronous operation failed because the '
            'number of simultaneous tasks has reached the limit'
    }
    extend_info_list = resp['message']['error']['@Message.ExtendedInfo']
    message_id = extend_info_list[0]['MessageId'].split(".")[-1]
    if message_id in error_message_dict:
        print(error_message_dict.get(message_id))
    else:
        print('Failure: status code 400')
