# -*- coding: UTF-8 -*-
from com.huawei.ism.tlv.lang import UnsignedInt32
from java.lang import Exception as JException
from com.huawei.ism.exception import IsmException
from com.huawei.ism.tool.protocol.tlv.exception import TLVException

from common import resourceParse, constant, cliUtils, configAutoSwitcher, \
    upload
from common.baseFactory import log, threadUp, finishProcess
from common.configAutoSwitcher import ExecutionException
from common.check_ibase_ism_connection import DefunctProcessCheck
from frame.context import contextUtil
from frame.rest.restUtil import Tlv2Rest
from frame.rest import restData
from service.patch_multi_service import PatchMultiPackagesManager
from hotpatch import PatchCheck

import time
import traceback

totalRemainTime = 317


def execute(data_dict):
    """
    功能说明：
    """
    lang = data_dict['lang']
    """解析resource文件并保存"""
    resource = resourceParse.execute(lang)
    data_dict["resource"] = resource
    altered_item_list = []
    # the instance of ConfigAutoSwitcher
    config_switcher = configAutoSwitcher.ConfigAutoSwitcher(data_dict)
    try:
        item_list = config_switcher.check_unrecommended_switch_list()
        if item_list:
            is_success, altered_item_list, commands = config_switcher \
                .switch_items_2_allow_patching(item_list)
            if not is_success:
                err_msg = resource.get("alter.config.before.patching.failed"
                                       ".rollback") % commands
                failed_items = config_switcher \
                    .recover_configure_status_2_last_modified(
                        altered_item_list)
                if failed_items:
                    err_msg += "\n" + resource \
                        .get("the.following.switch.rollback.failed") % \
                        "\n".join(failed_items)
                else:
                    err_msg += "\n" + resource.get(
                        "config.rollback.successful")
                finishProcess(data_dict, "upgrade")
                return False, err_msg
    except ExecutionException as exp:
        log.error(data_dict,
                  "ExecutionException occurred .\n %s" % exp)
        finishProcess(data_dict, "upgrade")
        return False, resource.get("failed.fetching.config.tobe.altered")
    except JException as exp1:
        log.error(data_dict,
                  "Exception occurred .\n %s" % exp1)
        finishProcess(data_dict, "upgrade")
        return False, resource.get("failed.fetching.config.tobe.altered")
    is_patch_succ, err_msg = False, ""

    package_path = data_dict.get('packagePath')
    manager = PatchMultiPackagesManager(data_dict, package_path)
    try:
        log.info(data_dict, "execute upgrade")
        # 升级前检查状态是否允许升级
        checkList = checkUpgradeState(data_dict)
        if not checkList[0]:
            return False, checkList[1]

        if manager.is_multi_patch_package():
            is_patch_succ, err_msg = manager.install_multi_packages(
                upload.execute, upgrade, PatchCheck.execute, totalRemainTime)
        else:
            # 执行升级
            threadUp(data_dict, "upgrade", totalRemainTime)
            is_patch_succ, err_msg = upgrade(data_dict)

        # 升级后检查CLI连接是否可用
        cliUtils.check_ssh_connection_normal(data_dict.get('ssh'))

        failed_items = config_switcher \
            .recover_configure_status_2_last_modified(altered_item_list)
        log.error(data_dict, "failed to recover items:" +
                  ",".join(failed_items))
        if is_patch_succ and failed_items:
            is_patch_succ = False
            err_msg += "\n" + resource.get(
                "patch.success.but.switch.rollback.failed") % "\n".join(
                failed_items)
        elif not is_patch_succ and failed_items:
            err_msg += "\n" + resource.get(
                "the.following.switch.rollback.failed") % "\n".join(
                failed_items)
    except (JException, Exception) as jexp:
        failed_items = config_switcher \
            .recover_configure_status_2_last_modified(altered_item_list)
        if failed_items:
            err_msg += "businessErrMsg:" + "\n" + resource.get(
                "the.following.switch.rollback.failed") % "\n".join(
                failed_items)
            raise JException(err_msg)
        raise jexp
    finally:
        finishProcess(data_dict, "upgrade")
        manager.del_child_directory()

    return is_patch_succ, err_msg

def checkUpgradeStatusIsNomarl(status):
    """
    功能说明：检查升级中的状态是不是正常的
    输入：升级状态
    输出：bool检查结果False/True
    """
    
    ###检查升级状态是不是处于回退阶段
    if constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_ROLLBACKING == status or constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_ROLLBACK_SUCCESS == status or constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_ROLLBACK_FAIL == status or constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_UPD_FAIL == status:
        return False
    
    return True

def checkUpgradeStatusIsUpgrade(status):
    """
    功能说明：检查升级中的状态是不是升级中和升级成功
    输入：升级状态
    输出：bool检查结果False/True
    """
    ###检查升级状态是否处于升级中和升级成功
    if constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_UPDING == status or constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_UPD_SUCCESS == status:
        return True
    return False

def upgrade(dataDict):
    """
    功能说明：执行升级并轮询升级结果
    输入：工具框架上下文
    输出：bool执行结果False/True，str错误信息
    """
    try:
        resource = dataDict["resource"]
        rest = contextUtil.getRest(dataDict)
        context = dataDict['context']
        deviceSN = str(dataDict.get('dev').getDeviceSerialNumber())
        patchType = context["patchType_%s"%deviceSN]
        msgType = ''
        errMsg = ''
        errorInfo = ''
        
        #用来判断异构包和热补丁包
        if patchType == constant.PATCH_TYPE.ASL_PATCH:
            msgType = constant.CMO_EXE_UPD_MSGTYPE.ASL_PATCH_MSGTYPE
        elif patchType == constant.PATCH_TYPE.HOT_PATCH:
            msgType = constant.CMO_EXE_UPD_MSGTYPE.HOT_PATCH_MSGTYPE
        #在线升级类型都为在线升级
        modelNum = 0
        
        params = []
        param0 = (restData.Upgrade.NotifyExcUpgrade.CMO_EXE_UPD_MSGTYPE, UnsignedInt32(msgType))
        param1 = (restData.Upgrade.NotifyExcUpgrade.CMO_EXE_UPD_NIDLIST, "")
        param2 = (restData.Upgrade.NotifyExcUpgrade.CMO_EXE_UPD_ACTIVETYPE, UnsignedInt32(modelNum))
        params.extend([param0, param1, param2])
        ##执行升级命令 
        Tlv2Rest.execCmd(rest, restData.TlvCmd.EXC_UPD, params)
        time.sleep(5)
      
        ##查询检查进度命令      
        DEVEICE_UP_FALLD_BASE_VALUE = 0 #轮询计数器
        status = None
        while (constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_UPD_SUCCESS != status):
            try:
                DEVEICE_UP_FALLD_BASE_VALUE = DEVEICE_UP_FALLD_BASE_VALUE + 1
                if DEVEICE_UP_FALLD_BASE_VALUE > constant.UPGRADE_PACKAGE_POLL_TIMES:
                    return (False, resource.get("upgrade.faild"))
                #查询升级进度
                recs = Tlv2Rest.execCmd(rest, restData.TlvCmd.OM_MSG_OP_UPD_LST_SYS_PROGRESS, params)
                rec = recs[0]
                if (None != rec):
                    log.info(dataDict,"upgrade process:" + str(rec))
                    status = Tlv2Rest.getRecordValue(rec, restData.Upgrade.UpdLstSysProgress.CMO_UPD_SYS_STEP_TASK_STATUS)
                ###升级状态检查
                if not checkUpgradeStatusIsUpgrade(status):
                    ##查询升级详情
                    log.info(dataDict,"system status is not in upgrading, status is:"+str(status))
                    if not checkUpgradeStatusIsNomarl(status):
                        log.info(dataDict,"system status is not normal.")
                    ##查询升级详细信息
                    recs = Tlv2Rest.execCmd(rest, restData.TlvCmd.OM_MSG_OP_UPD_LIST_DETAILINFO, params=[])
                    itemNum = recs.size()
                    for index in range(0,itemNum):
                        rec = recs.get(index)
                        nameKey = Tlv2Rest.getRecordValue(rec, restData.Upgrade.UpdListDetaiInfo.NAME_KEY)
                        errorKey = Tlv2Rest.getRecordValue(rec, restData.Upgrade.UpdListDetaiInfo.ERROY_KEY)
                        errorKeyList = errorKey.split(":")
                        errorKeyListLen = len(errorKeyList)
                        try:
                            if errorKeyListLen >= 2:
                                firstCommaLoc = errorKey.find(":") #第一个冒号前为key值，后面为错误详情参数，此方法避免参数中存在冒号
                                msgKey = errorKey[:firstCommaLoc]
                                errParam = errorKey[firstCommaLoc+1:]#冒号之后为错误参数信息
                                parseErrorInfoRet = parseAtomInfo(errParam)
                                log.info(dataDict,"msgKey: %s parseErrorInfoRet:%s"%(msgKey, parseErrorInfoRet))                                
                                errorInfo = resource.get(msgKey) %parseErrorInfoRet
                            else:    
                                errorInfo = resource.get(errorKey)
                        except:
                            log.error(dataDict,"parse atom info failed:%s" %errorInfo)
                            errorInfo = resource.get("upgrade.faild")
                        nodeId = Tlv2Rest.getRecordValue(rec, restData.Upgrade.UpdListDetaiInfo.NODE_ID)
                        errMsg += (nodeId + '--' + resource.get(nameKey) + '--' + errorInfo + '\n')
                        
                    return (False, errMsg)
                try:   
                    time.sleep(constant.UPGRADE_PACKAGE_POLL_INTERVAL)
                except KeyboardInterrupt:
                    log.error(dataDict,"Interrupted" )
            except JException as e:
                log.error(dataDict,"device upgrade query progress error"+ str(e)) 
                #第一个原子执行时间较长，会抛出该错误码，继续执行查询
                if isinstance(e, TLVException):
                    if constant.OM_OPERATE_FAIL_CODE == e.getErrorId():
                        continue
                if not checkDevReboot(dataDict):
                    errMsg = resource.get("upgrade.rebooterr")
                    return (False, errMsg)
                else:
                    continue
    except:
        log.error(dataDict,"upgrade error!" +  str(traceback.format_exc()))
        return(False, resource.get("upgrade.faild"))
    return (True, '')

def checkUpgradeState(dataDict):
    """
        功能说明：升级前检查状态是否允许升级
        输入：工具框架上下文
        输出：bool检查结果False/True，str错误信息
    """
    rest = contextUtil.getRest(dataDict)
    lang = dataDict.get('lang')
    """解析resource文件并保存"""
    resource = resourceParse.execute(lang)
    dataDict["resource"] = resource
    #查询状态
    recs = Tlv2Rest.execCmd(rest, restData.TlvCmd.OM_MSG_OP_UPD_LST_SYS_PROGRESS, [], constant.CMD_DEFAULT_TIMEOUT)
    rec = recs[0]
    log.info(dataDict,'TLV cmd [%s] send[%s] receive[%s]'%(restData.TlvCmd.OM_MSG_OP_UPD_LST_SYS_PROGRESS,str([]),str(rec)))
    status = Tlv2Rest.getRecordValue(rec, restData.Upgrade.UpdLstSysProgress.CMO_UPD_SYS_STEP_TASK_STATUS)
    #得到状态值
    
    log.info(dataDict,"current device status is:" + str(status))
    
    #不能执行升级的状态有（升级中，回滚中，同步中）
    if status == constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_UPDING : #升级中判断
        return (False, resource.get("upgrade.upding"))
    elif status == constant.OM_MSG_OP_UPD_LST_SYS_PROGRESS.UPD_ROLLBACKING : #回滚中判断
        return (False, resource.get("upgrade.rollbacking"))
    
    return (True, "")


def checkDevReboot(dataDict):
    """
    功能说明：等待设备重启完成后，服务正常启动，重新建立连接
    输入：工具上下文
    输出：bool连接结果False/True
    """
    #使用cli命令打开TLV连接通道。
    cli = dataDict['ssh']
    
    ##进行UPGRADE_REBOOT_AFTER_CONNECT_TIMES次重试连接，如果连接成功则返回，如果全部没有连接成功在返回失败
    rebootAfterConnectSuc = False
    process_check = DefunctProcessCheck(dataDict)
    for times in range(0, constant.UPGRADE_REBOOT_AFTER_CONNECT_TIMES):
        try:
            cli.reConnect()
            # cli连接不能抛异常。注意捕获ToolException。
            process_check.execute()
            contextUtil.getRest(dataDict)
            rebootAfterConnectSuc = True
            break
        except JException as e:
            try:
                time.sleep(constant.UPGRADE_REBOOT_AFTER_CONNECT_INTERVAL)
            except KeyboardInterrupt:
                log.error(dataDict, "Interrupted" )
            log.error(dataDict,"dev reboot after connect error " + str(times + 1) + " times"+str(e))
        except IsmException as ismE:
            try:
                time.sleep(constant.UPGRADE_REBOOT_AFTER_CONNECT_INTERVAL)
            except KeyboardInterrupt:
                log.error(dataDict, "Interrupted" )
            log.error(dataDict,"dev reboot after connect error " + str(times + 1) + " times"+str(ismE))

    log.info(dataDict,"device reboot moniter finished . isOK:" + str(rebootAfterConnectSuc))
    return rebootAfterConnectSuc


def parseAtomInfo(errParam):
    """
            描述：转换升级原子信息
            原子错误信息格式：参数1,参数2
            参数之间用逗号隔开
            参数：errParam：错误信息字符串
            返回：解析后的错误信息参数元组
    """

    errDetailParas = errParam.split(",")        
    return tuple(errDetailParas)
