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

# BBU补丁安装前检查（补丁工具 非内置）

import re
import time
from datetime import datetime, timedelta

from resource.resource import MESSAGES_DICT
from cbb.frame.cli import cliUtil
from cbb.frame.context import contextUtil
from cbb.frame.base import baseUtil


def execute(data_dict):
    """
    :功能描述: 针对指定BBU电源补丁版本升级前进行告警检查及BBU状态检查
    :param data_dict: 上下文
    :return:
    """

    check_item = CheckBBUAlarmAndStatus(data_dict)
    # 1.检查补丁版本,不是目标版本返回False,检查结果为通过
    if not check_item.check_patch_version():
        return True, "", "\n".join(check_item.original_info)
    # 2.检查目标告警
    if not check_item.show_alarm_of_bbu():
        # BBU存在告警，检查不通过，否则继续检查
        return False, "\n".join(check_item.err_msg_list), "\n".join(check_item.original_info)
    # 3.检查BBU运行状态&健康状态
    if not check_item.check_bbu_status():
        # BBU状态异常，检查不通过，否则继续检查
        return False, "\n".join(check_item.err_msg_list), "\n".join(check_item.original_info)
    # 4.检查超级管理员用户的账号状态
    if not check_item.check_super_admin_status():
        # 存在账号状态异常的超级管理员
        return False, "\n".join(check_item.err_msg_list), "\n".join(check_item.original_info)
    # 5.检查用户名称是否包含root关键字
    if not check_item.check_user_name():
        # 用户名称包含root关键字
        return False, "\n".join(check_item.err_msg_list), "\n".join(check_item.original_info)
    # 6.不存在BBU告警，且状态正常时，关闭BBU放电，检查结果为通过
    check_item.change_bbu_discharging_switch_to_off()
    return True, "", "\n".join(check_item.original_info)


def execute_after(data_dict):
    """
    :功能描述: 针对指定BBU电源补丁版本升级后进行告警检查
    :param data_dict: 上下文
    :return:
    """

    check_item = CheckBBUAlarmAndStatus(data_dict)
    # 检查补丁版本,不是目标版本返回False,退出检查
    if not check_item.check_patch_version():
        return True, ""
    # 检查不兼容告警
    result = check_item.check_alarm_of_bbu_after()
    # 检查是否存在告警误屏蔽
    if not check_item.check_alarm_masking_event_after():
        # 取消告警屏蔽
        result_alarm_mask = check_item.cancel_bbu_alarm_mask_after()
        # 检查内部组件异常告警
        result_components = check_item.check_alarm_components_abnormal_after()
        # 更新result结果
        result = result if result_alarm_mask and result_components else False
    return result, "\n".join(check_item.err_msg_list), "\n".join(check_item.original_info)


class CheckBBUAlarmAndStatus:
    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.err_msg_list = []
        self.original_info = []  # 原始信息
        self.bbu_patch_ver_dict = {
            "V300R006C50SPC100": "SPH139",
            "V500R007C30SPC100": "SPH139",
            "V500R007C61": "SPH060",
            "V300R002C10SPC100": "SPH162"
        }
        self.alarm_tuple = (
            "0xF00D20019", "0xF00D2001D", "0xF0D20004", "0xF00D20014",
            "0xF00D2001C", "0xF0D20005", "0xF00D2001B", "0xF010D0027"
        )
        # admin密码过期导致补丁安装失败的版本
        self.problem_ver = ("V500R007C61SPH060", "V500R007C61SPH061", "V500R007C61SPH063", "V500R007C61SPH065")
        # 使用新框架的版本
        self.new_framework_ver = ("V500R007C61", "V300R002C10SPC100")

    def check_patch_version(self):
        dev = self.data_dict.get("dev")
        product_version = dev.getProductVersion()  # 基础版本：V300R006C50SPC100
        cur_patch_ver = dev.getHotPatchVersion()  # 当前补丁版本：--或者V300R006C50SPH138
        target_patch_ver = self.data_dict.get("pkgVersion")  # 目标补丁版本：V300R006C50SPH139
        self.original_info.append("Product Version: %s\nCurrent Patch Version: %s\nTarget Patch Version: %s\n"
                                  % (product_version, cur_patch_ver, target_patch_ver))

        if product_version not in self.bbu_patch_ver_dict:
            return False
        # 当前补丁版本
        sph_v_current_info = re.findall('SPH(\d+)', cur_patch_ver)
        sph_v_current = int(sph_v_current_info[0]) if sph_v_current_info else 0
        # 基线补丁版本
        sph_v_base = int(self.bbu_patch_ver_dict.get(product_version).replace("SPH", ""))
        # 目标补丁版本
        sph_v_target_info = re.findall('SPH(\d+)', target_patch_ver)
        sph_v_target = int(sph_v_target_info[0]) if sph_v_target_info else 0
        self.logger.info("[check_bbu_alarm_and_status]sph_v_current=%s, sph_v_base=%s, sph_v_target=%s"
                         % (sph_v_current, sph_v_base, sph_v_target))
        return sph_v_current < sph_v_base and sph_v_base <= sph_v_target

    def change_bbu_discharging_switch_to_off(self):
        # 部分型号不支持放电开关命令
        not_support_switch_off_products = (
            "2600 V3", "2800 V3", "2800 V5", "2810 V5", "5110 V5", "5210 V5", "5300 V5", "5310 V5",
            "2600F V3", "5110F V5", "5210F V5", "5300F V5", "5310F V5",
            "2600 V3 Enhanced", "5210 V5 Enhanced", "5300 V5 Enhanced",
            "2600F V3 Enhanced", "5210F V5 Enhanced", "5500 V5 Elite"
        )
        product_model = str(self.data_dict.get("dev").getDeviceType())

        self.original_info.append("Product Model: %s\n" % product_model)
        if product_model in not_support_switch_off_products:
            self.logger.info("[check_bbu_alarm_and_status][discharging_switch]Command not supported.")
            return True  # 不支持命令的型号, 直接返回True

        # 进入developer模式
        cliUtil.enterDeveloperMode(self.cli, self.lang)
        for i in range(3):
            self.logger.info("[check_bbu_alarm_and_status][discharging_switch]Trying to %s." % i)
            cmd = "change bbu discharging_switch switch=off"
            is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
            self.original_info.append("%s\n" % cli_ret)
            time.sleep(1)

            cmd = "show bbu discharging_switch"
            is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
            self.original_info.append("%s\n" % cli_ret)
            if "off" in cli_ret.lower():
                return True
            time.sleep(1)

        self.logger.info("[check_bbu_alarm_and_status][discharging_switch]The command fails to be executed.")
        return True

    def get_alarm_bbu_id(self, alarm_sequence):
        '''从告警的Detail中解析BBU ID'''
        cmd_detail = "show alarm sequence=%s" % alarm_sequence
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd_detail, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        alarm_detail = cliUtil.getVerticalCliRet(cli_ret)
        if not alarm_detail:
            return ""
        alarm = alarm_detail[0]
        detail = alarm.get("Detail")
        bbu_module = re.findall('.*BBU module.*\\((.*)\\)', detail)
        if not bbu_module:
            return ""
        msg_lst = bbu_module[0].split(',')
        if len(msg_lst) < 2:
            return ""
        ctrl = "".join(msg_lst[0].strip().split()[2:])
        bbu = "".join(msg_lst[1].strip().split()[2:])
        bbu_id = "{}.{}".format(ctrl, bbu)
        return bbu_id

    def show_alarm_of_bbu(self):
        alarm_flag = True
        cmd = "show alarm|filterColumn include columnList=Sequence,ID"
        filter_lst = ["column=ID predict=match value=%s" % alarm_id for alarm_id in self.alarm_tuple]
        cmd += "|filterRow " + " logicOp=or ".join(filter_lst)
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        alarm_dict = cliUtil.getHorizontalCliRet(cli_ret)
        for alarm_info in alarm_dict:
            alarm_flag = False
            alarm_id = alarm_info.get("ID")
            alarm_sequence = alarm_info.get("Sequence")
            err_report = "Alarm ID: %s, %s" % (alarm_id, self.get_msg("check.power.bbu.alarm.pre"))
            self.logger.error("[check_bbu_alarm_and_status]Alarm ID: %s, Alarms need to be handled." % alarm_id)

            # 查询Alarm所在的BBU
            bbu_id = self.get_alarm_bbu_id(alarm_sequence)
            if bbu_id:
                err_report = "BBU ID: %s, " % bbu_id + err_report
            self.err_msg_list.append(err_report)
        return alarm_flag

    def check_alarm_of_bbu_after(self):
        # 补丁升级后，检查是否存在BBU固件版本与阵列软件不兼容告警
        alarm_id = "0xF010D0027"
        cmd = "show alarm|filterColumn include columnList=Sequence,ID" \
              "|filterRow column=ID predict=match value=%s" % alarm_id
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        alarm_dict = cliUtil.getHorizontalCliRet(cli_ret)
        if alarm_dict:
            if self.data_dict.get("dev").getProductVersion() in self.new_framework_ver:
                # 基线版本为新框架，支持小系统命令升级
                self.err_msg_list.append("Alarm ID: %s, %s" % (alarm_id,
                                                               self.get_msg("check.power.bbu.alarm.after.minisystem")))
            else:
                self.err_msg_list.append("Alarm ID: %s, %s" % (alarm_id,
                                                               self.get_msg("check.power.bbu.alarm.after")))
            self.logger.error("[check_bbu_alarm_and_status]Alarm ID: %s, Alarms need to be handled." % alarm_id)
            return False
        return True

    def check_bbu_status(self):
        bbu_status = True
        cmd = "show bbu general"
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        bbu_dict = cliUtil.getHorizontalCliRet(cli_ret)
        for bbu_info in bbu_dict:
            bbu_id = bbu_info.get("ID")
            bbu_health_status = bbu_info.get("Health Status")
            bbu_running_status = bbu_info.get("Running Status")
            if bbu_running_status == "Charging" or bbu_running_status == "Discharging":
                # BBU处于在线测试状态
                self.err_msg_list.append("BBU ID: %s, %s" % (bbu_id, self.get_msg("check.power.bbu.online_testing")))
                self.logger.error("[check_bbu_alarm_and_status]The BBU %s is being tested online." % bbu_id)
                bbu_status = False
                continue
            if bbu_health_status != "Normal":
                # BBU健康状态不为Normal
                self.err_msg_list.append("BBU ID: %s, %s" % (bbu_id, self.get_msg("check.power.bbu.status.fail")))
                self.logger.error("[check_bbu_alarm_and_status]The BBU %s health status is abnormal." % bbu_id)
                bbu_status = False
        return bbu_status

    def get_msg(self, msg_key, args=""):
        return baseUtil.getPyResource(self.lang, msg_key, args, resource=MESSAGES_DICT)

    def check_super_admin_status(self):
        '''
        超级管理员用户的"Account Status"字段值为Initialization、Expired、About to expire或Unsafe，则检查结果为不通过，否则继续检查。
        '''
        target_patch_ver = self.data_dict.get("pkgVersion")  # 目标补丁版本
        if target_patch_ver not in self.problem_ver:
            self.logger.info("[check_bbu_alarm_and_status] There is no failure risk in the current hotpatch version.")
            return True

        abnormal_super_admins = []  # 账号状态异常的超级管理员
        cmd = "show user|filterRow column=Level predict=equal_to value=Super_admin"
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        users = cliUtil.getHorizontalCliRet(cli_ret)
        for user in users:
            account_status = user.get("Account Status")
            name = user.get("Name")
            if account_status in ("Initialization", "Expired", "About to expire", "Unsafe"):
                abnormal_super_admins.append(name)
        if not abnormal_super_admins:
            # 无异常超级管理员
            self.logger.info("[check_bbu_alarm_and_status] The super administrator account is in normal state.")
            return True

        for name in abnormal_super_admins:
            self.err_msg_list.append(
                "%s: %s" % (name, self.get_msg("check.power.bbu.super_admin_abnormal.interception")))
        return False

    def check_user_name(self):
        '''
        用户名称包含root关键字，则检查不通过，否则检查通过。
        '''
        target_patch_ver = self.data_dict.get("pkgVersion")  # 目标补丁版本
        if target_patch_ver not in self.problem_ver:
            self.logger.info("[check_bbu_alarm_and_status] There is no failure risk in the current hotpatch version.")
            return True

        check_flag = True  # 检查结果
        cmd = "show user|filterColumn include columnList=Name"
        _, cli_ret, _ = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        users = cliUtil.getHorizontalCliRet(cli_ret)
        for user in users:
            name = user.get("Name")
            if "root" in name:
                self.err_msg_list.append(
                    "%s: %s" % (name, self.get_msg("check.power.bbu.user_name_err.interception")))
                check_flag = False
        self.logger.info("[check_bbu_alarm_and_status] The user name check result is %s." % check_flag)
        return check_flag

    def search_system_time(self):
        # 获取系统时间
        cmd = "show system general"
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        system_message = cliUtil.getVerticalCliRet(cli_ret)
        if system_message:
            curr_time = re.findall(r"(\d+-\d+-\d+/\d+:\d+:\d+).*", system_message[0].get("Time", ""))
            return curr_time[0] if curr_time else ""
        return ""

    def gen_show_event_cmd(self):
        # 查询一天内的事件，提升查询效率
        curr_time = self.search_system_time()
        if curr_time:
            from_time = (datetime.strptime(curr_time, "%Y-%m-%d/%H:%M:%S") - timedelta(days=1)) \
                .strftime("%Y-%m-%d/%H:%M:%S")
            return "show event from_time=%s to_time=%s" % (from_time, curr_time)
        else:
            return "show event"

    def get_latest_event_time(self, event_id):
        '''
        获取最近一次事件发生时间
        '''
        cmd = "%s|filterColumn include columnList=Sequence,Occurred\sOn|" \
              "filterRow column=ID predict=match value=%s" \
              % (self.gen_show_event_cmd(), event_id)

        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        online_test_events = cliUtil.getHorizontalCliRet(cli_ret)
        if not online_test_events:
            return ""
        latest_event_time = ""
        last_sequence = -1
        for event in online_test_events:
            sequence = event.get("Sequence")
            if int(sequence) < last_sequence:
                continue
            last_sequence = int(sequence)
            occurred_on = event.get("Occurred On")
            occurred_date = re.findall(r"(\d+-\d+-\d+/\d+:\d+:\d+).*", occurred_on)
            if occurred_date:
                latest_event_time = occurred_date[0]
        self.logger.info("[check_bbu_alarm_and_status] The latest %s event occurred at %s."
                         % (event_id, latest_event_time))
        return latest_event_time

    def check_alarm_masking_event_after(self):
        '''
        补丁升级后，检查是否存告警误屏蔽
        检查升级过程中的告警屏蔽事件，0xF00D2001C告警屏蔽setting次数大于cancelling次数
        '''
        dev = self.data_dict.get("dev")
        product_version = dev.getProductVersion()  # 基础版本
        if product_version not in self.new_framework_ver:
            # 老补丁框架不涉及该问题
            return True

        start_upg_success_event = "0x200F010D001A"  # 启动升级成功事件
        upg_success_event = "0x100F010D0023"  # 升级成功事件
        alarm_masking_event = "0x200F00310008"  # 告警屏蔽事件
        bbu_model_fault_event = "0xF00D2001C"  # BBU模块故障告警

        start_upg_success_time = self.get_latest_event_time(start_upg_success_event)
        upg_success_time = self.get_latest_event_time(upg_success_event)

        if not (start_upg_success_time and upg_success_time):
            # 无升级事件
            self.logger.info("[check_bbu_alarm_and_status] No upgrade startup or upgrade success event is reported.")
            return True

        cmd = "%s|filterColumn include columnList=Sequence|" \
              "filterRow column=ID predict=equal_to value=%s " \
              "logicOp=and column=Occurred\sOn predict=greater_than value=%s " \
              "logicOp=and column=Occurred\sOn predict=less_than value=%s" \
              % (self.gen_show_event_cmd(), alarm_masking_event, start_upg_success_time, upg_success_time)
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        alarm_masking_events = cliUtil.getHorizontalCliRet(cli_ret)
        if not alarm_masking_events:
            # 无告警屏蔽事件
            self.logger.info("[check_bbu_alarm_and_status] There is no alarm masking event.")
            return True

        count_setting = 0   # 设置告警屏蔽的次数
        count_cancelling = 0   # 取消告警屏蔽的次数
        for alarm_masking in alarm_masking_events:
            sequence = alarm_masking.get("Sequence")
            cmd = "show event sequence=%s" % sequence
            is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
            self.original_info.append("%s\n" % cli_ret)

            event_info = cliUtil.getVerticalCliRet(cli_ret)
            detail = event_info[0].get("Detail")
            if bbu_model_fault_event not in detail:
                # 非BBU模块故障
                continue
            if "setting" in detail:
                count_setting += 1
            else:
                count_cancelling += 1
        self.logger.info("[check_bbu_alarm_and_status] Count setting is %s, count cancelling is %s." %
                         (count_setting, count_cancelling))
        return count_setting <= count_cancelling

    def exec_change_cmd_cli_mode(self, cmd):
        # 封装CLI配置命令
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        # 需要确认直接输入y
        cnt = 1
        while "y/n" in cli_ret and cnt <= 3:
            cli_ret = cliUtil.execCliYesorNoCmd(self.cli, "y", True)
            cnt += 1
        return cli_ret

    def cancel_bbu_alarm_mask_after(self):
        # 取消屏蔽BBU模块故障告警
        alarm_id = "0xF00D2001C"  # BBU模块故障告警
        for _ in range(3):
            cmd = "show alarm_mask"
            is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
            self.original_info.append("%s\n" % cli_ret)

            if alarm_id in cli_ret:
                # 取消屏蔽
                cmd = "change alarm_mask alarm_id_list=%s mask_switch=off" % alarm_id
                cli_ret = self.exec_change_cmd_cli_mode(cmd)
                self.original_info.append("%s\n" % cli_ret)
                time.sleep(1)
            else:
                self.logger.info("[check_bbu_alarm_and_status] Change alarm_mask %s off success." % alarm_id)
                return True
        self.logger.error("[check_bbu_alarm_and_status] Change alarm_mask %s off failed." % alarm_id)
        self.err_msg_list.append(self.get_msg("check.bbu.model.fault.alarm.after"))
        return False

    def check_alarm_components_abnormal_after(self):
        # 补丁升级后，检查是否存在内部组件异常告警，存在则报错提示
        alarm_id = "0xF00C90150"
        cmd = "show alarm|filterColumn include columnList=Sequence,ID" \
              "|filterRow column=ID predict=match value=%s" % alarm_id
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        alarm_dict = cliUtil.getHorizontalCliRet(cli_ret)
        if alarm_dict:
            self.logger.error("[check_bbu_alarm_and_status] An internal component exception alarm is generated.")
            self.err_msg_list.append(self.get_msg("check.components.abnormal.alarm.after"))
            return False
        self.logger.info("[check_bbu_alarm_and_status] No internal component exception alarm is generated.")
        return True
