# -*- coding: UTF-8 -*-
from common.baseFactory import log
from common import resourceParse
from common import cliUtils
from java.lang import Exception as JException
import traceback

def _get_item_err_msg(item_key, steps):
    err_msg = "\n" + item_key + ":\n"
    init_placeholder = 1
    for step in steps:
        for single_cmd in step.get("cmd").split("\n"):
            err_msg += " (" + str(init_placeholder) + ") " + single_cmd + "\n"
            init_placeholder += 1
    return err_msg


class ConfigAutoSwitcher:
    """
    operations before and after the patching operation, including
    configuration alternation and recovery of device.
    """

    def __init__(self, data_dict):
        """
        automatically switch configures to
        :param data_dict: context.
        """
        log.info(data_dict, 'entering ConfigAutoSwitcher.')
        self.data_dict = data_dict
        self.lang = data_dict['lang']
        self.ssh_con = data_dict['ssh']
        self.resource = resourceParse.execute(self.lang)

    def check_unrecommended_switch_list(self):
        """
        query for the device's un-recommended switch list during patching
        operation.
        :raise ExecutionException
        :return: unrecommended_switchs:
        list of item keys.
        """
        log.info(self.data_dict,
                 'checking for device switchs status before patching.')
        unrecommended_switchs = []
        involved_items = []
        for switch_key in SwitchCheckingItems.SWITCH_ITEMS_RECOMMENDED_STATUS:
            if self.__is_cur_dev_has_issue(switch_key):
                involved_items.append(switch_key)
        for switch_key in involved_items:
            log.info(self.data_dict,
                     "checking switch %s's configuration." % switch_key)
            steps = SwitchCheckingItems.SWITCH_ITEMS_CHECK_STEPS.get(
                switch_key)
            if not self.__is_target_echo_in_switch_config(steps):
                unrecommended_switchs.append(switch_key)
        return unrecommended_switchs

    def switch_items_2_allow_patching(self, config_items):
        """
        do switch configs to target status
        :param config_items : list,
        that the configuration is not supported during patching operation.
        :return: is_success{: boolean}, altered_items{list},error_message
        """
        log.info(self.data_dict, 'switching configuration status...')
        altered_items = []
        for item_key in config_items:
            steps = SwitchCheckingItems.SWITCH_ITEMS_2_DO_SWITCH.get(item_key)
            try:
                is_suc = self.__is_target_echo_in_switch_config(steps)
                altered_items.append(item_key)
                if is_suc:
                    log.info(self.data_dict,
                             "configuration of %s is altered." % item_key)

                else:
                    log.error(self.data_dict, "try to configure %s's state "
                                              "but failed." % item_key)
                    return False, altered_items, \
                        _get_item_err_msg(item_key, steps)
            except (Exception, JException):
                log.error(
                    self.data_dict, "exception: %s" % traceback.format_exc(0))
                altered_items.append(item_key)
                return False, altered_items, _get_item_err_msg(item_key, steps)
        log.info(self.data_dict, "all items status has been altered.")
        return True, altered_items, ''

    def __is_target_echo_in_switch_config(self, steps):
        """
        checking whether one switch's configuration passed the recommended
        test.
        :param steps: the steps of current switch's check criteria.
        :return: True or False.
        """
        for step in steps:
            if step.get("target_function", ""):
                configure = SelfDefinedOperator(self.data_dict)
                func = getattr(configure, step.get("target_function"))
                if func():
                    log.info(self.data_dict, "current switch status matches "
                                             "the required configuration.")
                    return True
                else:
                    log.info(self.data_dict,
                             "current switch status matches not the required "
                             "configuration or "
                             "executed failed.")
                    return False
            else:
                cli_mode = step.get("mode")
                if cli_mode == "developer":
                    is_suc, cli_ret, err_msg = cliUtils \
                        .excuteCmdInDeveloperMode(
                            self.ssh_con, step.get("cmd"), True)
                    if not is_suc:
                        raise ExecutionException("error.on.execution")
                    if step.get("target_echo").lower() in cli_ret.lower():
                        return True
                    else:
                        return False

    def recover_configure_status_2_last_modified(self, config_items):
        """
        recover all altered configuration to the status before the patching
        operation is executed.
        :param config_items: all items that have been altered
        :return:failed_items: list-- failed items
        """
        log.info(self.data_dict,
                 "now start to recover all altered items to last status...")
        failed_items = []
        for item_key in config_items:

            steps = SwitchCheckingItems.SWITCH_ITEMS_2_RECOVERY.get(
                item_key)
            try:

                is_suc = self.__is_target_echo_in_switch_config(steps)

                if is_suc:
                    log.info(self.data_dict,
                             "configuration of %s is recovered." % item_key)
                else:
                    log.error(self.data_dict,
                              "try to recover %s's state but failed." %
                              item_key)
                    failed_items.append(_get_item_err_msg(item_key, steps))
            except (Exception, JException):
                log.error(self.data_dict,
                          "exception occurred on recover item." +
                          traceback.format_exc(0))
                failed_items.append(_get_item_err_msg(item_key, steps))

        return failed_items

    def __is_cur_dev_has_issue(self, switch_key):
        """
        recoding whether current device's software version and product model
        has this issue(specified configuration).
        :param switch_key: the configuration name.
        :return: True/False
        """
        dev_node = self.data_dict.get("dev")
        software_ver = dev_node.getProductVersion()
        log.info(self.data_dict,
                 "current device's software version: %s" % software_ver)
        cur_dev_model = dev_node.getDeviceType().toString()
        log.info(self.data_dict,
                 "current device's device model: %s" % cur_dev_model)
        return software_ver in \
            SwitchCheckingItems.SWITCH_ITEMS_INVOLVED_VERSION.get(
                switch_key).get("software_ver") \
            and cur_dev_model.replace(" ", "") in \
            SwitchCheckingItems.SWITCH_ITEMS_INVOLVED_VERSION.get(
                switch_key).get("product_model")


class SwitchCheckingItems:
    """
    Switch Checking Items configuration utils.
    """
    SWITCH_ITEMS_RECOMMENDED_STATUS = ["FDSA"]
    # the target echos should match the scenarios that should not switch the
    # switch
    SWITCH_ITEMS_CHECK_STEPS = {"FDSA": [{"cmd": "show fdsa switch",
                                          "mode": "developer",
                                          "target_echo": "switch : off"}]}
    # alter cmd list:
    SWITCH_ITEMS_2_DO_SWITCH = {
        "FDSA": [{"cmd": "change user_mode current_mode user_mode=developer\n"
                         "change fdsa switch switch=no",
                  "target_function": "_try_to_switch_off_fdsa"}]}

    # recover to last status cmd list:
    SWITCH_ITEMS_2_RECOVERY = {
        "FDSA": [{"cmd": "change user_mode current_mode user_mode=developer\n"
                         "change fdsa switch switch=yes",
                  "target_function": "_try_to_switch_on_fdsa",
                  "target_echo": "switch : on"}]}

    # target product model and software version:
    SWITCH_ITEMS_INVOLVED_VERSION = {"FDSA": {
        "product_model": "2100V3,2200V3,2600V3,2800V3,5300V3,5500V3,5600V3,"
                         "5800V3,6800V3,18500V3,18800V3, "
                         "2800V5,5110V5,5210V5,5300V5,5500V5,5500V5Elite,"
                         "5600V5,5800V5,6800V5,18500V5,18800V5, "
                         "Dorado5000V3,Dorado6000V3,Dorado18000V3,"
                         "Dorado3000V3, "
                         "Dorado18000V3,DoradoNAS",
        "software_ver": "V300R003C20,V300R003C20SPC100,V300R003C20SPC200,"
                        "V300R005C00,V300R005C00SPC300,V300R005C01, "
                        "V300R006C00,V300R006C00SPC100,V300R006C01,"
                        "V300R006C10,V300R006C10SPC100,V300R006C20, "
                        "V300R006C20SPC100,V300R006C30,V500R007C00,"
                        "V500R007C00SPC100,V500R007C10,V500R007C20, "
                        "V300R001C00,V300R001C01,V300R1C00SPC100,"
                        "V300R001C01SPC100,V300R001C20,V300R001C21, "
                        "V300R001C21SPC100,V300R001C30,V300R001C30SPC100"}}


class ExecutionException(Exception):
    """
    Execution Exception: a self defined exception class inherited of Exception.
    """

    def __init__(self, info):
        """
        an sub-exception of base  Exception.
        :param info: error message.
        """
        super(ExecutionException, self).__init__(info)
        self.exception_info = info


class SelfDefinedOperator:
    """
    For those operations that the auto-config method does not support
    """

    def __init__(self, data_dict):
        """
        initiator
        :param data_dict: context
        """
        self.data_dict = data_dict

    def _try_to_switch_on_fdsa(self):
        """
        this function try to make the fdsa switch status as on
        :return: True/False
        """
        try:
            return self.__change_fdsa_switch_status("yes")
        except JException as exp:
            log.error(self.data_dict,
                      "exception occurred on _try_to_switch_on_fdsa : %s." %
                      exp.toString())
            return False

    def _try_to_switch_off_fdsa(self):
        """
        this function try to make the fdsa switch status as OFF
        :return: True/False
        """
        try:
            return self.__change_fdsa_switch_status("no")
        except JException as exp:
            log.error(self.data_dict,
                      "exception occurred on try_to_switch_off_fdsa : %s." %
                      exp.toString())
            return False

    def __change_fdsa_switch_status(self, switch_status):
        """
        switch status  of fdsa configuration as 'switch_status'
        :param switch_status: on/off
        :raise ToolException
        :return:
        """
        ssh_con = self.data_dict['ssh']
        cmd = "change fdsa switch switch=" + switch_status
        cliUtils.execCmdInDeveloperModePrompt(ssh_con, cmd, True)
        cmd = 'show fdsa switch'
        is_suc, cli_ret, err_msg = cliUtils \
            .excuteCmdInDeveloperMode(ssh_con, cmd, True)
        is_ok = {"no": "switch : off", "yes": "switch : on"} \
            .get(switch_status) in cli_ret.lower()
        return is_ok
