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

# BBU补丁安装前检查（补丁评估 非内置）

import re
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.检查超级管理员用户的账号状态，无异常True，异常False
    result1 = check_item.check_super_admin_status()
    # 5.检查用户名称是否包含root关键字
    result2 = check_item.check_user_name()
    # 6.检查内部组件异常告警误报风险，无风险True，风险False
    result3 = check_item.check_component_alarm_risk()
    # 7.检查是否存在未升级的BBU，存在True，不存在False
    result4 = check_item.check_bbu_fw_ver()
    # 用户正常
    is_user_valid = result1 and result2
    if is_user_valid and result3 and not result4:
        # 超级管理员账号无异常，无内部组件异常告警误报风险，且不存在未升级的BBU
        return True, "", "\n".join(check_item.original_info)
    if result4:
        # 6.存在未升级的BBU，获取上一次的在线测试时间，预估下一次在线测试时间
        check_item.calculate_online_test_time()
    return False, "\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")

    @staticmethod
    def get_days_per_month(year):
        # 一年中每月的天数
        is_leap_year = (year % 100 == 0 and year % 400 == 0) or (year % 100 != 0 and year % 4 == 0)
        if is_leap_year:
            # 闰年
            days_per_month = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        else:
            # 非闰年
            days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        return days_per_month

    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 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_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 is_bbu_version_match(self, bbu_id):
        """
        检查方法: 执行命令：show bbu general bbu_id=?，查询BBU固件版本；
        检查标准：
        2 若步骤3中查询BBU固件版本为30.07T7或20.09T7之前，则进行下一步检查，否则退出检查并返回通过；
        """
        base_version_lst = [(30, 7, 7), (20, 9, 7)]  # ("30.07T7", "20.09T7")
        cmd = "show bbu general bbu_id={}".format(bbu_id)
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        bbu_info = cliUtil.getVerticalCliRet(cli_ret)
        fm_version = bbu_info[0].get("Firmware Version")
        self.logger.info("[check_bbu_alarm_and_status] BBU {} firmware version is {}.".format(bbu_id, fm_version))
        if not re.match("30.(\d+)T(\d+)", fm_version) and not re.match("20.(\d+)T(\d+)", fm_version):
            self.logger.info("[check_bbu_alarm_and_status] The BBU {} firmware version not match.".format(bbu_id))
            return False
        elif re.match("30.(\d+)T(\d+)", fm_version):
            base_version = base_version_lst[0]
        else:
            base_version = base_version_lst[1]
        for i, v in enumerate(re.findall("\d+", fm_version)):
            if int(v) == base_version[i]:
                continue
            return int(v) < base_version[i]
        self.logger.info("[check_bbu_alarm_and_status] The BBU {} firmware version has been upgraded.".format(bbu_id))
        return False

    def check_bbu_fw_ver(self):
        '''
        检查BBU固件版本是否升级，存在未升级的BBU返回True，否则返回False
        '''
        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")
            if self.is_bbu_version_match(bbu_id):
                # BBU未升级
                self.logger.info(
                    "[check_bbu_alarm_and_status] The BBU {} firmware version has not been upgraded.".format(bbu_id))
                return True
        self.logger.info("[check_bbu_alarm_and_status] The firmware of all BBUs has been upgraded.")
        return False

    def get_event_detail(self, sequence):
        '''获取事件的详细信息'''
        cmd = "show event sequence={}".format(sequence)
        _, cli_ret, _ = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)

        event_info = cliUtil.getVerticalCliRet(cli_ret)
        detail = event_info[0].get("Detail") if event_info else ""
        return detail

    @staticmethod
    def check_bbuid_in_detail(bbu_id, detail):
        '''检查BBU ID是否匹配'''
        bbu_module = re.findall(r'.*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_detail = "{}.{}".format(ctrl, bbu)
        return bbu_id == bbu_id_detail

    def get_event_occur_time(self, event_id, start_time="", end_time="", bbu_id=""):
        '''
        获取事件发生时间
        event_id:
        0x100F00D20015 在线测试
        0x200F00D20001 设置在线测试开关
        0x100F00E00037 电量充足
        '''
        if start_time and end_time:
            # 按照时间范围过滤
            cmd = "show event level=informational object_type=210 from_time=%s to_time=%s" \
                  "|filterColumn include columnList=Sequence,Occurred\sOn" \
                  "|filterRow column=ID predict=match value=%s" \
                  % (start_time, end_time, event_id)
        else:
            cmd = "show event level=informational object_type=210" \
                  "|filterColumn include columnList=Sequence,Occurred\sOn|" \
                  "filterRow column=ID predict=match value=%s" \
                  % event_id

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

        event_time_list = []
        online_test_events = cliUtil.getHorizontalCliRet(cli_ret)
        if not online_test_events:
            self.logger.info("[check_bbu_alarm_and_status] The event[%s] occur time list is %s."
                             % (event_id, event_time_list))
            return event_time_list
        for event in online_test_events:
            sequence = event.get("Sequence")
            detail = self.get_event_detail(sequence)
            if bbu_id and not self.check_bbuid_in_detail(bbu_id, detail):
                # bbu_id不匹配
                continue
            if event_id == "0x200F00D20001":
                # 设置在线测试开关只取off的事件
                if "off" not in detail:
                    continue
            occurred_on = event.get("Occurred On")
            occurred_date = re.findall(r"(\d+-\d+-\d+/\d+:\d+:\d+).*", occurred_on)
            if occurred_date:
                event_time_list.append((int(sequence), datetime.strptime(occurred_date[0], "%Y-%m-%d/%H:%M:%S")))
        # 按照Sequence逆序排列
        event_time_list.sort(key=lambda x: x[0], reverse=True)
        self.logger.info("[check_bbu_alarm_and_status] The event[%s] occur time list is %s."
                         % (event_id, event_time_list))
        return event_time_list

    def calculate_after_three_months_date(self, latest_online_test):
        """
          往后推三个月
          在线测试触发周期为3个月的计算方法
          1、看月份，间隔月份等于3，再看日期，与上一次触发日期相同
          2、若当月天数不够，顺延1个月，当间隔月份大于3时，就不看日期了，故触发时间为月份加1，日期为1号
        """
        # 下一次在线测试的时间：年、月、日
        next_y = latest_online_test.year
        next_m = latest_online_test.month + 3
        next_d = latest_online_test.day
        # 重新调整月份
        if next_m > 12:
            next_y += 1
            next_m -= 12
        # 重新调整日期
        days_per_month = self.get_days_per_month(next_y)
        if next_d <= days_per_month[next_m - 1]:
            next_occur_date = "{}-{}-{}".format(next_y, next_m, next_d)
        else:
            next_occur_date = "{}-{}-{}".format(next_y, next_m + 1, 1) if next_m != 12 \
                else "{}-{}-{}".format(next_y + 1, 1, 1)
        return datetime.strptime(next_occur_date, "%Y-%m-%d")

    def calculate_before_three_months_date(self, latest_online_test):
        """
          往前推三个月
          在线测试触发周期为3个月的计算方法
          1、看月份，间隔月份等于3，再看日期，与上一次触发日期相同
          2、若当月天数不够，顺延1个月，当间隔月份大于3时，就不看日期了，故触发时间为月份加1，日期为1号
        """
        # 下一次在线测试的时间：年、月、日
        next_y = latest_online_test.year
        next_m = latest_online_test.month - 3
        next_d = latest_online_test.day
        # 重新调整月份
        if next_m < 1:
            next_y -= 1
            next_m += 12
        # 重新调整日期
        days_per_month = self.get_days_per_month(next_y)
        if next_d <= days_per_month[next_m - 1]:
            next_occur_date = "{}-{}-{}".format(next_y, next_m, next_d)
        else:
            next_occur_date = "{}-{}-{}".format(next_y, next_m, days_per_month[next_m - 1])
        return datetime.strptime(next_occur_date, "%Y-%m-%d")

    def search_system_time(self):
        """查询系统时间"""
        cmd = "show system general"
        _, cli_ret, _ = 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 datetime.strptime(curr_time[0], "%Y-%m-%d/%H:%M:%S") if curr_time else None
        return None

    def calculate_intersection_date(self, calculate_set, border_date):
        """求预估范围与有效时间(负无穷,  latest_online_test - 三个月]的交集"""
        self.logger.info("[check_bbu_alarm_and_status] Re-adjusting the estimated time.")
        if calculate_set[1] <= border_date:
            # 预估范围在有效时间范围内
            return calculate_set[0], calculate_set[1]
        elif calculate_set[0] <= border_date and calculate_set[1] > border_date:
            # 预估范围与有效时间存在交集
            return calculate_set[0], border_date
        else:
            # 预估范围与有效时间范围无交集
            return None, None

    def search_latest_online_test(self, bbu_id, system_time):
        """
          查询最近一次有效在线测试时间
          从在线测试启动到跑完充放电全流程的在线测试被视为有效测试，BBU会更新测试时间，对应BBU日志为103->104
          BBU在线测试过程中被动停止，则为无效测试，BBU不更新测试时间，对应BBU日志为103->105
        """
        online_test_lst = self.get_event_occur_time("0x100F00D20015", bbu_id=bbu_id)
        if not online_test_lst:
            # 未获取到在线测试
            return None, None
        # 最近一次在线测试
        latest_online_test = online_test_lst[0][1]
        # 关闭在线测试
        close_online_test = None
        for online_test in online_test_lst:
            start_time = online_test[1].strftime("%Y-%m-%d/%H:%M:%S")
            end_time = (online_test[1] + timedelta(days=1)).strftime("%Y-%m-%d/%H:%M:%S")
            # 查询一天内的电量充足事件
            full_charge_lst = self.get_event_occur_time("0x100F00E00037", start_time, end_time, bbu_id=bbu_id)
            # 不存在电量充足事件，则取触发在线测试6小时内的事件
            latest_full_charge = full_charge_lst[-1][1] if full_charge_lst else online_test[1] + timedelta(hours=6)
            self.logger.info("[check_bbu_alarm_and_status] Latest full charge time is %s." % latest_full_charge)
            end_time = latest_full_charge.strftime("%Y-%m-%d/%H:%M:%S")
            # 查询关闭在线测试的事件
            close_online_test_lst = self.get_event_occur_time("0x200F00D20001", start_time, end_time)
            if not close_online_test_lst:
                # 无关闭在线测试，则为有效在线测试
                return online_test[1], None
            if not close_online_test:
                # 暂存在最后一次关闭在线测试的时间
                close_online_test = close_online_test_lst[0][1]
        self.logger.info(
            "[check_bbu_alarm_and_status] The online test is disabled at %s." % close_online_test)
        # 遍历完所有在线测试事件，仍然未找到有效的在线测试
        if datetime.strptime(system_time.strftime("%Y-%m-%d"), "%Y-%m-%d") <= \
                datetime.strptime(close_online_test.strftime("%Y-%m-%d"), "%Y-%m-%d") - timedelta(days=7):
            # 系统时间 <= T2 - 7天，无法预估下一次在线测试时间
            self.logger.info(
                "[check_bbu_alarm_and_status] "
                "The system time is less than or equal to the online test time minus 7 days.")
            return None, None
        return latest_online_test, close_online_test

    def calculate_seven_day_time(self, latest_online_test, system_time, close_online_test, latest_close_online_test):
        """预估7天后的在线测试时间"""
        # 先转换为日期，方便比较
        system_date = datetime.strptime(system_time.strftime("%Y-%m-%d"), "%Y-%m-%d")
        close_online_test_date = datetime.strptime(close_online_test.strftime("%Y-%m-%d"), "%Y-%m-%d")
        if system_date <= close_online_test_date:
            # 系统时间在预估在线测试的左侧区间：（T2 - 7天, T2]
            self.logger.info("[check_bbu_alarm_and_status] Into the left branch.")
            next_occur_date = close_online_test + timedelta(days=7)
        elif system_date <= close_online_test_date + timedelta(days=7):
            # 系统时间在预估在线测试的中间区间：（T2, T2 + 7天]
            self.logger.info("[check_bbu_alarm_and_status] Into the middle branch.")
            next_occur_date = latest_close_online_test + timedelta(days=7)
        else:
            # 系统时间在预估在线测试的右侧区间：( T2 + 7天, 正无穷)
            self.logger.info("[check_bbu_alarm_and_status] Into the right branch.")
            if latest_close_online_test:
                next_occur_date = latest_close_online_test + timedelta(days=7)
            else:
                next_occur_date = system_time
        self.logger.info("[check_bbu_alarm_and_status] Next occur time is %s." % next_occur_date)
        calculate_time = CalculateTime(latest_online_test.strftime("%Y-%m-%d"),
                                       system_time.strftime("%Y-%m-%d"),
                                       next_occur_date.strftime("%Y-%m-%d"),
                                       (next_occur_date + timedelta(days=7)).strftime("%Y-%m-%d"))
        return calculate_time

    def calculate_three_months_time(self, latest_online_test, system_time, latest_close_online_test):
        """预估三个月后的在线测试时间"""
        # 往前推三个月的触发时间
        date_before_three_months = self.calculate_before_three_months_date(latest_online_test)
        # 往后推三个月的触发时间
        date_after_three_months = self.calculate_after_three_months_date(latest_online_test)
        self.logger.info("[check_bbu_alarm_and_status] The security time range is %s to %s."
                         % (date_before_three_months.strftime("%Y-%m-%d"),
                            date_after_three_months.strftime("%Y-%m-%d")))

        system_date = datetime.strptime(system_time.strftime("%Y-%m-%d"), "%Y-%m-%d")
        if system_date <= date_before_three_months:
            # 系统时间在预估在线测试的左侧区间：(负无穷,  latest_online_test - 三个月]
            self.logger.info("[check_bbu_alarm_and_status] Into the left branch.")
            if latest_close_online_test:
                # 7天内存在关闭在线测试的动作，预估时间为：关闭在线测试的时间 + 7天
                next_occur_date = latest_close_online_test + timedelta(days=7)
            else:
                # 7天内无关闭在线测试的动作，预估时间为：系统时间
                next_occur_date = system_date
            # 预估时间与有效时间求交集
            next_occur_date, next_occur_date_delay = \
                self.calculate_intersection_date((next_occur_date, next_occur_date + timedelta(days=7)),
                                                 date_before_three_months)
            if not next_occur_date:
                # 若未取到交集，则预估时间为三个月后：[latest_online_test + 三个月，latest_online_test + 三个月 + 7天]
                next_occur_date = date_after_three_months
                next_occur_date_delay = next_occur_date + timedelta(days=7)
        elif system_date >= date_after_three_months:
            # 系统时间在预估在线测试的右侧区间：[latest_online_test + 三个月, 正无穷)
            self.logger.info("[check_bbu_alarm_and_status] Into the right branch.")
            if latest_close_online_test:
                # 7天内存在关闭在线测试的动作，预估时间为：关闭在线测试的时间 + 7天
                next_occur_date = latest_close_online_test + timedelta(days=7)
            else:
                # 7天内无关闭在线测试的动作，预估时间为：系统时间
                next_occur_date = system_date
            next_occur_date_delay = next_occur_date + timedelta(days=7)
        else:
            # 系统时间不在预估在线测试时间范围内：(latest_online_test - 三个月，latest_online_test + 三个月)
            self.logger.info("[check_bbu_alarm_and_status] Into the middle branch.")
            if latest_close_online_test and (latest_close_online_test + timedelta(days=7)) > date_after_three_months:
                # 7天内存在关闭在线测试的动作，且系统时间与后推3个月时间的间隔小于7天，预估时间为：关闭在线测试的时间 + 7天
                next_occur_date = latest_close_online_test + timedelta(days=7)
            else:
                # 预估时间为：后推3个月时间
                next_occur_date = date_after_three_months
            next_occur_date_delay = next_occur_date + timedelta(days=7)
        self.logger.info("[check_bbu_alarm_and_status] Next online test time is %s to %s." %
                         (next_occur_date.strftime("%Y-%m-%d"), next_occur_date_delay.strftime("%Y-%m-%d")))
        calculate_time = CalculateTime(latest_online_test.strftime("%Y-%m-%d"),
                                       system_date.strftime("%Y-%m-%d"),
                                       next_occur_date.strftime("%Y-%m-%d"),
                                       next_occur_date_delay.strftime("%Y-%m-%d"))
        return calculate_time

    def calculate_online_test_time_for_one_bbu(self, bbu_id, system_time, latest_close_online_test):
        '''
        计算在线测试时间
        在线测试运行事件流程：
        -> 打开在线测试开关（0x200F00D20001）
        -> 周期性放电测试(0x100F00D20015)/电量不足(0x100F0D20006)
        -> 充电（0x100F00D20017）
        -> 关闭在线测试开关（0x200F00D20001）或者测试结束
        -> 充电（0x100F00D20017）
        -> 电量充足（0x100F00E00037）
        '''
        self.logger.info("[check_bbu_alarm_and_status] Estimated online test time for bbu(%s)." % bbu_id)
        # 在线测试的时间
        latest_online_test, close_online_test = self.search_latest_online_test(bbu_id, system_time)
        if not latest_online_test and not close_online_test:
            # 未获取到在线测试事件，无法预估
            self.logger.info("[check_bbu_alarm_and_status] The online test time cannot be estimated.")
            return None

        if latest_online_test and close_online_test:
            # 手动关闭在线测试，下一次在线测试将在7天后打开
            self.logger.info("[check_bbu_alarm_and_status] The online test is manually disabled.")
            return self.calculate_seven_day_time(latest_online_test,
                                                 system_time,
                                                 close_online_test,
                                                 latest_close_online_test)
        self.logger.info("[check_bbu_alarm_and_status] Latest online test time is %s."
                         % latest_online_test.strftime("%Y-%m-%d"))

        # 获取到有效在线测试，下一次在线测试将在3个月后触发
        return self.calculate_three_months_time(latest_online_test, system_time, latest_close_online_test)

    def calculate_online_test_time(self):
        '''
        遍历BBU，分别计算在线测试时间
        '''
        # 查询系统时间
        system_time = self.search_system_time()
        self.logger.info("[check_bbu_alarm_and_status] System tine is %s."
                         % system_time.strftime("%Y-%m-%d/%H:%M:%S"))

        # 7天内最后一次关闭测试开关的时间
        start_time = (system_time - timedelta(days=7)).strftime("%Y-%m-%d/%H:%M:%S")
        end_time = system_time.strftime("%Y-%m-%d/%H:%M:%S")
        close_online_test_lst = self.get_event_occur_time("0x200F00D20001", start_time, end_time)
        latest_close_online_test = close_online_test_lst[0][1] if close_online_test_lst else None
        if latest_close_online_test:
            self.logger.info("[check_bbu_alarm_and_status] Latest close bbu online test time is %s."
                             % latest_close_online_test.strftime("%Y-%m-%d"))
        else:
            self.logger.info("[check_bbu_alarm_and_status] The online test switch is not disabled within seven days.")

        cmd = "show bbu general"
        _, cli_ret, _ = 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")
            self.original_info.append("BBU_ID: %s\n" % bbu_id)
            calculate_time = self.calculate_online_test_time_for_one_bbu(bbu_id, system_time, latest_close_online_test)
            if calculate_time:
                # 提示3个月后触发
                self.err_msg_list.append("BBU(%s): %s" % (bbu_id,
                                                          self.get_msg("check.power.bbu.online_testing.next_date", (
                                                              calculate_time.latest_time, calculate_time.system_time,
                                                              calculate_time.next_time_less,
                                                              calculate_time.next_time_max))))
            else:
                # 系统中无在线测试事件记录，无法估计下一次在线测试时间
                self.err_msg_list.append("BBU(%s): %s" %
                                         (bbu_id, self.get_msg("check.power.bbu.online_testing.next_date.null")))

    def check_super_admin_status(self):
        '''
        超级管理员用户的"Account Status"字段值为Initialization、Expired、About to expire或Unsafe，则检查结果为不通过，否则继续检查。
        '''
        product_version = self.data_dict.get("dev").getProductVersion()  # 基础版本
        target_patch_ver = self.data_dict.get("pkgVersion")  # 目标补丁版本
        if product_version not in self.new_framework_ver and target_patch_ver not in self.new_framework_ver:
            # 当前版本不涉及CLI问题，跳过超级管理员检查
            self.logger.info("[check_bbu_alarm_and_status] The current hotpatch version does not use the cli.")
            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:
            if target_patch_ver in self.problem_ver:
                self.err_msg_list.append(
                    "%s: %s" % (name, self.get_msg("check.power.bbu.super_admin_abnormal.interception")))
            else:
                self.err_msg_list.append(
                    "%s: %s" % (name, self.get_msg("check.power.bbu.super_admin_abnormal.suggestion")))
        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 check_component_alarm_risk(self):
        '''
        若目标补丁版本为V500R007C61SPH060、V500R007C61SPH061、V500R007C61SPH063或V500R007C61SPH065，
        且步骤6查询的引擎信息为多引擎，则检查标准6不通过，否则检查标准6通过，然后进行下一步检查。
        '''
        product_version = self.data_dict.get("dev").getProductVersion()  # 基础版本
        target_patch_ver = self.data_dict.get("pkgVersion")  # 目标补丁版本
        if product_version not in self.new_framework_ver and target_patch_ver not in self.new_framework_ver:
            # 当前版本不涉及CLI问题
            self.logger.info("[check_bbu_alarm_and_status] The current hotpatch version does not use the cli.")
            return True

        if target_patch_ver not in self.problem_ver:
            # 目标补丁版本不涉及内部组件异常告警误报问题
            self.logger.info("[check_bbu_alarm_and_status] The target patch version does not involve component alarm.")
            return True

        cmd = "show enclosure|filterRow column=Logic\sType predict=equal_to value=Engine"
        is_suc, cli_ret, err_msg = cliUtil.execCmdInCliMode(self.cli, cmd, True, self.lang)
        self.original_info.append("%s\n" % cli_ret)

        engines = cliUtil.getHorizontalCliRet(cli_ret)
        self.logger.info("[check_bbu_alarm_and_status] The number of engines is %s." % len(engines))
        if len(engines) >= 2:
            self.logger.info("[check_bbu_alarm_and_status] Multi-engine device.")
            self.err_msg_list.append(
                self.get_msg("check.power.bbu.component_alarm_risk.suggestion", (target_patch_ver)))
            return False
        return True


class CalculateTime:
    def __init__(self, latest_time, system_time, next_time_less, next_time_max):
        # 上一次在线测试时间
        self.latest_time = latest_time
        # 系统时间
        self.system_time = system_time
        # 下一次在线测试时间,开始值
        self.next_time_less = next_time_less
        # 下一次在线测试时间,结束值
        self.next_time_max = next_time_max
