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

import utils.common.log as logger
from utils.common.fic_base import TestCase
from utils.common.exception import HCCIException
from plugins.DistributedStorage.logic.deploy_operate import DeployOperate
from plugins.DistributedStorage.logic.install_operate import InstallOperate
from plugins.DistributedStorage.logic.error_msg_handle import PoolInfoCheck
from plugins.DistributedStorage.utils.common.deploy_constant import DeployConstant


class CreatePool(TestCase):
    def __init__(self, project_id, pod_id, fs_args, **kwargs):
        super(CreatePool, self).__init__(project_id, pod_id)
        self.fs_args = fs_args
        self.opr = DeployOperate(self.fs_args)
        self.install_operate = InstallOperate(project_id, pod_id, fs_args)
        self.fsa_list = self.fs_args.get('fsa_list')
        self.osd_list = self.fs_args.get('osd_list')
        self.float_ip = self.fs_args.get('float_ip')
        self.update_pwd = self.fs_args.get('dm_update_pwd')
        self.openstack_nodes = self.fs_args.get('all_openstack_nodes')
        self.pool_name = self.fs_args.get('backend_name')
        self.host_and_tool = (self.float_ip, None)
        self.main_storage_type = None
        self.cache_type = None
        self.min_disk_num = 0
        self.max_disk_num = 0
        self.disk_num = 0
        self.more_args = kwargs
        self.pool_info_check = None

    @staticmethod
    def record_disk_num(min_num, max_num, disk_num):
        if min_num == 0:
            min_num = disk_num
        else:
            if disk_num < min_num:
                min_num = disk_num
        if max_num == 0:
            max_num = disk_num
        else:
            if disk_num > max_num:
                max_num = disk_num
        return min_num, max_num

    def procedure(self):
        if not len(self.fsa_list) >= len(self.osd_list):
            err_msg = "The osd list is not the subset of fsa list!"
            logger.error(err_msg)
            raise Exception(err_msg)
        try:
            logger.info("Start to create pool.")
            self.install_operate.config_create_pool_license_switch(self.pod_id, self.float_ip)
            self.install_operate.config_disk_type_switch(support=True)
            self.opr.login(DeployConstant.DM_LOGIN_USER, self.update_pwd)

            logger.info('Check cluster...')
            self.check_cluster()

            logger.info("Start to disable global deduplication and compression function .")
            self.opr.disable_dedup_compress()
            logger.info("Disable global deduplication and compression successful .")

            logger.info('Get data of storage pool')
            pool_config_args = self.get_and_check_pool_args(self.osd_list)

            logger.info('Creating pool')
            task_id = self.create_pool(pool_config_args)

            query_result = self.query_pool_task_process(task_id, pool_config_args.get('poolName'))
            if query_result:
                err_msg = "Failed to create pool, Detail:%s" % query_result
                logger.error(err_msg)
                raise Exception(err_msg)
            logger.info('Create pool successful')
        finally:
            self.install_operate.config_create_pool_license_switch(self.pod_id, self.float_ip, delete=True)
            self.opr.login_out(DeployConstant.DM_LOGIN_USER, self.update_pwd)

    def create_pool(self, pool_config_args):
        res = self.opr.create_pool(pool_config_args)
        status_code, result, task_id, error_code, error_des = res.get_create_pool_code()
        if status_code != 200 or result != 0:
            logger.info("Pool args is: %s" % pool_config_args)
            err_msg = "Failed to Create pool, " \
                      "Detail:[status:%s,result:%s,error:%s]%s" % (status_code, result, error_code, error_des)
            logger.error(err_msg)
            sector_msg = "The sector size of the specified disk list varies " \
                         "or is different from that of disks in the storage pool"
            if sector_msg in error_des:
                raise HCCIException(626400, err_msg)
            raise HCCIException(626003, err_msg)
        return task_id

    def check_cluster(self):
        response = self.opr.query_manage_cluster()
        cluster = response.get_query_data()
        if not cluster.get('clusterName'):
            logger.error('Query cluster fail...')
            raise Exception("Query cluster fail...")
        else:
            logger.info('Query cluster successfully...')

    def query_pool_task_process(self, task_id, pool_name, timeout=3600):
        logger.info('Query the process of creating pool[%s]' % pool_name)
        current_time = 0
        task_data = dict()
        while current_time <= timeout:
            time.sleep(10)
            current_time += 10
            logger.info("Start query the process of pool[%s] task[%s] after %s seconds." % (pool_name, task_id,
                                                                                            current_time))
            res_query = self.opr.query_task_info()
            task_info = res_query.get_task_by_id(task_id)
            task_status, entity_name = task_info.get('taskStatus'), task_info.get('entityName')
            if task_status == 'success':
                logger.info("Create the storage pool[%s] success." % entity_name)
                break
            elif task_status == "failed":
                logger.error("Failed to create the storage pool[%s]. Detail:%s" % (entity_name, str(task_info)))
                task_data['pool'] = pool_name
                task_data['status'] = task_status
                task_data['description'] = "Failed to create the storage pool"
                task_data['detail'] = task_info
                break
            elif task_status == "part_success":
                err_hint = "The task of creating a pool is partially successful. " \
                           "The OSD process fails to be started or other sub-task fail to be created on some nodes. " \
                           "Make the following operations by Manually: " \
                           "1. Login to FusionStorage web page and go to the task center, " \
                           "find the task of creating a pool, and handle the faulty disk or failed sub-task. " \
                           "2. Delete the created storage pool. " \
                           "If a message is displayed indicating that the storage pool depends on the VBS, " \
                           "enable the VBS first, delete the storage pool, " \
                           "and then disable the VBS. Try again after the pool is deleted."
                logger.error(err_hint)
                task_data['pool'] = pool_name
                task_data['status'] = task_status
                task_data['description'] = err_hint
                task_data['detail'] = task_info
                break
            else:
                task_name, task_progress = task_info.get('taskName'), 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 current_time >= timeout:
            err_msg = "Waiting for the task[%s] completion times out after %s seconds." % (task_id, current_time)
            logger.error(err_msg)
            task_data['pool'] = pool_name
            task_data['status'] = 'timeout'
            task_data['description'] = "Waiting for the task completion times out."
            task_data['detail'] = err_msg
        return task_data

    def get_and_check_pool_args(self, osd_nodes):
        osd_list = self.get_osd_list(osd_nodes)
        logger.info('query all nodes disk info')
        all_nodes_disks = self.opr.query_all_disk()
        self.install_operate.check_third_party_server_disk(all_nodes_disks, osd_list)
        self.main_storage_type = None
        self.cache_type = None
        server_list = list()
        self.pool_info_check = PoolInfoCheck.pool_info_check_dict_init()
        for osd in osd_list:
            server = self.get_server_info(all_nodes_disks, osd)
            server_list.append(server)
        self.check_disk_num()
        pool_para_args = {'redundancyPolicy': 'replication',
                          'replicaNum': 3,
                          'poolName': self.pool_name,
                          'encryptType': 0,
                          'storageMediaType': self.main_storage_type,
                          'cacheMediaType': self.cache_type,
                          'securityLevel': 'server'
                          }
        pool_config_args = {'poolPara': pool_para_args, 'serverList': server_list}
        PoolInfoCheck.pool_info_error_check(self.pool_info_check)
        return pool_config_args

    def get_osd_list(self, osd_nodes):
        logger.info('get osd list')
        osd_list = list()
        for node in self.openstack_nodes:
            for osd in osd_nodes:
                if node.get('bmc_ip') == osd.get('bmc_ip'):
                    osd_node = dict(list(node.items()) + list(osd.items()))
                    osd_list.append(osd_node)
        return osd_list

    def get_server_info(self, all_nodes_disks, osd):
        server = dict()
        server['nodeMgrIp'] = osd.get("om_ip")
        osd_slot = osd.get('primary_slot')
        if not osd_slot:
            err_msg = "The main storage slot cannot be empty in LLD for manage storage pool"
            logger.error(err_msg)
            raise Exception(err_msg)
        start_slot = osd_slot.split('-')[0]
        end_slot = osd_slot.split('-')[1]
        storage_disk_list = self.get_main_storage_disk_list(osd, all_nodes_disks, start_slot, end_slot)
        disk_type = self.main_storage_type
        if disk_type in ['sas_disk', 'sata_disk']:
            server['startSlot'] = start_slot
            server['endSlot'] = end_slot
            self.cache_type = osd.get('cache_type')
        elif disk_type in ['ssd_disk']:
            server['startSlot'] = start_slot
            server['endSlot'] = end_slot
            self.cache_type = 'none'
        else:
            self.cache_type = 'none'
        server['cacheMediaType'] = self.cache_type
        cache_disk_list = self.get_cache_disk_list(osd, all_nodes_disks, self.cache_type, disk_type)
        server['mediaList'] = storage_disk_list + cache_disk_list
        return server

    def get_main_storage_disk_list(self, osd, all_nodes_disks, start_slot, end_slot):
        storage_disks = 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
            disk_status = disk_meta.get('devRole')
            if disk_status != 'no_use':
                self.pool_info_check["murano"]["main_storage_info"]["disk_status"][disk_status][om_ip].append(slot)
                continue
            disk = dict()
            disk['phySlotId'] = slot
            disk['mediaRole'] = "main_storage"
            disk['mediaType'] = disk_meta.get('devType')
            disk['mediaSize'] = disk_meta.get('devTotalCapacity')
            disk['phyDevEsn'] = disk_meta.get('devEsn')
            self.main_storage_type = disk_meta.get('devType')
            main_stor_info = "main_storage_info"
            try:
                self.pool_info_check["murano"][main_stor_info]["type_info"][disk['mediaType']][om_ip].append(slot)
            except KeyError as e:
                logger.error("Key not exist. Detail:%s" % str(e))
                raise e
            try:
                self.pool_info_check["murano"][main_stor_info]["capacity_info"][disk['mediaSize']][om_ip].append(slot)
            except KeyError as e:
                logger.error("Key not exist. Detail:%s" % str(e))
                raise e
            storage_disks.append(disk)

        disk_num = len(storage_disks)
        slot_range = '%s-%s' % (start_slot, end_slot)
        self.pool_info_check["murano"]["main_storage_info"]["disk_num"][disk_num][om_ip] = slot_range
        self.disk_num += disk_num
        self.min_disk_num, self.max_disk_num = self.record_disk_num(self.min_disk_num, self.max_disk_num, disk_num)
        return storage_disks

    def get_cache_disk_list(self, osd, all_nodes_disks, cache_type, main_storage_type):
        cache_disks = list()
        om_ip = osd.get('om_ip')
        if cache_type and main_storage_type not in ['ssd_disk', 'ssd_card']:
            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 = dict()
                    cache_disk['phySlotId'] = cache.get('devSlot')
                    cache_disk['mediaRole'] = "osd_cache"
                    cache_disk['mediaType'] = cache.get('devType')
                    cache_disk['mediaSize'] = cache.get('devTotalCapacity')
                    cache_disk['phyDevEsn'] = cache.get('devEsn')
                    self.pool_info_check["murano"]["cache_info"]["media_type"][cache.get('devType')].append(om_ip)
                    self.pool_info_check["murano"]["cache_info"]["media_size"][cache.get('devTotalCapacity')].\
                        append(om_ip)
                    cache_num += 1
                    cache_disks.append(cache_disk)
                if cache_num >= max_cache_num:
                    break
            self.pool_info_check["murano"]["cache_info"]['cache_num'][len(cache_disks)].append(om_ip)
        return cache_disks

    def check_disk_num(self):
        if self.max_disk_num == 0 or self.min_disk_num == 0:
            logger.info("No available disk num[max:%s, min:%s]. Skip.." % (self.max_disk_num, self.min_disk_num))
            return
        self.pool_info_check["murano"]["disk_num_gap"] = (self.max_disk_num, self.min_disk_num)
        try:
            self.pool_info_check["murano"]["percentage"] = (self.max_disk_num - self.min_disk_num) / float(
                self.max_disk_num)
        except ZeroDivisionError as e:
            err_msg = 'ZeroDivisionError, Detail:%s' % str(e)
            logger.error(err_msg)
            raise e
        self.pool_info_check["murano"]["disk_total_num"] = self.disk_num
