# -*- coding: utf-8 -*-
import traceback

import IPy

import utils.common.log as logger
from utils.common.fic_base import TestCase
from utils.common.message import Message
from utils.common.exception import FCDException
from plugins.DistributedStorage.scripts.logic.DeployOperate import DeployOperate
from plugins.DistributedStorage.scripts.utils.common.DeployConstant import DeployConstant


class ConfigStorageNetwork(TestCase):
    def __init__(self, project_id, pod_id, fs_args, condition=None,
                 metadata=None, **kwargs):
        super(ConfigStorageNetwork, self).__init__(project_id, pod_id)
        self.opr = DeployOperate({'float_ip': fs_args.get('float_ip')})

        self.update_pwd = fs_args['dm_update_pwd']
        self.frontend_network = fs_args.get(
            'frontend_storage_network_list')
        self.backend_network = fs_args.get('backend_storage_network_list')
        self.fsa_list = fs_args.get('fsa_list')
        self.osd_list = fs_args.get('osd_list')

        self.condition = condition
        self.metadata = metadata
        self.more_args = kwargs

    def procedure(self):
        try:
            logger.info("[FS8]Start to config storage network.")
            self.login_deploy_manager()

            logger.info("Query frontend storage network.")
            net_type = 'storage_frontend'
            storage_frontend_network_list, frontend_scenario = self.query_storage_network(net_type)
            logger.info("Config frontend storage network.")
            self.config_storage_network(storage_frontend_network_list, net_type, frontend_scenario)

            logger.info("Query backend storage network.")
            net_type = 'storage_backend'
            storage_backend_network_list, backend_scenario = self.query_storage_network(net_type)
            logger.info("Config backend storage network.")
            self.config_storage_network(storage_backend_network_list, net_type, backend_scenario)

            self.validity_frontend_net()
            self.validity_backend_net()

        except FCDException as e:
            logger.error(traceback.format_exc())
            raise e
        except Exception as e:
            logger.error(traceback.format_exc())
            raise FCDException(626064, str(e))
        finally:
            self.opr.logout()
        return Message()

    def login_deploy_manager(self):
        status_code, error_code, error_des = self.opr.login(
            DeployConstant.DM_LOGIN_USER, self.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 query_storage_network(self, net_type='storage_frontend'):
        rsp_result, rsp_data = self.opr.get_storage_net_plane(net_type)
        error_code = rsp_result.get('code')
        if error_code != 0:
            err_msg = "Failed to query frontend storage network"
            logger.error(err_msg)
            raise Exception(err_msg)
        net_info_list = rsp_data.get("ip_list")
        if net_type == 'storage_frontend':
            storage_network_list, scenario = ExtractNetInf.extract_network_info(self.frontend_network, net_info_list)
            logger.info("Current frontend storage network scenario: %s." % scenario)
        else:
            storage_network_list, scenario = ExtractNetInf.extract_network_info(self.backend_network, net_info_list)
            logger.info("Current backend storage network scenario: %s." % scenario)
        return storage_network_list, scenario

    def config_storage_network(self, storage_network_list, net_type, scenario):
        res_result, res_data = self.opr.set_net_plane(storage_network_list, net_type, scenario)
        if res_result.get("code") != 0 or res_data is not None:
            err_msg = "Failed to config storage network [network:%s, nettype:%s], Detail:[%s]%s" \
                      % (storage_network_list, net_type, res_result, res_data)
            logger.error(err_msg)
            raise Exception(err_msg)
        logger.info("Config storage network [nettype:%s] successful" % net_type)

    def validity_frontend_net(self):
        net_type = 'storage_frontend'
        if self.fsa_list:
            fsa_list = [fsa['om_ip'] for fsa in self.fsa_list]
            rsp_result, rsp_data = self.opr.validity_net(net_type, fsa_list)
            fail_ip_list = list(rsp_data.keys())
            if rsp_result.get("code") != 0 or len(fail_ip_list) > 0:
                err_msg = "Failed to config frontend storage network on " \
                          "host%s, Detail:[%s] %s" % (fail_ip_list, rsp_result, rsp_data)
                logger.error(err_msg)
                raise Exception(err_msg)
        else:
            logger.info("There is no node need to validity storage_frontend network!")

    def validity_backend_net(self):
        net_type = 'storage_backend'
        if self.osd_list:
            osd_list = [osd['om_ip'] for osd in self.osd_list]
            res_result, res_data = self.opr.validity_net(net_type, osd_list)
            fail_ip_list = list(res_data.keys())
            if res_result.get("code") != 0 or len(fail_ip_list) > 0:
                err_msg = "Failed to config backend storage network on host%s, Detail:%s" % (fail_ip_list, res_data)
                logger.error(err_msg)
                raise Exception(err_msg)
            logger.info("Config backend storage network finished.")
        else:
            logger.info("There is no node need to validity storage_backend network!")


class ExtractNetInf:
    @classmethod
    def extract_network_info(cls, new_net_info: list, cluster_net_info: list = None):
        """
        提取网络信息

        :param cluster_net_info: 集群中已存在的网络信息
        :param new_net_info: 扩容新增网络信息
        :return:
        """
        logger.info('Extracting network, new network:{}, existing network:{}'.format(new_net_info, cluster_net_info))
        extract_net_info = list()
        if cluster_net_info:
            cls.exist_network_info_handler(cluster_net_info, extract_net_info)

        scenario = 'extend' if extract_net_info else 'initialization'

        for sub_net_info in new_net_info:
            netmask = sub_net_info.get("netmask")
            gateway = sub_net_info.get("gateway")
            subnet = sub_net_info.get("subnet")
            new_handle_seg = IPy.IP(subnet.split('-')[0]).make_net(netmask)
            cls.ip_segment_handler(gateway, new_handle_seg, extract_net_info)
        logger.info('Extraction result: {}'.format(extract_net_info))
        return extract_net_info, scenario

    @classmethod
    def ip_segment_handler(cls, new_gateway: str, new_handle_seg: IPy.IP, extract_net_info: list):
        """
        合并在同一网段的网络信息

        :param new_gateway: 待处理的网络信息的gateway
        :param new_handle_seg: 待处理的网络信息
        :param extract_net_info: 已提取的网段列表
        :return:
        """
        new_ip_seg = {
            'port_name': '',
            'ip_segment': {'begin_ip': str(new_handle_seg[0]), 'end_ip': str(new_handle_seg[-1])},
            'subnet_prefix': str(new_handle_seg.netmask()),
            'default_gateway': new_gateway,
        }
        logger.info('Merge net info: {}'.format(new_ip_seg))

        if not extract_net_info:
            extract_net_info.append(new_ip_seg)
            return extract_net_info

        for idx, net_info in enumerate(extract_net_info):
            extracted_seg = IPy.IP(net_info.get('ip_segment').get('begin_ip')).make_net(net_info.get('subnet_prefix'))
            if new_handle_seg in extracted_seg:
                cls._check_gateway_params(net_info, new_ip_seg)
                return extract_net_info
            elif extracted_seg in new_handle_seg:
                cls._check_gateway_params(net_info, new_ip_seg)
                extract_net_info[idx] = new_ip_seg
                return extract_net_info

        extract_net_info.append(new_ip_seg)
        return extract_net_info

    @classmethod
    def exist_network_info_handler(cls, cluster_net_info: list, extract_net_info: list):
        """
        提取集群中已存在的网络信息

        :param cluster_net_info: 集群中已存在的网络信息
        :param extract_net_info: 已提取的网络信息列表
        :return:
        """
        for sub_network in cluster_net_info:
            sub_ip_segment = sub_network.get('ip_segment')
            sub_begin_ip = sub_ip_segment.get('begin_ip')
            sub_end_ip = sub_ip_segment.get('end_ip')
            if not sub_begin_ip or not sub_end_ip:
                continue
            cluster_ip_seg = {
                'port_name': '',
                'ip_segment': {'begin_ip': sub_begin_ip, 'end_ip': sub_end_ip},
                'subnet_prefix': sub_network.get('subnet_prefix'),
                'default_gateway': sub_network.get('default_gateway'),
            }
            extract_net_info.append(cluster_ip_seg)

    @classmethod
    def _check_gateway_params(cls, new_ip_seg: dict, extract_ip_seg: dict):
        if new_ip_seg.get('default_gateway') != extract_ip_seg.get('default_gateway'):
            raise FCDException(626384, extract_ip_seg, new_ip_seg)
