# -*- coding: UTF-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.

import re
import codecs
import shutil
import os

from cbb.frame.cli.cliUtil import get_system_version_with_ret
from cbb.business.collect import log_collector
from cbb.frame.cli.execute_on_all_controllers import ExecuteOnAllControllers
from cbb.frame.cli.execute_on_all_controllers import ResultType
from cbb.frame.cli.execute_on_all_controllers import FuncResult
from cbb.frame.cli.execute_on_all_controllers import ExeOnAllCtrlContext
from cbb.frame.cli import cliUtil
from cbb.frame.context import contextUtil
from cbb.frame.base.exception import UnCheckException
from cbb.frame.ruleConfig import common
from com.huawei.ism.tool.obase.utils import ApplicationContext
from cbb.frame.util.tar_util import decompress_tar_all_file

# 5110 V5/5210 V5/5110F V5/5210F V5/5100K V5/5200K V5	V5R7C00SPC100及之后
DEVICE_TYPE_V5_LIST = ['5300 V5 Kunpeng', '5310 V5', '5310F V5,', '5510 V5', '5510F V5',
                       '5610 V5', '5610F V5', '5810 V5', '5810F V5']

V5_VERSION = "V500R007C60SPC100 Kunpeng"

RISK_VERSION_DICT = {"V500R007C60SPC100": "V500R007C60SPH107"}

# 当前日志
DEFAULT_TRANS_FILE_PATH = {}

# 历史日志
COLLECTED_FILES = {
    "Hi1822_fc": {
        "path": '/OSM/log/cur_debug/pangea/',
        "file_pattern": "Hi1822_fc"
    },
    "hifc_chip_log": {
        "path": "/OSM/log/cur_debug/pangea/",
        "file_pattern": "hifc_chip_log.tar.gz"
    },
}


def execute(data_dict):
    """ 检查方法：
    步骤1：admin用户登录环境，执行show upgrade package命令，获取环境大包版本和补丁版本。
    步骤2：切换到developer视图下进入debug模式，输入命令drvunf mml allinfo查询到所有卡号，grep过滤出00结尾的端口号。
    步骤3：继续使用drvunf mml getfwlog portid收集之前匹配到的端口对应卡的日志，会生成一个tar在/OSM/coffer_log/log/cur_debug/pangea路径下。
    步骤4：解压日志tar包，匹配up_ram_xxx日志中的关键字up_mips_get_exception_info。
    步骤5：检查完后删除冗余日志。
    检查标准：
    1 步骤1中大包版本为V500R007C60SPC100,补丁版本低于SPH107，则继续执行步骤2，否则不涉及该问题。
    2 步骤2中有00结尾的端口号则继续进行步骤3，否则不涉及该问题。
    3 步骤4中匹配命中则检查不通过，出现该问题，否则检查通过。
    修复建议：
    如果检查不通过，请安装V500R007C60SPH107补丁，如有问题请联系华为工程师。
    :param data_dict: 上下文信息
    :return: 检查结果
    """
    # 执行升级前检查
    return Check1822exceptionhotpatch(data_dict).execute()


class Check1822exceptionhotpatch:
    def __init__(self, data_dict):
        self.data_dict = data_dict
        self.cli = contextUtil.getCli(data_dict)
        self.logger = contextUtil.getLogger(data_dict)
        self.lang = contextUtil.getLang(data_dict)
        self.dev = self.data_dict.get("dev")
        self.dev_type = str(self.dev.getDeviceType())
        self.product_version = self.dev.getProductVersion()
        self.origin_info = []

    def execute(self):
        log_collect_context = self.get_context(self.data_dict)
        try:
            self.logger.info("dev_type:{} product_version:{}".format(
                self.dev_type, self.product_version))
            self.origin_info.append("dev_type:{} product_version:{}".format(
                self.dev_type, self.product_version))
            target_patch_version = self.data_dict.get("pkgVersion")
            self.logger.info("target_patch_version:{}".format(target_patch_version))
            # 检查设备型号和版本信息。
            if not self.is_involved_version():
                return True, "", self.get_origin_info()
            # 2、当前版本为V5R7C60SPC100版本且待安装的补丁版本低于SPH107，
            # 或者为其他系统版本，则继续检查，否则检查通过；
            if self.is_install_patch(target_patch_version):
                return True, "", self.get_origin_info()
            # 3、每控进入developer再到debug下生成芯片日志。
            exe_context = ExeOnAllCtrlContext(self.data_dict)
            self.collect_chip_log(exe_context)
            collet_ret = "\n".join(self.origin_info)
            if exe_context.err_msg:
                suggest_msg = common.getMsg(self.lang,
                                            "contact.technical.support")
                exe_context.append_err_msg(suggest_msg)
                return False, exe_context.err_msg, collet_ret
            self.logger.info(
                "the chip log is collected.")
            # 4、获取芯片日志到本地并解析匹配关键字。
            collected_failed_engines, export_files = log_collector.execute(log_collect_context)
            self.logger.info("failed_eng:{}, export_files:{}".format(
                collected_failed_engines, export_files))
            err_contrl, key_not_match_flag = self.check_up_mips_get_exception_info_in_tar_file(export_files)
            err_msg = self.get_error_msg(err_contrl)
            self.logger.info("the err_msg is:{}.".format(err_msg))
            if key_not_match_flag:
                self.logger.info("1822_fc card is normal.")
                self.origin_info.append("1822_fc card is normal.")
                return True, '', self.get_origin_info()
            else:
                self.logger.info("1822_fc card is abnormal.")
                self.origin_info.append("1822_fc card is abnormal.")
                return False, err_msg, self.get_origin_info()
        except UnCheckException as UnCheck:
            self.logger.error("UnCheck check_1822_exception_hotpatch except :{}".format(str(UnCheck)))
            msg = cliUtil.getMsg(
                self.lang, "SmartIO2.0.exception.hotpatch.is.abnormal", "")
            return False, msg, self.get_origin_info()
        except Exception as e:
            self.logger.error("check_1822_exception_hotpatch except:{}".format(str(e)))
            msg = cliUtil.getMsg(self.lang, "SmartIO2.0.exception.hotpatch.is.abnormal", "")
            return False, msg, self.get_origin_info()
        finally:
            self.clean_up(self.data_dict, log_collect_context)

    def get_origin_info(self):
        return "\n".join(self.origin_info)

    def is_involved_version(self):
        self.logger.info("involved_version check")
        if "Dorado" in self.dev_type:
            return False
        if self.dev_type in DEVICE_TYPE_V5_LIST and self.product_version == V5_VERSION:
            return True
        return False

    def is_install_patch(self, target_patch_version):
        """
        2、当前版本为V5R7C60SPC100版本且待安装的补丁版本低于SPH107，
        或者为其他系统版本，则继续检查，否则检查通过；
        :return: 版本的检查结果。
        """
        self.logger.info("get_system_version_with_ret start")
        flag, software_version, patch_version, cli_ret = get_system_version_with_ret(
            self.cli, self.lang)
        self.origin_info.append(cli_ret)
        if flag is not True:
            self.logger.error("get_system_version_with_ret failed")
            raise UnCheckException("get_system_version_with_ret failed.")
        if "V500R007C60" in target_patch_version:
            safe_version = RISK_VERSION_DICT.get(software_version)
            self.logger.info("safe_version long is:{}.".format(safe_version))
        else:
            safe_version_list = list(RISK_VERSION_DICT.get(software_version))[11:]
            safe_version = "".join(safe_version_list)
            self.logger.info("safe_version short is:{}.".format(safe_version))
        if software_version in RISK_VERSION_DICT and target_patch_version >= safe_version:
            self.logger.info(
                "RISK_VERSION_DICT.get(software_version) is {}.".format(safe_version))
            return True
        self.logger.info("is_install_patch will return False.")
        return False

    @ExecuteOnAllControllers
    def collect_chip_log(self, data_dict):
        """
        debug模式执行drvunf mml allinfo查询卡件
        :return: 环境FC卡port信息是否收集成功 True or False
        """
        current_ctrl_id = data_dict.cur_ctrl_id
        cmd_1822_info = "drvunf mml allinfo"
        self.logger.info("cmd_1822_info is {}".format(cmd_1822_info))
        self.cli = data_dict.dev_info.cli
        lang = data_dict.lang
        flag, cli_ret, err_msg = cliUtil.excuteCmdInDebugModel(self.cli, cmd_1822_info, True, lang)
        self.origin_info.append(cli_ret)
        self.logger.info("the cmd_1822_info ret is :{}".format(cli_ret))
        if "ERROR:parameter error" in cli_ret:
            self.logger.error("show 1822 fc card info failed on:{} for parameter error".format(current_ctrl_id))
            raise UnCheckException("ERROR:parameter error")
        if "CMND exe error" in cli_ret:
            self.logger.error("show 1822 fc card info failed on:{} for CMND exe error".format(current_ctrl_id))
            raise UnCheckException("CMND exe error")
        try:
            cli_ret_list = cli_ret.encode("utf8").splitlines()
            self.logger.info("the cli_ret_list is :{}".format(cli_ret_list))
            for line in cli_ret_list:
                port_match = re.findall(r'0x110[0-7]00', line)
                self.logger.info("the port_match is :{}".format(port_match))
                self.getfwlog_exec(port_match, current_ctrl_id, lang)
            return FuncResult(ResultType.SUCCESS, "", "")
        except Exception as e:
            self.logger.error("collect_1822_card_info except:{}".format(str(e)))
            return FuncResult(ResultType.FAILED, "", "")

    def getfwlog_exec(self, port_match, current_ctrl_id, lang):
        if port_match:
            for match_i in port_match:
                cmd_get_log = "drvunf mml getfwlog " + match_i + " /OSM/log/cur_debug 1 0"
                flag_log, cli_ret_log, err_msg_log = cliUtil.excuteCmdInDebugModel(self.cli, cmd_get_log, True, lang)
                self.origin_info.append(cli_ret_log)
                self.logger.info("the cli_ret_log for getfwlog is :{}".format(cli_ret_log))
                if "error" in cli_ret_log:
                    self.logger.error("get 1822 fc card info failed")
                    raise UnCheckException("get 1822 fc card info failed on".format(current_ctrl_id))

    def get_context(self, origin_context):
        """
        获取context
        :param origin_context:
        :return:
        """
        import com.huawei.ism.tool.obase.entity.EntityUtils as EntityUtils
        from com.huawei.ism.tool.bizpack.wizardparse.util import DevNodeUtil
        new_context = dict()
        new_context["logger"] = origin_context.get("logger")
        new_context["lang"] = origin_context.get("lang")
        dev_node = origin_context.get("dev")
        new_context['origin_context'] = origin_context
        new_dev_node = EntityUtils.toNewDev(dev_node)
        new_context['dev'] = DevNodeUtil.getDevMapDict(new_dev_node)
        new_context['devInfo'] = new_dev_node
        new_context['protocalContext'] = origin_context.get("protocalContext")
        new_context['cli'] = contextUtil.getCli(origin_context)
        new_context["log_path"] = self.get_tmp_log_path(origin_context)
        new_context["collected_files"] = COLLECTED_FILES
        new_context["default_trans_file_path"] = DEFAULT_TRANS_FILE_PATH
        self.logger.info("log_path is:{},collected_files is:{},default_trans_file_path is:{}".format(
                                                                            new_context["log_path"],
                                                                            new_context["collected_files"],
                                                                            new_context["default_trans_file_path"],
                                                                            ))
        return new_context

    def clean_up(self, context, log_collect_context):
        """
        回退到cli模式，清理残留文件
        :param context:
        :param log_collect_context:
        :return:
        """
        self.clear_temp_log_file(log_collect_context)
        cli = log_collect_context.get("cli")
        if cli:
            cliUtil.enterCliModeFromSomeModel(cli, context.get("lang"))

    def clear_temp_log_file(self, log_collect_context):
        tmp_path = log_collect_context.get("log_path", '')
        shutil.rmtree(tmp_path, ignore_errors=True)

    def check_up_mips_get_exception_info_in_tar_file(self, export_files):
        """
        处理压缩文件
        :param file_path: 文件路径
        :return:
        """
        def get_stat_match_count():
            match_cnt = 0
            for f_path in os.listdir(fina_dir):
                self.logger.info("the path of tar.gz is :{}".format(f_path))
                match_cnt += self.check_up_mips_get_exception_info_in_text_file(
                    os.path.join(fina_dir, f_path)
                )
            return match_cnt

        def decompress_tar_file():
            target_dir = ctrl_file_path.replace(".tar.gz", "")
            result = decompress_tar_all_file(ctrl_file_path, target_dir)
            if not result:
                target_dir = ''
            ctrl_name, fina_dir = parse_target_dir() if result else ('', '')
            return target_dir, ctrl_name, fina_dir

        def parse_target_dir():
            self.logger.info("target_dir is:{}.".format(target_dir))
            win_path = target_dir.split("\\")
            tmp_path = win_path[-1].split("/")
            enter_path = tmp_path[-1][3:]
            controller_name = tmp_path[-1][:2]
            self.logger.info("enter_path is :{}".format(enter_path))
            fina_dir = os.path.join(target_dir, enter_path)
            self.logger.info("fina_dir is :{}".format(fina_dir))
            return controller_name, fina_dir

        self.logger.info("come to tar.gz to check.")
        controller_list = []
        for ctrl_name, res_list in export_files.items():
            for ctrl_file_path in res_list:
                self.logger.info("ctrl_file_path is:{}.".format(ctrl_file_path))
                if not str(ctrl_file_path).endswith(".tar.gz"):
                    continue
                match_count = 0
                target_dir, controller_name, fina_dir = decompress_tar_file()

                if not os.path.exists(fina_dir):
                    continue
                match_count += get_stat_match_count()
                if match_count != 0 and (controller_name not in controller_list):
                    controller_list.append(controller_name)
                shutil.rmtree(target_dir, ignore_errors=True)
        self.logger.info("wrong controllers are :{}".format(controller_list))
        return controller_list, not controller_list

    def check_up_mips_get_exception_info_in_text_file(self, file_path):
        """
        处理单个文件，匹配up_mips_get_exception_info:
        :param file_path: 文件路径
        :return:
        """
        res = []
        with codecs.open(file_path, 'r', encoding='utf-8') as f:
            res = f.readlines()
        res_str = "".join(res)
        self.logger.info("res_str is:{}.".format(res_str))
        regex = re.compile("up_mips_get_exception_info")
        all_event = regex.findall(res_str)
        self.logger.info("the event in txt is:{}".format(all_event))
        if not all_event:
            return 0
        return 1

    def get_work_path(self):
        return ApplicationContext.getInstance().getWorkPath()

    def get_tmp_log_path(self, context):
        return self.get_work_path() + "\\log_collect\\" + self.get_sn(context)

    def get_sn(self, context):
        return context.get('dev').getDeviceSerialNumber()

    def get_error_msg(self, err_contrl):
        if err_contrl:
            return cliUtil.getMsg(
                self.lang, "SmartIO2.0.exception.hotpatch.not.pass",
                ", ".join(err_contrl))

        return ''
