# -*- coding: UTF-8 -*-
import traceback

from com.huawei.ism.tlv.bean import Param
from com.huawei.ism.tlv.docoder import ParamType
from com.huawei.ism.tlv import TLVUtils

from cbb.frame.rest.restUtil import Tlv2Rest
from cbb.frame.rest import restData
from cbb.frame.base import baseUtil
from cbb.frame.cli.cliUtil import excuteCmdInCliMode
from cbb.frame.cli import cliUtil
from cbb.frame.context import contextUtil
from cbb.frame.base import constants

from cbb.frame.base.constants import TLV_CMD
from checkitems.dev_free_memory import DevFreeMemory

from utils import Products
from com.huawei.ism.tlv.lang import UnsignedInt32
from com.huawei.ism.tool.obase.exception import ToolException

# 2021.6.15 变更：升级前检查删除了补丁包，释放了一些内存，所以内存检查都降低了一些
# 2021 8 19 变更，新增dorado V3，修改了中高端的没有不定的内存值为350
CMD_DEFAULT_TIMEOUT = 60 * 5
# V3R1和V3R2版本：如果空闲内存低于180M，则检查不通过
BEFORE_V3R2_FREE_MEMORY = 180
# 超低端如果空闲内存低于180M，则检查不通过
SUPER_LOWER_FREE_MEM = 180

# 2800 V3、2100 V3、2200 V3（8G内存）、2200V3 Enhanced（8G内存）和
# 2600 V3 for Video（V300R006C10及之前版本）：如果空闲内存低于210M，则检查不通过
LOWER_DEV_FRWW_MEM = 210
# doradoV3和低端如果当前设备没有补丁且空闲内存低于280，则检查不通过
LOWER_FREE_MEM = 280
# 中端如果当前设备没有补丁且空闲内存低于330
MID_NO_PATCH_FREE_MEM = 330
# 中端如果当前设备已经有补丁且空闲内存低于210，则检查不通过
MID_WITH_PATCH_FREE_MEM = 210
# 中端如果当前设备没有补丁且空闲内存低于350
HIGH_NO_PATCH_FREE_MEM = 350
DEFAULT_PATCH_SIZE = 300 * 1024 * 1024

NEED_CHECK_DEV_TYPE = ["2200 V3", "2200 V3 Enhanced"]
SPECIAL_DEV_MODEL_LIST = ["2600 V3 for Video"]
NO_NAS_CAPACITY = "8.000GB"
NAS_CAPACITY = "16.000GB"

MID_HIGH_DEV_MODEL = (
    "5300 V3",
    "5500 V3",
    "5600 V3",
    "5500F V3",
    "5600F V3",
    "5800 V3",
    "5800F V3",
    "6800 V3",
    "6800F V3",
    "18500 V3",
    "18800 V3",
    "18500F V3",
    "18800F V3",
    "2800 V5",
    "5500 V5",
    "5600 V5",
    "5300F V5",
    "5500F V5",
    "5600F V5",
    "5800 V5",
    "5800F V5",
    "6800 V5",
    "6800F V5",
    "18500 V5",
    "18800 V5",
    "18500F V5",
    "18800F V5",
)

SUPER_LOWER_DEV_MODEL = (
    "2800 V3",
    "2100 V3",
    "2200 V3 8G",
    "2200 V3",
    "2200 V3 Enhanced 8G",
    "2200 V3 Enhanced",
)

LOWER_DEV_MODEL = (
    "2200 V3 16G",
    "2200 V3 Enhanced 16G",
    "2600 V3",
    "2600 V3 for Video",
    "2600F V3",
    "2600 V3 Enhanced",
    "2600F V3 Enhanced",
    "5110 V5",
    "5210 V5",
    "5110F V5",
    "5210F V5",
    "5100K V5",
    "5200K V5",
    "5300 V5",
)


def execute(data_dict):
    """
    主程序入口，java框架调用
    :param data_dict: 上下文
    :return:
    """
    # 执行升级前检查
    free_memory_check_obj = FreeMemoryCheck(data_dict)
    free_memory = DevFreeMemory(data_dict)
    flag, msg, ret = free_memory_check_obj.check_free_memory(free_memory)
    if flag is True:
        return flag, msg, ret
    # 如果空闲内存不足，清理缓存后再检查

    free_memory_check_obj.get_upload_path(free_memory.is_support_rest)
    return free_memory_check_obj.check_free_memory(free_memory)


class FreeMemoryCheck:
    """
    空闲内存检查-空闲内存必须满足热补丁安装要求才允许升级
    """

    def __init__(self, data_dict):
        """
        初始化
        :param data_dict: 上下文
        """
        self.all_ret_list = list()
        self.error_msg_list = list()
        self.data_dict = data_dict
        self.log = contextUtil.getLogger(data_dict)
        self.lang = data_dict.get("lang")

    @staticmethod
    def get_threshold(has_patch, patched_mem, no_patch):
        """
        区分带补丁和不带补丁的内存值
        :param has_patch: 是否有补丁
        :param patched_mem: 有补丁内存值
        :param no_patch: 无补丁内存值
        :return: 内存阈值
        """
        return patched_mem if has_patch else no_patch

    @staticmethod
    def get_special_dev_model_threshold(
            product_mode, product_version, has_patch
    ):
        """
        处理特殊型号版本的内存阈值
        :param product_mode: 型号
        :param product_version: 版本
        :param has_patch: 是否有补丁
        :return:
        """
        if product_mode == "2600 V3 for Video":
            if Products.compareVersion(product_version, "V300R006C20") < 0:
                return LOWER_DEV_FRWW_MEM
            elif has_patch:
                return LOWER_DEV_FRWW_MEM
            else:
                return MID_NO_PATCH_FREE_MEM

    def check_free_memory(self, free_memory):
        normal_memory = self.get_memory_threshold()
        if not normal_memory:
            msg = cliUtil.getMsg(self.lang, "free.memory.check.pass")
            return True, msg, ""
        return free_memory.check_free_memory(normal_memory)

    def get_memory_threshold(self):
        dev = self.data_dict.get("dev")
        product_version = str(dev.getProductVersion())
        product_mode = self.get_dev_type()
        patch_version = dev.getHotPatchVersion()
        has_patch = bool(patch_version != "--" and patch_version)

        if baseUtil.isDoradoDev(product_mode) and product_version.startswith(
                "V3"):
            return LOWER_FREE_MEM

        if (
                product_version.startswith("V3")
                and Products.compareVersion(product_version, "V300R003") < 0
        ):
            return BEFORE_V3R2_FREE_MEMORY

        if product_mode in SPECIAL_DEV_MODEL_LIST:
            return self.get_special_dev_model_threshold(
                product_mode, product_version, has_patch
            )

        if product_mode in SUPER_LOWER_DEV_MODEL:
            return LOWER_DEV_FRWW_MEM

        if product_mode in LOWER_DEV_MODEL:
            return self.get_threshold(
                has_patch, LOWER_DEV_FRWW_MEM, MID_NO_PATCH_FREE_MEM
            )

        if product_mode in MID_HIGH_DEV_MODEL:
            return self.get_threshold(
                has_patch, MID_WITH_PATCH_FREE_MEM, HIGH_NO_PATCH_FREE_MEM
            )

        return 0

    def get_dev_type(self):
        """
        获取型号-"2200 V3", "2200 V3 Enhanced" 增加8G或16G内存显示。
        :return:
        """
        device_type = str(self.data_dict.get("dev").getDeviceType())
        if device_type not in NEED_CHECK_DEV_TYPE:
            return device_type

        flag, capacity = self.get_cache_capacity()
        if not flag:
            return device_type

        if capacity == NO_NAS_CAPACITY:
            return "{} {}".format(device_type, "8G")

        if capacity == NAS_CAPACITY:
            return "{} {}".format(device_type, "16G")

        return device_type

    def get_cache_capacity(self):
        """
        获取设备内存规格
        :return: 内存信息
        """
        cli = self.data_dict.get("ssh")
        lang = self.data_dict.get("lang")
        cliUtil.enterCliModeFromSomeModel(cli, lang)
        cmd = (
            "show controller general |filterColumn include columnList"
            "=Cache\\sCapacity"
        )
        execute_ret = excuteCmdInCliMode(cli, cmd, True, lang)
        cli_ret = execute_ret[1]
        cli_ret_lines_list = cli_ret.splitlines()
        cache_capacity_list = []

        for line in cli_ret_lines_list:
            fields = line.split(":")
            if len(fields) < 2:
                continue

            field_name = fields[0].strip()
            field_value = fields[1].strip()

            if field_name == "Cache Capacity":
                cache_capacity_list.append(field_value)

        if self.check_capacity_valid(cache_capacity_list):
            return True, cache_capacity_list[0]

        return False, ""

    def check_capacity_valid(self, cache_capacity_list):
        if len(cache_capacity_list) == 0:
            self.log.info("cannot get cacheCapacity info.")
            return False
        if len(set(cache_capacity_list)) != 1:
            self.log.info("cache capacity is diffrent.")
            return False
        return True

    def get_upload_path(self, is_support_rest):
        """
        获取上传路径，会触发清理缓存
        :return:
        """
        if not is_support_rest:
            # 命令层兼容rest和tlv。当老版本不支持rest时，使用tlv。
            self.log.info("not support rest, get upload path use tlv")
            return self.get_upload_path_by_tlv()

        return self.get_upload_path_by_rest()

    def get_upload_path_by_rest(self):
        try:
            params = []
            pkg_type = constants.TLV_PACKAGE_TYPE.HOT_PATCH_PKG
            param_0 = (
                restData.Upgrade.GetPackageUploadPath.CMO_PACKAGE_TYPE,
                UnsignedInt32(pkg_type),
            )
            param_1 = (
                restData.Upgrade.GetPackageUploadPath.CMO_PACKAGE_SIZE,
                UnsignedInt32(DEFAULT_PATCH_SIZE),
            )
            params.extend([param_0, param_1])
            rest = contextUtil.getRest(self.data_dict)
            result = Tlv2Rest.execCmd(
                rest, restData.TlvCmd.OM_MSG_OP_GET_PACKAGE_UPLOADPATH, params
            )
            self.log.info("get upload path success:{}".format(result))
            return result
        except (ToolException, Exception):
            return []

    def get_upload_path_by_tlv(self):
        """
        获取上传路径，会触发清理缓存
        :return:
        """
        try:
            tlv_conn = self.data_dict.get("tlv")
            pkg_type = constants.TLV_PACKAGE_TYPE.HOT_PATCH_PKG
            param0 = Param(0, ParamType.UNSIGN_INT, UnsignedInt32(pkg_type))
            param1 = Param(1, ParamType.STRING, "")
            param2 = Param(2, ParamType.UNSIGN_INT, UnsignedInt32(0))
            params = TLVUtils.paramList(param0, param1, param2)
            # 获取补丁包上传路径
            recs = tlv_conn.invoke(
                TLV_CMD.OM_MSG_OP_GET_PACKAGE_UPLOADPATH,
                params,
                CMD_DEFAULT_TIMEOUT,
            )
            self.log.info("get upload path by tlv success:{}".format(recs))
            return recs
        except (ToolException, Exception):
            self.log.error(
                "get upload by tlv: ignore result{}".format(
                    traceback.format_exc()
                )
            )
            return []
