# -*- coding: UTF-8 -*-
from com.huawei.ism.tlv.lang import UnsignedInt32
import tarfile
import os
import re
import shutil
from common import resourceParse
from common.constant import *
from common.baseFactory import log
from frame.context import contextUtil
from frame.rest.restUtil import Tlv2Rest
from frame.rest import restData
from common.baseFactory import deleteTempPatch
from common import cliUtils
from frame.base import config
from common.baseFactory import safeSleep
from cbb.business.checkitems import patch_ver_match_check
from service.patch_multi_service import PatchMultiPackagesManager
from hotpatch.FSConfigCheck import FSConfigCheck
from com.huawei.ism.tool.devicepatch.utils import DevicePatchUtil
from cbb.frame.util.tar_util import decompress_tar_special_file, UnZipLimit,\
    get_safe_entry_name

patchConfigKeyDict = {
    "2100 V3": config.PatchKey.NO_NAS,
    "2600 V3": config.PatchKey.NAS,
    "2200 V3 8.000GB": config.PatchKey.NO_NAS,
    "2200 V3 16.000GB": config.PatchKey.NAS,
    "2600 V3 Enhanced": config.PatchKey.NAS,
    "2200 V3 Enhanced 8.000GB": config.PatchKey.NO_NAS,
    "2200 V3 Enhanced 16.000GB": config.PatchKey.NAS,
}

PATCH_CONFIG_DEVTYPE_26_V = "2600 V3 for Video"
# 支持NAS的版本号：V300R006C20及之后版本；之前的版本支持NoNAS
PATCH_KEY_FOR_26_V_VER_RANGE = ["V300R006C20", "V300R006C60"]

PATCH_KEY_FOR_26_V_VER_DICT = {"V300R006C20": "V300R006C20SPH030",
                               "V300R006C50SPC100": "V300R006C50SPH125"}


def isSpecialPkgVersion(devType, productVersion):
    typeEnum = ['2200 V3', "2100 V3", '2600 V3', '2600 V3 for Video',
                '2200 V3 Enhanced', '2600 V3 Enhanced']
    need_unzip_least_version = 'V300R006C00SPC100'
    if devType in typeEnum and "V300" in productVersion and productVersion >= need_unzip_least_version:
        return True
    return False


def getPatchPKGSubPatchPath(dataDict, filePath):
    """
    功能说明：解析补丁包，判断是否为多补丁压缩包（是否存在 version.conf文件），如果则获取阵列数字版本号，若数字版本号与version.conf匹配则返回子补丁文件路径
   输入：上下文，多补丁压缩包路径。
    @return: (falg, Val)
        flag:
            True: 包括不需要处理的版本  +  空字符串， 或正确解析出阵列匹配的子补丁路径
            False: 补丁文件格式错误  +  错误消息资源ID
    """
    subPatchPath = ''
    packageValid = False
    versionConf = 'version.conf'
    context = dataDict.get("context")

    # 限定特定版本和设备类型
    dev = dataDict.get('dev')
    deviceSN = str(dev.getDeviceSerialNumber())
    devType = str(dev.getDeviceType())
    productVersion = dev.getProductVersion()
    log.info(dataDict,
             'devType=%s,productVersion=%s' % (devType, productVersion))
    if not isSpecialPkgVersion(devType, productVersion):
        return (True, "")

    # 如果路径存在则删除，用于补丁路径修改路径
    patchPathKey = 'patch_dir_%s' % deviceSN
    deleteTempPatch(dataDict)

    extractPath = filePath[0: filePath.index('.tgz')]
    extractPath = "%s_%s" % (extractPath, deviceSN)
    context[patchPathKey] = extractPath
    log.info(dataDict, 'extractPath=%s' % extractPath)
    if os.path.isdir(extractPath):
        try:
            shutil.rmtree(extractPath, ignore_errors=True)
        except Exception as e:
            log.error(dataDict, 'failed=%s' % unicode(e))

    try:
        # 查看热补丁包是否有效及2600V3 for video新的补丁包
        packageValid, is_old_pakge = check_pkg_valid_or_is_old_pkg(filePath, versionConf, extractPath)
        safeSleep(2)
        # 补丁包无效，直接返回为空
        if not packageValid:
            log.warn(dataDict, 'not find version.conf, its not a mult hotpatch')
            return (True, "")
        packageValid = False
        log.info(dataDict, 'find %s' % versionConf)

        retGetCacheCapacity = (True, "")
        patch_config_key = ""

        if devType == PATCH_CONFIG_DEVTYPE_26_V:
            flag, error_key, patch_config_key = get_patch_key_2600_v(is_old_pakge, dataDict, productVersion)
            if not flag:
                return False, error_key
        elif devType in patchConfigKeyDict.keys():
            patch_config_key = patchConfigKeyDict.get(devType)
        else:
            retGetCacheCapacity = getCacheCapacity(dataDict)
            if not retGetCacheCapacity[0]:
                return (False,
                        "HotPatchCheck.notpass.multpkgGetCacheCapacitySupportedErr")
            patch_config_key = patchConfigKeyDict.get("{} {}".format(devType, retGetCacheCapacity[1]))

        if not patch_config_key:
            log.info(dataDict,
                     'No matching to patch information; devType=[{}],CacheCapacity=[{}].'.format(
                         devType, retGetCacheCapacity[1]))
            return (False,
                    "HotPatchCheck.notpass.multpkgGetCacheCapacitySupportedErr")

        # 解析补丁配置文件
        confFile = os.path.join(extractPath, versionConf)
        if len(
                confFile) > config.PATCH_PATH_SAFE_LEN - config.PATCH_PKG_NAME_LEN:
            return (False, "HotPatchCheck.notpass.multpkgetPathTooLongErr")
        conFileFd = open(confFile, "r")
        confInfo = conFileFd.read()
        # 关闭文件句柄
        conFileFd.close()
        line_list = confInfo.splitlines()

        # 从配置文件中查找版本号是否与产品版本匹配
        patch_file_tmp = get_patch_file_tmp(line_list, patch_config_key)
        log.info(dataDict, 'find patchFileTmp = %s'.format(patch_file_tmp))
        if patch_file_tmp == "":
            return (False, "HotPatchCheck.notpass.multpkSysVerNotMatchErr")

        # 解压补丁文件
        # 普通热补丁包：patch.conf文件在第一层压缩目录下
        packageValid, _ = decompress_tar_special_file(filePath, extractPath, patch_file_tmp)
        if packageValid:
            subPatchPath = os.path.join(extractPath, patch_file_tmp)
            if len(subPatchPath) > config.PATCH_PATH_SAFE_LEN:
                return False, "HotPatchCheck.notpass.multpkgetPathTooLongErr"
        if subPatchPath == "":
            return (False, "HotPatchCheck.notpass.multpkSysVerNotMatchErr")
    except Exception as e:
        packageValid = False
        log.error(dataDict, 'failed=%s' % unicode(e))
        return (False, "HotPatchCheck.notpass.multpkErr")
    finally:
        if not packageValid:
            try:
                shutil.rmtree(extractPath, ignore_errors=True)
            except Exception as e:
                log.error(dataDict, 'failed=%s' % unicode(e))
        try:
            if conFileFd:
                conFileFd.close()
        except Exception as e:
            log.error(dataDict, 'failed=%s' % unicode(e))

    log.info(dataDict, 'find subPatchPath=%s' % subPatchPath)
    return (True, subPatchPath)


def decompress_version_tar_file(tar_obj, extract_path, version_conf):
    def check_is_ocean_stor_2600v3_for_video(tar_name):
        return "OceanStor2600V3forVideo" in tar_name

    def decompress_one_tar_obj(tar_sub_file):
        # 校验文件大小，以防ZIP炸弹，没问题则解压
        limit.increase_file_size(tar_sub_file.size)
        if limit.is_file_size_over_limit():
            return False
        tar_obj.extract(get_safe_entry_name(tarSubFile.name), extract_path)
        return True

    package_valid = False
    is_old_pkg = True
    limit = UnZipLimit()
    for tarSubFile in tar_obj:
        # 普通热补丁包：patch.conf文件在第一层压缩目录下
        if tarSubFile.name == version_conf:
            package_valid = decompress_one_tar_obj(tarSubFile)
            if not package_valid:
                break
        if check_is_ocean_stor_2600v3_for_video(tarSubFile.name):
            is_old_pkg = False
    return package_valid, is_old_pkg


def check_pkg_valid_or_is_old_pkg(file_path, version_conf, extract_path):
    # 查看热补丁包是否有效
    with tarfile.open(file_path, "r:gz") as tar_obj:
        # 判断补丁包类型。如OceanStor2600V3forVideo为新的补丁包。
        return decompress_version_tar_file(tar_obj, extract_path, version_conf)


def get_patch_file_tmp(line_list, patch_config_key):
    patch_file_tmp = ""
    for line in line_list:
        field = line.split()
        if len(field) < 2:
            continue
        # 获取版本配置信息
        if field[0] != "PatchConfig":
            continue

        pconfig_arr = field[1].split(':')
        if len(pconfig_arr) < 2:
            continue
        is_nas = pconfig_arr[0]
        patch_pkg_name = pconfig_arr[1]
        patch_config_key_tmp = str(is_nas).strip()
        if patch_config_key_tmp == patch_config_key:
            patch_file_tmp = str(patch_pkg_name).strip()
            break
    return patch_file_tmp


def get_patch_key_2600_v(is_old_pkg, data_dict, product_version):
    # 获取2600 V3 for Video设备的补丁包的配置key
    patch_config_key = ""
    if is_old_pkg:
        return get_patch_key_old_pkg(data_dict, product_version)
    else:
        if product_version in PATCH_KEY_FOR_26_V_VER_DICT:
            log.info(data_dict, '2600 V3 for Video new patch package.')
            flag, error_key, _, patch_config_key = FSConfigCheck(data_dict).get_new_patch_key()
            if not flag:
                return False, error_key, patch_config_key
        return True, "", patch_config_key


def get_patch_key_old_pkg(data_dict, product_version):
    lang = data_dict.get("lang")
    resource = resourceParse.execute(lang)
    log.info(data_dict, '2600 V3 for Video old patch package.')
    if product_version in PATCH_KEY_FOR_26_V_VER_DICT:
        # rec是用户选择的结果，True选择的“是”，False选择的是“否”是否安装提示的补丁，是则终止，否则继续。
        waring_msg = resource.get(
            'FSConfigCheck.file.system.install.patch.alarm') \
            .format(product_version, PATCH_KEY_FOR_26_V_VER_DICT.get(product_version))
        choose = DevicePatchUtil.showYesNoDialog(waring_msg, "Yes", "No")
        log.info(data_dict,
                 "[getPatchPKGSubPatchPath] User choose: {}.".format(choose))
        if choose:
            return False, "FSConfigCheck.file.system.select.patch", ""
    flag, error_key, _, patch_config_key = FSConfigCheck(
        data_dict).get_patch_key()
    if not flag:
        return False, error_key, patch_config_key
    return True, "", patch_config_key

# *************************************************************#
# 函数名称: getCacheCapacity
# 功能说明: 获取设备内存规格
# 其 他   :  无
# *************************************************************#  
def getCacheCapacity(dataDict):
    cli = dataDict["ssh"]
    cmd = 'show controller general |filterColumn include columnList=Cache\sCapacity'
    excuteRet = cliUtils.excuteCmdInCliMode(cli, cmd, True)
    log.info(dataDict, 'excuteRetMsg=%s' % excuteRet[2])
    cliRet = excuteRet[1]
    cliRetLinesList = cliRet.splitlines()
    cacheCapacityList = []

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

        fieldName = fields[0].strip()
        fieldValue = fields[1].strip()

        if fieldName == "Cache Capacity":
            cacheCapacity = fieldValue
            cacheCapacityList.append(cacheCapacity)

    if len(cacheCapacityList) == 0:
        log.info(dataDict, "cannot get cacheCapacity info.")
        return (False, cliRet)

    if len(set(cacheCapacityList)) != 1:
        return (False, cliRet)

    return (True, cacheCapacityList[0])


# *************************************************************#
# 函数名称: getHotPatchPKGVersion
# 功能说明: 解压热补丁包的配置文件，获取热补丁包版本号配套关系
# 其 他   :  无
# *************************************************************#  
def getPatchPKGVersion(data_dict, filePath):
    """
    功能说明：解析补丁包，获取补丁包类型、热补丁版本、热补丁B版本、异构补丁名称和异构补丁版本
   输入：补丁包路径。
  输出：bool解析成功结果False/True，dict获取的补丁包信息(patchType、hotPatchPkgVersion、SysVersionBSupported、AslName、AslVersion)
    """
    packageVersion = {}
    packageValid = False

    extractPath = filePath[0: filePath.index('.tgz')]

    try:
        # 查看热补丁包是否有效
        # 普通热补丁包：patch.conf文件在第一层压缩目录下
        # 补丁包无效，直接返回为空
        packageValid, _ = decompress_tar_special_file(filePath, extractPath, 'patch.conf')
        if not packageValid:
            log.warn(data_dict, 'not find patch.conf, its not a mult hotpatch')
            return False, "", "", ""

        # 解析补丁配置文件
        confFile = extractPath + os.sep + 'patch.conf'
        conFileFd = open(confFile, "r")
        confInfo = conFileFd.read()
        # 关闭文件句柄
        conFileFd.close()
        lineList = confInfo.splitlines()

        # 获取配置文件中的热补丁版本号
        for line in lineList:

            field = line.split()
            if len(field) < 2:
                continue
            # 获取版本配置信息
            if field[0] == "Version":
                packageVersion['hotPatchPkgVersion'] = field[1]
            elif field[0] == "PATCHTYPE":
                packageVersion['patchType'] = field[1]
            elif field[0] == "SysVersionBSupported":
                packageVersion['SysVersionBSupported'] = field[1]
            elif field[0] == 'Asl_Version':
                packageVersion['AslVersion'] = field[1]
            elif field[0] == 'Asl_Name':
                packageVersion['AslName'] = field[1]
            elif field[0] == 'SysVersionSupported':
                packageVersion['SysVersionSupported'] = field[1]
            else:
                continue
    except Exception as e:
        log.error(data_dict, 'parse patch.conf config happened exception, failed={}'.format(str(e)))
        packageValid = False
    finally:
        shutil.rmtree(filePath[0:-4], ignore_errors=True)

    return (packageValid, packageVersion)


def getHotPatchCurVersion(dataDict):
    """
    功能说明：查询当前阵列已安装的热补丁版本号
    输入：工具框架上下文
    返回：bool查询成功结果False/True，str热补丁版本号
    """
    rest = contextUtil.getRest(dataDict)
    params = []
    param0 = (restData.Upgrade.LstVer.CMO_VER_PACKAGE_TYPE,
              UnsignedInt32(TLV_PACKAGE_TYPE.HOT_PATCH_PKG))
    params.append(param0)
    recs = Tlv2Rest.execCmd(rest, restData.TlvCmd.OM_MSG_OP_LST_VER, params)
    rec = recs[0]
    log.info(dataDict, 'TLV cmd [%d] send[%s] receive[%s]' % (
              restData.TlvCmd.OM_MSG_OP_LST_VER.get('cmd'), str(params), str(rec)))
    curHotPatchVersion = Tlv2Rest.getRecordValue(rec,
                                                 restData.Upgrade.LstVer.CMO_VER_CUR_VERSION)
    if not curHotPatchVersion:
        log.error(dataDict, 'Get curHotPatchVersion failed')
        return (True, '--')
    return (True, curHotPatchVersion)


def getAslPatchCurVersion(dataDict):
    """
    功能说明：查询当前阵列已安装的异构补丁版本号
    输入：工具框架上下文
    返回：bool查询成功结果False/True，str异构补丁版本号
    """
    curAslPatchVersions = ''
    rest = contextUtil.getRest(dataDict)

    params = []
    param0 = (restData.Upgrade.LstVer.CMO_VER_PACKAGE_TYPE,
              UnsignedInt32(TLV_PACKAGE_TYPE.ASL_PATCH_PKG))
    params.append(param0)
    recs = Tlv2Rest.execCmd(rest, restData.TlvCmd.OM_MSG_OP_LST_VER, params)
    log.info(dataDict, 'TLV cmd [%d] send[%s] receive[%s]' % (
              TLV_CMD.OM_MSG_OP_LST_VER, str(params), str(recs)))
    itemNum = recs.size()
    for index in range(0, itemNum):
        curAslPatchVersioninfo = recs.get(index)
        if index == 0:
            result = Tlv2Rest.getRecordValue(curAslPatchVersioninfo,
                                             restData.Upgrade.LstVer.CMO_VER_PACKAGE_TYPE)
            log.info(dataDict, 'getAslPatchCurVersion result:' + str(result))
            if result != 0:
                log.error(dataDict,
                          'Get AslPatchCurVersion failed   result:' + str(
                              result))
                return (True, curAslPatchVersions)
        else:
            AslPatchName = Tlv2Rest.getRecordValue(curAslPatchVersioninfo,
                                                   restData.Upgrade.LstVer.CMO_VER_CUR_VERSION)
            log.info(dataDict, 'AslPatchName:' + AslPatchName)
            AslPatchVersion = Tlv2Rest.getRecordValue(curAslPatchVersioninfo,
                                                      restData.Upgrade.LstVer.CMO_VER_SAVED_VERSION)
            log.info(dataDict, 'AslPatchVersion:' + AslPatchVersion)
            curAslPatchVersions += AslPatchName + ' ' + AslPatchVersion + '; '
    return (True, curAslPatchVersions)


# *************************************************************#
# 函数名称: execute
# 功能说明: 解析热补丁包所在路径，返回字典：热补丁版本号+对应的文件名(入口函数)
# 其 他   :  无
# *************************************************************#
def execute(dataDict):
    """
          函数名称: execute
         功能说明: 解析热补丁包所在路径，返回字典：补丁包版本号+当前阵列补丁版本号(入口函数)
        其 他   :  无
    """
    context = dataDict.get("context")
    lang = dataDict.get("lang")
    curPatchVersion = ''
    hotPatchPKGVersion = ''
    hotPatchTgzPkgName = dataDict['packagePath']
    deviceSN = str(dataDict.get('dev').getDeviceSerialNumber())
    checkDict = {}
    # 解析resource文件并保存
    resource = resourceParse.execute(lang)
    dataDict["resource"] = resource
    errMsg = ''
    if False == os.path.isfile(hotPatchTgzPkgName):
        log.error(dataDict, 'patchPath is follow')
        log.error(dataDict, hotPatchTgzPkgName)
        errMsg = resource.get('upload.pkgPathAbnormality')
        return (False, errMsg, checkDict)

    # 检查是否为多补丁压缩包，判断是否存在 version.conf 文件
    packagePathKey = "%s_packagePath" % deviceSN
    context = dataDict.get("context")
    context[packagePathKey] = ""  # 初始化为空
    iRet = getPatchPKGSubPatchPath(dataDict, hotPatchTgzPkgName)
    if iRet[0]:
        if len(iRet[1]) > 1:
            hotPatchTgzPkgName = iRet[1]
            context[packagePathKey] = hotPatchTgzPkgName
    else:
        errMsgID = iRet[1]
        log.info(dataDict, "not a valid mul hotPatchPackge: %s" % errMsgID)
        errMsg = resource.get(errMsgID)
        return False, errMsg, checkDict
    log.info(dataDict, "hotPatchTgzPkgName = %s" % hotPatchTgzPkgName)

    manager = PatchMultiPackagesManager(dataDict, hotPatchTgzPkgName)
    if manager.is_multi_patch_package():
        if not manager.is_valid_multi_package():
            log.error(dataDict, 'Patch multi package invalid')
            return False, resource.get('upload.pkgInvalid'), checkDict
        hotPatchTgzPkgName = manager.get_last_patch_package()
        log.info(dataDict, "multi patch packages")
    # 获取补丁包信息
    iRet = getPatchPKGVersion(dataDict, hotPatchTgzPkgName)
    manager.del_child_directory()
    packageValid = iRet[0]
    if not packageValid:
        log.error(dataDict, 'Patch package invalid')
        errMsg = resource.get('upload.pkgInvalid')
        return False, errMsg, checkDict

    packageVersion = iRet[1]
    pkgVersion = packageVersion.get('hotPatchPkgVersion')
    patchType = packageVersion.get('patchType')
    pkgSystemSpcVerComp = packageVersion.get('SysVersionBSupported')
    AslVersion = packageVersion.get('AslVersion')
    AslName = packageVersion.get('AslName')

    # 判断补丁类型（热补丁、异构补丁）如果为空则为热补丁
    if not patchType:
        patchType = PATCH_TYPE.HOT_PATCH
        # we just need the version like this: VxxxRxxxCxx
        sysVersion = re.compile(r'^V\d{0,3}R\d{0,3}C\d{0,2}')
        if not pkgSystemSpcVerComp or not pkgVersion:
            errMsg = resource.get('upload.pkgAbnormality')
            return (False, errMsg, checkDict)
        tempResult = sysVersion.search(pkgSystemSpcVerComp)
        if tempResult is None:
            pkgSystemSpcVerComp = ""
        else:
            pkgSystemSpcVerComp = tempResult.group()

    elif patchType != PATCH_TYPE.ASL_PATCH:
        log.error(dataDict, u'获取补丁包类型异常   PATCH_TYPE:' + patchType)
        errMsg = resource.get('upload.pkgTypeAbnormality')
        return (False, errMsg, checkDict)

    # 检查-1补丁的VRC版本是否和阵列的VRC版本匹配
    flag, err_msg = patch_ver_match_check.execute(dataDict, packageVersion)
    if not flag:
        return False, err_msg, checkDict

    # 查询当前阵列安装的补丁版本号
    if patchType == PATCH_TYPE.HOT_PATCH:
        ret = getHotPatchCurVersion(dataDict)
        if not ret[0]:
            log.error(dataDict, 'gethotpatchversion failed')
        else:
            curPatchVersion = ret[1]
        hotPatchPKGVersion = pkgSystemSpcVerComp + pkgVersion
    else:
        if not AslName or not AslVersion:
            errMsg = resource.get('upload.pkgAbnormality')
            return (False, errMsg, checkDict)
        ret = getAslPatchCurVersion(dataDict)
        if not ret[0]:
            log.error(dataDict, 'getaslpatchversion failed')
        else:
            curPatchVersion = ret[1]
        hotPatchPKGVersion = AslName + ' ' + AslVersion
    # 将补丁类型放入框架公共字典
    context["patchType_%s" % deviceSN] = patchType
    # 填充返回字典
    checkDict = {"hotPatchVersion": hotPatchPKGVersion,
                 'curPatchVersion': curPatchVersion}
    # 检查结果返回
    return (True, errMsg, checkDict)
