# -*- coding: utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import time
import utils.common.log as logger
import utils.constant.constant as constant
from utils.common.fic_base import TestCase
from plugins.DistributedStorage.logic.deploy_operate import DeployOperate
from plugins.DistributedStorage.utils.config.device_check import DeviceResource
from plugins.DistributedStorage.utils.common.deploy_constant import DeployConstant


class ExpandPool(TestCase):
    def __init__(self, project_id, pod_id, fs_args, **kwargs):
        super(ExpandPool, self).__init__(project_id, pod_id)
        self.fs_args = fs_args
        self.operate = DeployOperate(self.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.main_storage_capacity = None
        self.cache_type = None
        self.cache_capacity = None
        self.more_args = kwargs

    def procedure(self):
        logger.info("Start to expand pool.")
        self.login_deploy_manager()

        logger.info('Get pool ID')
        pool_id = self.get_pool_id()
        logger.info('Succeed to get pool[ID:%s]' % pool_id)

        if len(self.osd_list) == 0:
            logger.info('There is no osd node for add to pool, return directly ')
            return

        logger.info('Get data of storage pool')
        pool_config_args = self.get_and_check_expand_pool_args(self.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'])

    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 Exception(err_msg)

    def get_pool_id(self):
        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("Failed to get poolId.")
        pool_info = pool_info_list[-1]
        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_and_check_expand_pool_args(self, storage_node_list, pool_id):
        all_openstack_nodes = self.db.get_ref_component_host_list(self.pod_id, constant.REFCOMPONENTCONSTANT.OSD)
        osd_list = list()
        for node in all_openstack_nodes:
            node_dict = node.__dict__
            for osd in storage_node_list:
                if node_dict.get('bmc_ip') == osd.get('bmc_ip'):
                    osd_node = dict(list(node_dict.items())+list(osd.items()))
                    osd_list.append(osd_node)
        all_nodes_disks = self.operate.query_all_disk()
        pool_config_args = dict()
        pool_config_args['poolId'] = pool_id
        slot_list = [node.get('primary_slot') for node in osd_list]
        if len(slot_list) == 0:
            err_msg = "The main storage slot cannot be empty in LLD for manage storage pool"
            logger.error(err_msg)
            raise Exception(err_msg)
        max_slot_list = self.device_res.get_storage_pool_slot(slot_list)
        server_list = list()
        for osd in osd_list:
            server = dict()
            server['nodeMgrIp'] = osd.get("om_ip")
            start_slot = max_slot_list.split('-')[0]
            end_slot = max_slot_list.split('-')[1]
            server['diskSlots'] = self.get_main_storage_slots(osd, all_nodes_disks, start_slot, end_slot)
            if self.main_storage_type in ['sas_disk', 'sata_disk', 'ssd_disk']:
                cache_type = osd.get('cache_type')
            else:
                cache_type = 'none'
                self.cache_type = 'none'
            if cache_type != 'none':
                server['cacheESNList'] = self.get_cache_disk_list(osd, all_nodes_disks, cache_type,
                                                                  self.main_storage_type)
            server_list.append(server)
        pool_config_args['mediaType'] = self.main_storage_type
        pool_config_args['cacheType'] = self.cache_type
        pool_config_args['servers'] = server_list
        return pool_config_args

    def get_main_storage_slots(self, osd, all_nodes_disks, start_slot, end_slot):
        slot_list = list()
        for slot in range(int(start_slot), int(end_slot)+1):
            om_ip = osd.get('om_ip')
            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
            if disk_meta.get('devRole') != 'no_use':
                break
            if not self.main_storage_type:
                self.main_storage_type = disk_meta.get('devType')
            else:
                self.var_compare(disk_meta, 'devType', self.main_storage_type)
            if not self.main_storage_capacity:
                self.main_storage_capacity = disk_meta.get('devTotalCapacity')
            else:
                self.var_compare(disk_meta, 'devTotalCapacity', self.main_storage_capacity)
            slot_list.append(str(slot))
        slot_num = len(slot_list)
        if slot_num < 4:
            err_msg = "The disk on expended node cannot be less than 4[current no use disk:%s]" % (slot_num)
            logger.error(err_msg)
            raise Exception(err_msg)
        return '-'.join(slot_list)

    def get_cache_disk_list(self, osd, all_nodes_disks, cache_type, main_storage_type):
        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:
                disk_status = cache.get('devType') == cache_type and cache.get('devRole') == 'no_use'
                if not disk_status:
                    continue
                self.get_cache_type(cache)
                self.get_cache_capacity(cache)
                cache_disk_esn_list.append(cache.get('devEsn'))
                cache_num += 1
                if cache_num >= max_cache_num:
                    break
            if len(cache_disk_esn_list) == 0:
                err_msg = "The is no or no unused cache disk[%s] on node[%s]." % (cache_type, om_ip)
                logger.error(err_msg)
                raise Exception(err_msg)
        return cache_disk_esn_list

    def get_cache_capacity(self, cache):
        if not self.cache_capacity:
            self.cache_capacity = cache.get('devTotalCapacity')
        else:
            self.var_compare(cache, 'devTotalCapacity', self.cache_capacity)

    def get_cache_type(self, cache):
        if not self.cache_type:
            self.cache_type = cache.get('devType')
        else:
            self.var_compare(cache, 'devType', self.cache_type)

    def var_compare(self, src_dict, src_key, cmp_value):
        src_value = src_dict.get(src_key)
        if src_value != cmp_value:
            err_msg = "Inconsistent primary storage disk type or size[%s!=%s]." % (cmp_value, src_value)
            logger.error(err_msg)
        return src_value

    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)
