# -*- coding: utf-8 -*-
import time
from collections import defaultdict

import utils.common.log as logger
from utils.common.message import Message
from utils.common.fic_base import TestCase
from utils.common.exception import HCCIException

from utils.business.project_condition_utils import get_project_condition_boolean

from plugins.DistributedStorage.logic.DeployOperate import DeployOperate
from plugins.DistributedStorage.logic.InstallOperate import InstallOperate
from plugins.DistributedStorage.utils.config.DeviceCheck import DeviceResource
from plugins.DistributedStorage.utils.common.DeployConstant import DeployConstant
from plugins.DistributedStorage.logic.ErrorMsgHandle import PoolInfoCheck, PoolLLDParamsCheck


class ExpandPool(TestCase):
    def __init__(self, project_id, pod_id, fs_args, condition=None, metadata=None, **kwargs):
        super(ExpandPool, self).__init__(project_id, pod_id)
        self.fs_args = fs_args
        self.operate = DeployOperate(self.fs_args)
        self.install_operate = InstallOperate(project_id, pod_id, fs_args)
        self.device_res = DeviceResource()
        self.fsa_list = self.fs_args.get('fsa_list')
        self.osd_list = self.fs_args.get('osd_list')
        self.main_storage_type = None
        self.cache_type = None
        self.condition = condition
        self.metadata = metadata
        self.more_args = kwargs
        self.pool_info_check = None

    def procedure(self):
        try:
            logger.info("Start to expand pool.")
            self.install_operate.config_disk_type_switch(support=True)
            self.login_deploy_manager()

            logger.info('Get pool ID')
            # 获取待扩节点列表
            ex_osd_list = self.fs_args.get('ex_osd_list')
            if len(ex_osd_list) == 0:
                logger.info('There is no osd node for add to pool, return directly')
                return Message(200)
            pool_id = self.get_pool_id(ex_osd_list)
            logger.info('Succeed to get poolId: %s' % pool_id)

            logger.info('Get data of storage pool')
            pool_config_args = self.get_pool_config_info(ex_osd_list, pool_id)

            logger.info('Expanding pool')
            task_id = self.expand_pool(pool_config_args)
            logger.info('start to expand pool[%s]' % pool_config_args.get('pool_name'))

            logger.info('Query the process of expanding pool')
            self.query_expanding_pool_process(task_id)
            logger.info('Expand pool successful')
            self.operate.login_out(DeployConstant.DM_LOGIN_USER, self.fs_args['dm_update_pwd'])
        except HCCIException as e:
            return Message(500, e)
        except Exception as e:
            return Message(500, HCCIException(626003, str(e)))
        return Message(200)

    def login_deploy_manager(self):
        status_code, error_code, error_des = self.operate.login(
            DeployConstant.DM_LOGIN_USER, self.fs_args['dm_update_pwd'])
        if status_code != 200 or error_code != 0:
            err_msg = "Failed to login deploy manager, " \
                      "Detail:[status:%s,code:%s]%s" % (status_code, error_code, error_des)
            logger.error(err_msg)
            raise HCCIException(626067, err_msg)

    def get_pool_id(self, ex_osd_list):
        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 HCCIException(626078, str(pool_data))
        # 获取目标存储池info
        pool_name = ex_osd_list[0].get("storage_pool_name_and_slot")
        pool_info = {}
        if pool_name:
            logger.info("The name of the storage pool to be expanded is %s." % pool_name)
            pool_info = self.get_pool_info_by_pool_name(pool_info, pool_info_list, pool_name)
        else:
            pool_info = self.get_pool_info_by_default_pool_name(pool_info, pool_info_list)
        if not pool_info:
            err_msg = "No storage pool named %s is found in the cluster." % pool_name
            logger.error(err_msg)
            raise Exception(err_msg)
        pool_id = pool_info.get('poolId')
        self.main_storage_type = pool_info.get('mediaType')
        self.cache_type = pool_info.get('cacheMediaType')
        return pool_id

    def get_pool_info_by_default_pool_name(self, pool_info, pool_info_list):
        if self.fs_args.get('default_pool_name'):
            for pool in pool_info_list:
                if pool.get('poolName') == self.fs_args.get('default_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)
        return pool_info

    @staticmethod
    def get_pool_info_by_pool_name(pool_info, pool_info_list, pool_name):
        for pool in pool_info_list:
            if pool.get("poolName") == pool_name:
                pool_info = pool
                break
        return pool_info

    def get_pool_config_info(self, ex_osd_list, pool_id):
        """
        :param ex_osd_list:
        :param pool_id:
        :return: pool_config_args
        {
           存储池ID poolId: pool_id
           盘类型mediaType: ssd_disk
           缓存类型cacheType: none
           服务器列表servers: [{server_dict}, {server_dict}, ...]
          }
        """
        # 初始化存储池信息校验字典
        self.pool_info_check = PoolInfoCheck.pool_info_check_dict_init()
        is_new_business_node = get_project_condition_boolean(self.project_id, 'TenantStorNewNode')
        logger.info('Start to query pool info, is_new_business_node:{}'.format(is_new_business_node))
        all_nodes_disks = self.get_cluster_disk_info()
        if is_new_business_node:
            pool_config_args = self.get_expand_pool_args_for_new_node(ex_osd_list, all_nodes_disks, pool_id)
        else:
            # 扩管理节点入口
            pool_config_args = self.get_expand_pool_args_for_manager(ex_osd_list, all_nodes_disks, pool_id)
        # 统一校验存储池参数包括主存和缓存类型、容量、盘数量（不小于4）校验
        PoolInfoCheck.pool_info_error_check(self.pool_info_check)
        return pool_config_args

    def get_cluster_disk_info(self):
        all_nodes_disks = self.operate.query_all_disk()
        all_disk_data = all_nodes_disks.get_query_data()
        disk_info = all_disk_data.get('disks')
        if not disk_info:
            err_msg = 'No disk information exists.'
            logger.error(err_msg)
            raise HCCIException(626074, all_disk_data)
        return all_nodes_disks

    def get_expand_pool_args_for_new_node(self, ex_osd_list, all_nodes_disks, pool_id):
        logger.info('Start to query pool args')
        storage_type = self.get_lld_main_and_cache_type(ex_osd_list, pool_id)
        logger.info('storage type:{}'.format(storage_type))
        server_list = []
        for osd_info in ex_osd_list:
            server_dict = self.get_server_info(osd_info, all_nodes_disks, storage_type, pool_id)
            server_list.append(server_dict)
        logger.info('Server list:{}'.format(server_list))
        pool_config_args = {'poolId': pool_id,
                            'servers': server_list,
                            'mediaType': storage_type.get('mediaType'),
                            'cacheType': storage_type.get('cacheType')
                            }
        logger.info('Pool config args:{}'.format(pool_config_args))
        return pool_config_args

    def get_lld_main_and_cache_type(self, ex_osd_list, pool):
        storage_type = {'mediaType': None, 'cacheType': None}
        lld_check = PoolLLDParamsCheck()
        lld_check.init_record_pool_info(pool)
        for node in ex_osd_list:
            primary_type = node.get('primary_type')
            if not storage_type.get('mediaType'):
                storage_type['mediaType'] = DeployConstant.STORAGE_TYPE_MAP.get(primary_type)
            if not storage_type.get('cacheType'):
                storage_type['cacheType'] = node.get('cache_type')
            lld_check.pub_type_check(pool, primary_type, 'primary_type')
            lld_check.cache_type_check(pool, node)
            lld_check.primary_slot_check(pool, node)
        logger.info('Result analysis')
        lld_check.expand_business_node_check_result_handle()
        if storage_type['mediaType'] != self.main_storage_type or storage_type['cacheType'] != self.cache_type:
            pool_main_type_map = DeployConstant.PRODUCT_TO_LLD_MAP.get(self.main_storage_type)
            pool_storage_type = {'mediaType': pool_main_type_map, 'cacheType': self.cache_type}
            logger.error('Disk type error. disk type in the LLD:{}, '
                         'disk type in the pool:{}'.format(storage_type, pool_storage_type))
            raise HCCIException(626375, storage_type, pool_storage_type)
        logger.info('Check Passed')
        logger.info('Storage type:{}'.format(storage_type))
        return storage_type

    def get_server_info(self, osd_info, all_nodes_disks, storage_type, pool):
        """
        server_dict:{'nodeMgrIp': '192.168.2.201'
                     'diskSlots': '2-3-4-5-6-7'
                     'cacheESNList': 可选 [ESN1, ESN2, ...]
                     'mediaESNList': 主存为ssd_card必选[ESN1, ESN2, ...]}
        :return:
        """
        server_dict = dict()
        cache_type = storage_type.get('cacheType')
        main_type = storage_type.get('mediaType')
        start_slot, end_slot = osd_info.get('primary_slot').split('-')
        if main_type in ['sas_disk', 'sata_disk', 'ssd_disk']:
            storage_disk_slots = self.get_main_storage_slots(osd_info, all_nodes_disks, start_slot, end_slot, pool)
        else:
            cur_disk_num = int(end_slot) - int(start_slot) + 1
            storage_disk_slots, storage_disk_esn_list = self.get_main_slots_when_all_nvme(osd_info, all_nodes_disks,
                                                                                          cur_disk_num, pool)
            server_dict["mediaESNList"] = storage_disk_esn_list
        if storage_type.get('cacheType') != 'none':
            server_dict['cacheESNList'] = self.get_cache_disk_list(osd_info, all_nodes_disks, cache_type, main_type,
                                                                   pool)
        server_dict['nodeMgrIp'] = osd_info.get('om_ip')
        server_dict['diskSlots'] = storage_disk_slots
        return server_dict

    def get_main_slots_when_all_nvme(self, osd_info, all_nodes_disks, disk_num, pool):
        slot_list = []
        storage_disk_esn_list = list()
        node_ip = osd_info.get('om_ip')
        disk_info = all_nodes_disks.get_query_data().get('disks')
        cur_node_disk_info = disk_info.get(node_ip)
        logger.info("All disk on node[%s]: %s" % (node_ip, cur_node_disk_info))
        if not cur_node_disk_info:
            err_msg = 'No disk information exists on the node:[%s]' % node_ip
            logger.error(err_msg)
            self.pool_info_check[pool]['nvme_main_storage']['no_disk_error'].append(node_ip)
            return '', storage_disk_esn_list
        capacity_team = defaultdict(list)
        for slot_disk_info in cur_node_disk_info:
            if slot_disk_info.get('devType') != 'ssd_card' or slot_disk_info.get('devRole') != 'no_use':
                continue
            capacity_team[slot_disk_info.get('devTotalCapacity')].append(slot_disk_info)
        useful_main_storage_info = list(filter(lambda x: len(x) >= 4, capacity_team.values()))
        logger.info('useful disk: {}'.format(useful_main_storage_info))
        if len(useful_main_storage_info) != 1:
            err_msg = 'This node has no available or different capacity nvme disks. node ip:{}'.format(node_ip)
            logger.error(err_msg)
            self.pool_info_check[pool]['nvme_main_storage']['disk_nums_error'].append(node_ip)
            return '', storage_disk_esn_list
        ordered_storage_info = sorted(useful_main_storage_info[0], key=lambda x: x.get('devSlot'))
        logger.info('ordered useful disk:{}'.format(ordered_storage_info))
        disk_capacity = ordered_storage_info[0]['devTotalCapacity']
        self.pool_info_check[pool]['nvme_main_storage']['disk_capacity'][disk_capacity].append(node_ip)
        for slot_disk_info in ordered_storage_info:
            slot_list.append(str(slot_disk_info.get('devSlot')))
            storage_disk_esn_list.append(slot_disk_info.get("devEsn"))
            if len(slot_list) == disk_num:
                break
        return '-'.join(slot_list), storage_disk_esn_list

    def get_expand_pool_args_for_manager(self, osd_list, all_nodes_disks, pool_id):
        logger.info('Start to query pool args')
        max_slot_range = self.manager_lld_params_check(osd_list, pool_id)
        logger.info('Max slot range:{}'.format(max_slot_range))
        # 检查戴尔服务器场景是否安装了扫盘工具
        self.install_operate.check_third_party_server_disk(all_nodes_disks, osd_list)
        server_list = list()
        for osd in osd_list:
            server = dict()
            server['nodeMgrIp'] = osd.get("om_ip")
            start_slot, end_slot = max_slot_range.split('-')
            server['diskSlots'] = self.get_main_storage_slots(osd, all_nodes_disks, start_slot, end_slot, pool_id)
            if self.cache_type != 'none':
                server['cacheESNList'] = self.get_cache_disk_list(
                    osd, all_nodes_disks, self.cache_type, self.main_storage_type, pool_id
                )
            server_list.append(server)
        logger.info('Server list:{}'.format(server_list))
        pool_config_args = {'poolId': pool_id,
                            'servers': server_list,
                            'mediaType': self.main_storage_type,
                            'cacheType': self.cache_type}
        logger.info('Pool config args:{}'.format(pool_config_args))
        return pool_config_args

    def manager_lld_params_check(self, ex_osd_list, pool):
        lld_check = PoolLLDParamsCheck()
        lld_check.init_record_pool_info(pool)
        lld_cache = None
        slot_list = []
        for node in ex_osd_list:
            slot_list.append(node.get('primary_slot'))
            cur_cache = node.get('cache_type')
            if lld_cache is None:
                lld_cache = cur_cache
            lld_check.pub_type_check(pool, cur_cache, 'cache_type')
            lld_check.primary_slot_check(pool, node)
        logger.info('Result analysis')
        lld_check.expand_manager_node_check_result_handle()
        if lld_cache != self.cache_type:
            logger.error('Cache type error. cache type in the LLD:{}, '
                         'cache type in the pool:{}'.format(lld_cache, self.cache_type))
            raise HCCIException(626376, lld_cache, self.cache_type)
        logger.info('Check Passed')
        logger.info('Cache type:{}'.format(lld_cache))
        max_slot_list = self.device_res.get_storage_pool_slot(slot_list)
        return max_slot_list

    def get_main_storage_slots(self, osd, all_nodes_disks, start_slot, end_slot, pool):
        slot_list = list()
        om_ip = osd.get('om_ip')
        for slot in range(int(start_slot), int(end_slot)+1):
            disk_meta = all_nodes_disks.get_disks_by_node_slot(om_ip, slot)
            if not disk_meta:
                logger.info("There is no disk on slot[%s] at node[%s]" % (slot, om_ip))
                continue
            capacity = disk_meta.get('devTotalCapacity')
            self.pool_info_check[pool]["main_storage_info"]["type_info"][disk_meta.get('devType')][om_ip].append(slot)
            self.pool_info_check[pool]["main_storage_info"]["capacity_info"][capacity][om_ip].append(slot)
            slot_list.append(str(slot))
        slot_num = len(slot_list)
        self.pool_info_check[pool]["main_storage_info"]["disk_num"][slot_num][om_ip] = slot_list
        return '-'.join(slot_list)

    def get_cache_disk_list(self, osd, all_nodes_disks, cache_type, main_storage_type, pool):
        cache_disk_esn_list = list()
        if cache_type and main_storage_type not in ['ssd_disk', 'ssd_card']:
            om_ip = osd.get('om_ip')
            all_disks = all_nodes_disks.get_disks_by_node(om_ip)
            cache_num = 0
            max_cache_num = 4
            for cache in all_disks:
                if cache.get('devType') == cache_type and cache.get('devRole') == 'no_use':
                    cache_disk_esn_list.append(cache.get('devEsn'))
                    cache_num += 1
                    self.pool_info_check[pool]["cache_info"]["media_type"][cache.get('devType')].append(om_ip)
                    self.pool_info_check[pool]["cache_info"]["media_size"][cache.get('devTotalCapacity')].append(om_ip)
                if cache_num >= max_cache_num:
                    break
            self.pool_info_check[pool]["cache_info"]['cache_num'][len(cache_disk_esn_list)].append(om_ip)
        return cache_disk_esn_list

    def expand_pool(self, pool_config_args):
        res = self.operate.expand_pool(pool_config_args)
        status_code, result, task_id, error_code, error_des = res.get_expand_pool_code()
        if status_code != 200 or result != 0:
            logger.info("Pool args is: %s" % pool_config_args)
            err_msg = "Failed to expand pool, " \
                      "Detail:[status:%s,result:%s,error:%s]%s" % (status_code, result, error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)
        return task_id

    def query_expanding_pool_process(self, task_id):
        wait_time = 13800
        task_status, task_info = None, None
        while wait_time > 0:
            time.sleep(10)
            wait_time -= 10
            res_query = self.operate.query_task_info()
            task_info = res_query.get_task_by_id(task_id)
            task_status = task_info.get('taskStatus')
            entity_name = task_info.get('entityName')
            if task_status == 'success':
                logger.info("Expanding the storage pool[%s] success." % entity_name)
                break
            elif task_status == "failed":
                logger.error("Failed to expand the storage pool[%s].Deatail:%s" % (entity_name, str(task_info)))
                break
            else:
                task_name = task_info.get('taskName')
                task_progress = task_info.get('progress')
                logger.info("Creating the storage pool[taskID:%s, taskName:%s, taskObject:%s, taskStatus:%s, "
                            "taskProgress:%s]" % (task_id, task_name, entity_name, task_status, task_progress))
        if task_status != 'success':
            if wait_time <= 0:
                err_msg = "Get the status of creating pool timeout, Detail:<%s>%s" % (task_status, str(task_info))
            else:
                err_msg = "Failed to expand pool, Detail:<%s>%s" % (task_status, str(task_info))
            logger.error(err_msg)
            raise Exception(err_msg)
