# coding=utf-8
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import re

import utils.common.log as logger
from utils.DBAdapter.DBConnector import BaseOps
from utils.common.ssh_util import Ssh as SshUtil
from utils.common.error.hcci_error_code import get_code_msg
from utils.business.project_condition_utils import get_project_condition_boolean
from plugins.DistributedStorage.logic.deploy_operate import FusionStorageOperate
from plugins.DistributedStorage.utils.common.deploy_constant import DeployConstant
from plugins.ResourceCheck.common.libs.BMCCmd import BMCCmdExc
from plugins.DistributedStorage.Deploy.scripts.PreCheck.implement.job_disk_check import DiskCheck


class CheckPoolNodeSSD(object):
    def __init__(self, project_id, pod_id, fs_args):
        self.project_id = project_id
        self.pod_id = pod_id
        self.db = BaseOps()
        self.cmd_exe = BMCCmdExc()
        self.fs_args = fs_args
        self.fsm_float_ip = fs_args.get('fsm_float_ip')
        self.fsm_admin_passwd = fs_args.get('fsm_admin_password')
        self.ex_osd_list = fs_args.get('ex_osd_list')
        self.operate = FusionStorageOperate(float_ip=self.fsm_float_ip)
        self.exp_sep_node = get_project_condition_boolean(self.project_id, "ExpansionServiceStorage&TenantStorNewNode")
        self.error_dic = {'not_exit_ssd_card_node': [], 'multiple_ssd_card_type_node': [],
                          'different_cache_type_node': [], 'different_nvme_cache_type_node': [],
                          'not_exit_ssd_disk_node': []}
        self.nvme_cache_disk_vendor = set()
        self.ssd_card = "ssd_card"
        self.ssd_disk = "ssd_disk"

    @staticmethod
    def get_bmc_node_ssd(bmc_client, bmc_ip):
        """
        获取bmc节点实际SSD类型
        :return: ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        """
        node_ssd_type = list()
        for nvme_type in list(DeployConstant.NVME_ID_MAP.values()):
            nvme_condition = DeployConstant.NVME_TYPE_CONDITION.get(nvme_type)
            cmd = nvme_condition.get("cmd")
            result = bmc_client.run([bmc_ip], cmd)
            if result[bmc_ip]["result"] != "0":
                logger.error("Failed to get [%s] nvme from node[%s]." % (
                    nvme_type, bmc_ip))
                continue
            logger.info("bmc node: %s, nvme type:%s" % (bmc_ip, nvme_type))
            node_ssd_type.append(nvme_type)
        if not node_ssd_type:
            err_msg = "node %s has no nvme ssd or connection timed out." % bmc_ip
            logger.error(err_msg)
        return node_ssd_type

    @staticmethod
    def get_node_ssd(node):
        """
        通过管理ip登录ssh_client查询节点ssd类型
        :return: ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        """
        nvme_type_list = list()
        om_ip = node.get("manageIp")
        account_info = node["creuser"].split(',')
        user = account_info[2]
        passwd = account_info[3]
        ssh_client = SshUtil.ssh_create_client(om_ip, user, passwd)
        type_list = ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        for nvme_type in type_list:
            logger.info("Check whether the node[%s] contains the [%s] type." % (om_ip, nvme_type))
            type_condition = DeployConstant.NVME_TYPE_CONDITION.get(nvme_type)
            cmd = type_condition.get("cmd")
            pattern = type_condition.get("pattern")
            ret_cmd = SshUtil.ssh_exec_command_return(ssh_client, cmd)
            logger.info("Run query cmd[%s],result:%s" % (cmd, str(ret_cmd)))
            ret_cmd = "".join(ret_cmd)
            if re.findall(pattern, ret_cmd):
                nvme_type_list.append(nvme_type)
                logger.info("Expanded node: %s, nvme type:%s" % (om_ip, nvme_type))
        SshUtil.ssh_close(ssh_client)
        return list(set(nvme_type_list))

    def procedure(self):
        server_list = self.ex_osd_list
        if not server_list:
            logger.info("No osd node needs to be added to the storage pool.")
            return

        self.operate.login(DeployConstant.DM_LOGIN_USER, self.fs_args.get('fsm_admin_password'))
        try:
            pool_id, cache_type, target_pool_nodes = self.get_exist_pool_info()
            if cache_type == self.ssd_card:
                self.get_nvme_cache_disk_vendor(target_pool_nodes)
        finally:
            self.operate.logout()

        logger.info("The target pool id : %s cache type : %s" % (pool_id, cache_type))
        self.check_cache_type_handle(cache_type, server_list, target_pool_nodes)

    def check_cache_type_handle(self, cache_type, server_list, target_pool_nodes):
        pool_nvme_type = list()
        for node in server_list:
            bmc_client = BMCCmdExc()
            bmc_ip = node.get("bmc_ip")
            bmc_cache_type = node.get("cache_type")
            if bmc_cache_type != cache_type:
                err_msg = "The cache type of the target storage pool[%s] is inconsistent with " \
                          "that of the node[%s] to be added." % (cache_type, bmc_cache_type)
                logger.error(err_msg)
                self.error_dic.get('different_cache_type_node').append(bmc_ip)
                continue
            if bmc_cache_type == self.ssd_card:
                # 管理存储支持不同型号 NVMe 共池，故只检查业务存储
                if not self.exp_sep_node:
                    continue
                node_nvme_type = self.get_nvme_type(bmc_client, bmc_ip, node)
                if not node_nvme_type:
                    err_msg = "The node %s has no NVMe, check whether the cache type in the LLD is correct." % bmc_ip
                    logger.error(err_msg)
                    self.error_dic.get('not_exit_ssd_card_node').append(bmc_ip)
                    continue
                self.check_nvme_type(bmc_ip, node_nvme_type)
            elif bmc_cache_type == self.ssd_disk:
                self.check_ssd_disk(bmc_client, bmc_ip)
            else:
                logger.info("none cache type, bmc[%s], bmc_cache:%s " % (bmc_ip, bmc_cache_type))
        self.error_dict_handler(cache_type, pool_nvme_type)

    def check_ssd_disk(self, bmc_client, bmc_ip):
        ssd_disk_list = DiskCheck(self.project_id, self.pod_id).get_ssd_disk(bmc_client, bmc_ip)
        ssd_num = len(ssd_disk_list)
        if ssd_num <= 0:
            logger.error("Not found sas ssd or stata ssd on node[%s]" % bmc_ip)
            self.error_dic.get('not_exit_ssd_disk_node').append(bmc_ip)

    def error_dict_handler(self, cache_type, pool_nvme_type):
        if self.error_dic.get('different_cache_type_node'):
            self.error_dic['different_cache_type_node'] = \
                get_code_msg(627611) % (self.error_dic.get('different_cache_type_node'), cache_type)
        if self.error_dic.get('different_nvme_cache_type_node'):
            self.error_dic['different_nvme_cache_type_node'] = \
                get_code_msg(627312) % (pool_nvme_type, self.error_dic.get('different_nvme_cache_type_node'))
        if self.error_dic.get('not_exit_ssd_card_node'):
            self.error_dic['not_exit_ssd_card_node'] = get_code_msg(627601) % self.error_dic.get(
                'not_exit_ssd_card_node')
        if self.error_dic.get('multiple_ssd_card_type_node'):
            self.error_dic['multiple_ssd_card_type_node'] = \
                get_code_msg(627612) % self.error_dic.get('multiple_ssd_card_type_node')
        if self.error_dic.get('not_exit_ssd_disk_node'):
            self.error_dic['not_exit_ssd_disk_node'] = get_code_msg(627602) % self.error_dic.get(
                'not_exit_ssd_disk_node')
        all_error_nodes = [value.strip() for value in self.error_dic.values() if value]
        if all_error_nodes:
            logger.error(all_error_nodes)
            raise Exception(all_error_nodes)

    def check_nvme_type(self, bmc_ip, node_nvme_type):
        if len(node_nvme_type) != 1:
            err_msg = "NVMe type of the same node to be expanded must be the same."
            logger.error(err_msg)
            self.error_dic.get('multiple_ssd_card_type_node').append(bmc_ip)
        elif not set(node_nvme_type).issubset(self.nvme_cache_disk_vendor):
            err_msg = "The ssd of the storage pool : %s, the node to be expanded: %s" \
                      % (self.nvme_cache_disk_vendor, node_nvme_type)
            logger.error(err_msg)
            self.error_dic.get('different_nvme_cache_type_node').append((bmc_ip, node_nvme_type))

    def get_nvme_type(self, bmc_client, bmc_ip, node):
        if get_project_condition_boolean(self.project_id, '!TenantStorFB_Heterogeneous'):
            node_nvme_type = self.get_bmc_node_ssd(bmc_client, bmc_ip)
        # 手动装机场景
        else:
            node_nvme_type = self.get_node_ssd(node)
        return node_nvme_type

    def get_exist_pool_info(self):
        """
        获取原有存储池信息
        """
        logger.info("Query storage pool data.")
        res_pool = self.operate.query_storage_pool()
        pool_data = res_pool.get_query_data()
        pool_info_list = pool_data.get('storagePools')
        if len(pool_info_list) == 0:
            logger.error('Check pool fail...')
            raise Exception("Check pool fail...")
        target_pool_name = self.ex_osd_list[0].get("storage_pool_name_and_slot")
        if not target_pool_name and len(pool_info_list) == 1:
            target_pool_name = self.fs_args.get("default_pool_name")
        pool_info = {}
        if target_pool_name:
            logger.info("The name of the target storage pool is : %s." % target_pool_name)
            for pool in pool_info_list:
                if pool.get("poolName") == target_pool_name:
                    pool_info = pool
                    break
        else:
            err_msg = "No storage pool name is available in the service storage scenario."
            logger.error(err_msg)
            raise Exception(err_msg)
        if not pool_info:
            err_msg = "No storage pool named %s is found in the cluster." % target_pool_name
            logger.error(err_msg)
            raise Exception(err_msg)
        cache_type = pool_info.get('cacheMediaType')
        pool_id = pool_info.get('poolId')
        rsp_data = self.operate.query_storage_node_by_pool_id(str(pool_id))
        pool_data = rsp_data.get_query_data()
        node_data_list = pool_data.get('nodeInfo')
        node_mgr_ip_list = [node.get('nodeMgrIp') for node in node_data_list]
        return pool_id, cache_type, node_mgr_ip_list

    def get_nvme_cache_disk_vendor(self, node_mgr_ip_list):
        all_disk_info = self.operate.query_all_disk().get_query_data()
        disks = all_disk_info.get("disks")
        if not disks:
            logger.error("all disk info:{}".format(all_disk_info))
            raise Exception("Failed to query the disk information of the storage pool.")
        for node_ip in node_mgr_ip_list:
            for disk_info in disks.get(node_ip):
                if disk_info.get('devRole') == "osd_cache":
                    self.nvme_cache_disk_vendor.add(DeployConstant.NVME_ID_MAP.get(disk_info.get("vendor")))
        logger.info("Cache disk vendor in the storage pool:{}".format(self.nvme_cache_disk_vendor))
