# -*- coding: utf-8 -*-
import utils.common.log as logger
from utils.business.hardware_driver_util import DriverApi
from utils.common.error.hcci_error_code import get_code_msg
from utils.business.project_condition_utils import get_project_condition_boolean
from plugins.ResourceCheck.common.libs.BMCCmd import BMCCmdExc
from plugins.DistributedStorage.utils.common.DeployConstant import DeployConstant


class DiskCheck(object):
    def __init__(self, project_id, pod_id):
        self.project_id = project_id
        self.pod_id = pod_id

    @staticmethod
    def get_system_disk(bmc_client, bmc_ip):
        raid_card_flag = DriverApi().check_host_is_hard_raid(bmc_ip)
        if not raid_card_flag:
            raid_card_flag = ''
        sys_cmd = SYS_DISK % raid_card_flag
        disk = ''
        result = bmc_client.run([bmc_ip], sys_cmd)
        if result[bmc_ip]['result'] == '0':
            disk = result[bmc_ip]['stdout'].strip('\n')
        else:
            logger.error("Failed to get disk from node[%s], Detail:%s" % (bmc_ip, result[bmc_ip]['stderr']))
        if disk != '':
            return disk
        else:
            return None

    def get_ssd_disk(self, bmc_client, bmc_ip, required_iops=1000):
        ssd_disk_list = list()
        disk_cmd = "fdisk -l|grep 'Disk /dev/sd'|awk -F: '{print $1}'|awk '{print $2}'|grep -v 'nvme'"
        disk_result = bmc_client.run([bmc_ip], disk_cmd)
        if disk_result[bmc_ip]['result'] != '0':
            logger.error("Failed to get disk from node[%s], Detail:%s" % (bmc_ip, disk_result[bmc_ip]['stderr']))
            return ssd_disk_list
        disk_str = disk_result[bmc_ip]['stdout']
        disk_list = disk_str.strip('\n').split('\n')
        sys_disk = self.get_system_disk(bmc_client, bmc_ip)
        for disk in disk_list:
            disk_name = disk.split('/')[-1]
            if sys_disk and disk_name in sys_disk:
                continue
            iops_cmd = IOPS_COMPUTE_CMD + disk
            iops_result = bmc_client.run([bmc_ip], iops_cmd)
            if iops_result[bmc_ip]['result'] != '0':
                logger.error("Failed to get IOPS of disk[%s] from node[%s], Detail:%s" % (disk, bmc_ip, iops_result[bmc_ip]['stderr']))
                continue
            iops_disk = iops_result[bmc_ip]['stdout'].strip('\n')
            if iops_disk == '':
                logger.error("Failed to get IOPS of disk[%s] from node[%s]" % (disk, bmc_ip))
                continue
            if int(float(iops_disk)) > int(required_iops):
                ssd_disk_list.append('{}:{}'.format(disk_name, iops_disk))
        logger.info("node[%s] ssd:%s" % (bmc_ip, ssd_disk_list))
        return ssd_disk_list

    @staticmethod
    def get_nvme_disk(bmc_client, bmc_ip):
        nvme_num = 0
        # 19e5:0123/19e5:3714/19e5:3754：ES3000 V3/V5/V6
        # 144d:a822/144d:a824：Samsung PM1725b/PM1735
        # 8086:0a54：Intel P4610
        # 1e81:1203/1cc4:1203：白牌A28
        nvme_cmd = "lspci -n | grep -E '19e5:0123|19e5:3714|19e5:3754|144d:a822|144d:a824|8086:0a54" \
                   "|1e81:1203|1cc4:1203|1e3b:1098' | wc -l"
        nvme_result = bmc_client.run([bmc_ip], nvme_cmd)
        if nvme_result[bmc_ip]['result'] != '0':
            logger.error("Failed to get disk from node[%s], Detail:%s" % (bmc_ip, nvme_result[bmc_ip]['stderr']))
            return nvme_num
        nvme_num = nvme_result[bmc_ip]['stdout'].strip('\n')
        logger.info("node[%s] nvme num:%s" % (bmc_ip, nvme_num))
        return int(nvme_num)

    def check_storage_node_cache_disk(self, all_bmc_data):
        bmc_client = BMCCmdExc()
        first_bmc_node = (None, 0, None)
        err_msg = "The number of caches disk at current node[%s] %s is inconsistent with first node[%s] %s."
        error_dic = {'not_exist_ssd_card_node': [], 'different_ssd_card_num_node': [],
                     'not_exist_ssd_disk_node': [], 'different_ssd_disk_num_node': [],
                     'all_flash_disk_num_error': []}
        for bmc_node in all_bmc_data:
            bmc_ip = bmc_node.get('bmc_ip')
            bmc_cache = bmc_node.get('cache_type')
            if bmc_cache == 'ssd_card':
                nvme_num = self.get_nvme_disk(bmc_client, bmc_ip)
                if nvme_num <= 0:
                    logger.error("Not found nvme card or disk on node[%s]" % bmc_ip)
                    error_dic['not_exist_ssd_card_node'].append(bmc_ip)
                elif not first_bmc_node[0]:
                    first_bmc_node = (bmc_ip, nvme_num, bmc_cache)
                elif nvme_num != first_bmc_node[1]:
                    cur_err_msg = err_msg % (bmc_ip, nvme_num, first_bmc_node[0], first_bmc_node[1])
                    logger.error(cur_err_msg)
                    error_dic['different_ssd_card_num_node'].append(bmc_ip)
            elif bmc_cache == 'ssd_disk':
                ssd_disk_list = self.get_ssd_disk(bmc_client, bmc_ip)
                ssd_num = len(ssd_disk_list)
                if ssd_num <= 0:
                    logger.error("Not found sas ssd or sata ssd on node[%s]" % bmc_ip)
                    error_dic['not_exist_ssd_disk_node'].append(bmc_ip)
                elif not first_bmc_node[0]:
                    first_bmc_node = (bmc_ip, ssd_num, bmc_cache)
                elif ssd_num != first_bmc_node[1]:
                    cur_err_msg = err_msg % (bmc_ip, ssd_num, first_bmc_node[0], first_bmc_node[1])
                    logger.error(cur_err_msg)
                    error_dic['different_ssd_disk_num_node'].append(bmc_ip)
            else:
                logger.info("none cache type, bmc[%s], bmc_cache:%s " % (bmc_ip, bmc_cache))
                check_res = self.all_flash_disk_num_check(bmc_node, bmc_client)
                if not check_res:
                    error_dic['all_flash_disk_num_error'].append(bmc_ip)
        all_error_nodes = self.check_result_parsing(error_dic, first_bmc_node)
        if all_error_nodes:
            logger.error(all_error_nodes)
            raise Exception(all_error_nodes)

    @staticmethod
    def check_result_parsing(error_dic, first_bmc_node):
        if error_dic.get('not_exist_ssd_card_node'):
            error_dic['not_exist_ssd_card_node'] = get_code_msg(627601) % error_dic.get('not_exist_ssd_card_node')
        if error_dic.get('different_ssd_card_num_node'):
            error_dic['different_ssd_card_num_node'] = \
                get_code_msg(627603) % (error_dic.get('different_ssd_card_num_node'), first_bmc_node[0])
        if error_dic.get('not_exist_ssd_disk_node'):
            error_dic['not_exist_ssd_disk_node'] = get_code_msg(627602) % error_dic.get('not_exist_ssd_disk_node')
        if error_dic.get('different_ssd_disk_num_node'):
            error_dic['different_ssd_disk_num_node'] = \
                get_code_msg(627604) % (error_dic.get('different_ssd_disk_num_node'), first_bmc_node[0])
        if error_dic.get('all_flash_disk_num_error'):
            error_dic['all_flash_disk_num_error'] = get_code_msg(627613) % error_dic.get('all_flash_disk_num_error')
        all_error_nodes = [value.strip() for value in error_dic.values() if value]
        return all_error_nodes

    def all_flash_disk_num_check(self, node_info: dict, bmc_client):
        """
        全闪场景，检查盘数量是否满足最低数量要求:
          非灾备场景： 检查LLD表主存盘数量
                    zk节点盘：如果与主存类型一致，则主存盘数量+1
                    复制融合部署，与元数据节点共节点且不共盘+1
        检查元数据、复制元数据杂合，怎么处理
        """
        bmc_ip = node_info.get("bmc_ip")
        primary_slot = node_info.get("primary_slot")
        primary_start, primary_end = primary_slot.split("-")
        disk_type = node_info.get("primary_type").strip()
        if disk_type == DeployConstant.LLD_DISK_TYPE[2]:
            ssd_disk_list = self.get_ssd_disk(bmc_client, bmc_ip)
            disk_num = len(ssd_disk_list)
        else:
            disk_num = self.get_nvme_disk(bmc_client, bmc_ip)
        need_disk_num = int(primary_end) - int(primary_start) + 1
        logger.info("node ip: {}, disk type in the LLD table:{}, number of disks on a node:{}, "
                    "Number of disks required:{}".format(bmc_ip, disk_type, disk_num, need_disk_num))
        if need_disk_num > disk_num:
            return False
        return True


IOPS_COMPUTE_CMD = r"""
cat << EOF >/ipos_compute.py
import sys, os, secrets, re, time

startTime = time.time()
testTime = 5
blockDev = sys.argv[1]
if os.path.exists(blockDev):
    pass
else:
    print('%s does not exist' % blockDev)
    exit(2)

blockSize = 1 * 1024 #1KB
blocks = 0
with open('/proc/partitions') as f:
    contents = f.readlines()
    for content in contents:
        pattern = blockDev.replace('/dev/','') + '$'
        if re.findall(pattern, content):
            blocks = int(content.split()[-2])
            break

deviceSize = blocks * blockSize
blocks4k = int(blocks / 4)
readCount = 0
f = open(blockDev,'rb')

while time.time() - startTime <= testTime:
    # 4K align
    offset = int(secrets.SystemRandom().random() * (blocks4k - 1) * blockSize * 4)
    f.seek(offset)
    f.read(blockSize * 4)
    readCount += 1
# print iops
print('%s' % (readCount / testTime))
f.close()

EOF
python /ipos_compute.py  """

SYS_DISK = r"""
cat << EOF >/sys_disk.sh
#!/bin/bash

sys_disk_name=''
raid_card_flag=%s
if [ -n "\${raid_card_flag}" ]
then
    for disk in \$(awk '/sd[a-z]\$|nvme/{print \$NF}' /proc/partitions)
    do
    	raid_flag=\$(udevadm info /dev/\${disk} | grep -E "ID_VENDOR=AVAGO|ID_VENDOR=LSI|ID_VENDOR=MSCC")
    	if [ -n "\${raid_flag}" ]; then
    	    sys_disk_name=\$(udevadm info -q name /dev/\${disk})
    	    break;
    	fi
    done
else
    #系统盘插在后面板IO模组1上
    fs_sd1=\$(ls /sys/devices/pci0000\:74/0000\:74\:04.0/host*/phy-*\:4/port*/end_device-*/target*/*/block/ 2>/dev/null)
    fs_sd2=\$(ls /sys/devices/pci0000\:74/0000\:74\:04.0/host*/phy-*\:5/port*/end_device-*/target*/*/block/ 2>/dev/null)

    #系统盘插在后面板IO模组2上
    if [ -z "\${fs_sd1}" -o -z "\${fs_sd2}" ]; then
        fs_sd1=\$(ls /sys/devices/pci0000\:b4/0000\:b4\:04.0/host*/phy-*\:4/port*/end_device-*/target*/*/block/ 2>/dev/null)
        fs_sd2=\$(ls /sys/devices/pci0000\:b4/0000\:b4\:04.0/host*/phy-*\:5/port*/end_device-*/target*/*/block/ 2>/dev/null)
    fi
    #匹配后面板槽位失败，默认使用前面板0号与1号槽位创建raid（IO模组1）
    if [ -z "\${fs_sd1}" -o -z "\${fs_sd2}" ]; then
        fs_sd1=\$(ls /sys/devices/pci0000\:74/0000\:74\:02.0/host*/phy-*\:0/port*/end_device-*/target*/*/block/ 2>/dev/null)
        fs_sd2=\$(ls /sys/devices/pci0000\:74/0000\:74\:02.0/host*/phy-*\:1/port*/end_device-*/target*/*/block/ 2>/dev/null)
    fi
    #匹配后面板槽位失败，默认使用前面板0号与1号槽位创建raid（IO模组2）
    if [ -z "\${fs_sd1}" -o -z "\${fs_sd2}" ]; then
        fs_sd1=\$(ls /sys/devices/pci0000\:b4/0000\:b4\:02.0/host*/phy-*\:0/port*/end_device-*/target*/*/block/ 2>/dev/null)
        fs_sd2=\$(ls /sys/devices/pci0000\:b4/0000\:b4\:02.0/host*/phy-*\:1/port*/end_device-*/target*/*/block/ 2>/dev/null)
    fi
    if [ -n "\${fs_sd1}" -a -n "\${fs_sd2}" ]; then
        sys_disk_name=\${fs_sd1},\${fs_sd2}
    fi
fi
echo \$sys_disk_name
EOF
sh /sys_disk.sh """