###
# Copyright 2016-2021 Hewlett Packard Enterprise, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###

# -*- coding: utf-8 -*-
""" Save Command for RDMC """

import json

from collections import OrderedDict

import redfish.ris

from ilorest.rdmc_helper import ReturnCodes, InvalidCommandLineErrorOPTS, \
    InvalidCommandLineError, InvalidFileFormattingError, Encryption, iLORisCorruptionError

# default file name
__filename__ = 'ilorest.json'


class SaveCommand():
    """ Constructor """

    def __init__(self):
        self.ident = {
            'name': 'save',
            'usage': None,
            'description': 'Run to save a selected type to a file'
                     '\n\texample: save --selector HpBios.\n\n\tChange the default '
                     'output filename\n\texample: save --selector HpBios. -f '
                     'output.json\n\n\tTo save multiple types in one file\n\texample: '
                     'save --multisave Bios.,ComputerSystem.',
            'summary': "Saves the selected type's settings to a file.",
            'aliases': [],
            'auxcommands': ["SelectCommand"]
        }
        self.filename = __filename__
        self.cmdbase = None
        self.rdmc = None
        self.auxcommands = dict()

    def run(self, line, help_disp=False):
        """ Main save worker function

        :param line: command line input
        :type line: string.
        """
        if help_disp:
            self.parser.print_help()
            return ReturnCodes.SUCCESS
        try:
            (options, args) = self.rdmc.rdmc_parse_arglist(self, line)
            if not line or line[0] == "help":
                self.parser.print_help()
                return ReturnCodes.SUCCESS
        except (InvalidCommandLineErrorOPTS, SystemExit):
            if ("-h" in line) or ("--help" in line):
                # self.rdmc.ui.printer(self.ident['usage'])
                return ReturnCodes.SUCCESS
            else:
                raise InvalidCommandLineErrorOPTS("")

        self.savevalidation(options)

        if args:
            raise InvalidCommandLineError('Save command takes no arguments.')

        self.rdmc.ui.printer("Saving configuration...\n")
        if options.filter:
            try:
                if (str(options.filter)[0] == str(options.filter)[-1]) \
                        and str(options.filter).startswith(("'", '"')):
                    options.filter = options.filter[1:-1]

                (sel, val) = options.filter.split('=')
                sel = sel.strip()
                val = val.strip()
            except:
                raise InvalidCommandLineError("Invalid filter"
                                              " parameter format [filter_attribute]=[filter_value]")

            instances = self.rdmc.app.select(selector=self.rdmc.app.selector,
                                             fltrvals=(sel, val), path_refresh=options.ref)
            contents = self.saveworkerfunction(instances=instances)
        else:
            contents = self.saveworkerfunction()

        if options.multisave:
            for select in options.multisave:
                self.auxcommands['select'].run(select)
                contents += self.saveworkerfunction()

        if not contents:
            raise redfish.ris.NothingSelectedError
        else:
            # TODO: Maybe add this to the command. Not sure we use it elsewhere in the lib
            contents = self.add_save_file_header(contents)

        if options.encryption:
            with open(self.filename, 'wb') as outfile:
                outfile.write(Encryption().encrypt_file(json.dumps(contents,
                                                                   indent=2, cls=redfish.ris.JSONEncoder),
                                                        options.encryption))
        else:
            with open(self.filename, 'w') as outfile:
                outfile.write(json.dumps(contents, indent=2, cls=redfish.ris.JSONEncoder,
                                         sort_keys=True))
        self.rdmc.ui.printer("Configuration saved to: %s\n" % self.filename)

        self.cmdbase.logout_routine(self, options)

        # Return code
        return ReturnCodes.SUCCESS

    def saveworkerfunction(self, instances=None):
        """ Returns the currently selected type for saving

        :param instances: list of instances from select to save
        :type instances: list.
        """

        content = self.rdmc.app.getprops(insts=instances)
        try:
            contents = [{val[self.rdmc.app.typepath.defs.hrefstring]: val} for val in content]
        except KeyError:
            try:
                contents = [{val['links']['self'][self.rdmc.app.typepath.defs.hrefstring]: val} \
                        for val in content]
            except KeyError:
                raise iLORisCorruptionError("iLO Database seems to be corrupted. Please check. Reboot the server to "
                                            "restore\n")
        type_string = self.rdmc.app.typepath.defs.typestring

        templist = list()

        for content in contents:
            typeselector = None
            pathselector = None

            for path, values in content.items():

                for dictentry in list(values.keys()):
                    if dictentry == type_string:
                        typeselector = values[dictentry]
                        pathselector = path
                        del values[dictentry]

                if values:
                    tempcontents = dict()

                    if typeselector and pathselector:
                        tempcontents[typeselector] = {pathselector: values}
                    else:
                        raise InvalidFileFormattingError("Missing path or selector in input file.")

                templist.append(tempcontents)

        return templist

    def nested_sort(self, data):
        """ Helper function to sort all dictionary key:value pairs

        :param data: dictionary to sort
        :type data: dict.
        """

        for key, value in data.items():
            if isinstance(value, dict):
                data[key] = self.nested_sort(value)

        data = OrderedDict(sorted(list(data.items()), key=lambda x: x[0]))

        return data

    def savevalidation(self, options):
        """ Save method validation function

        :param options: command line options
        :type options: list.
        """

        if options.multisave:
            options.multisave = options.multisave.replace('"', '').replace("'", '')
            options.multisave = options.multisave.replace(' ', '').split(',')
            if not len(options.multisave) >= 1:
                raise InvalidCommandLineError("Invalid number of types in multisave option.")
            options.selector = options.multisave[0]
            options.multisave = options.multisave[1:]

        self.cmdbase.login_select_validation(self, options)

        # filename validations and checks
        self.filename = None

        if options.filename and len(options.filename) > 1:
            raise InvalidCommandLineError("Save command doesn't support multiple filenames.")
        elif options.filename:
            self.filename = options.filename[0]
        elif self.rdmc.config:
            if self.rdmc.config.defaultsavefilename:
                self.filename = self.rdmc.config.defaultsavefilename

        if not self.filename:
            self.filename = __filename__

    def add_save_file_header(self, contents):
        """ Helper function to retrieve the comments for save file

        :param contents: current save contents
        :type contents: list.
        """
        templist = list()

        headers = self.rdmc.app.create_save_header()
        templist.append(headers)

        for content in contents:
            templist.append(content)

        return templist

    def definearguments(self, customparser):
        """ Wrapper function for new command main function

        :param customparser: command line input
        :type customparser: parser.
        """
        if not customparser:
            return

        self.cmdbase.add_login_arguments_group(customparser)
        customparser.add_argument(
            '-f',
            '--filename',
            dest='filename',
            help="Use this flag if you wish to use a different filename than the default one. " \
                 "The default filename is %s." % __filename__,
            action="append",
            default=None,
        )

        customparser.add_argument(
            '--selector',
            dest='selector',
            help="Optionally include this flag to select a type to run the current command on. " \
                 "Use this flag when you wish to select a type without entering another command, " \
                 "or if you wish to work with a type that is different from the one currently " \
                 "selected.",
            default=None,
        )
        customparser.add_argument(
            '--multisave',
            dest='multisave',
            help="Optionally include this flag to save multiple types to a single file. " \
                 "Overrides the currently selected type.\n\t Usage: --multisave type1.,type2.,type3.",
            default='',
        )
        customparser.add_argument(
            '--filter',
            dest='filter',
            help="Optionally set a filter value for a filter attribute. This uses the provided " \
                 "filter for the currently selected type. Note: Use this flag to narrow down your " \
                 "results. For example, selecting a common type might return multiple objects that " \
                 "are all of that type. If you want to modify the properties of only one of those " \
                 "objects, use the filter flag to narrow down results based on properties." \
                 "\n\t Usage: --filter [ATTRIBUTE]=[VALUE]",
            default=None,
        )
        customparser.add_argument(
            '-j',
            '--json',
            dest='json',
            action="store_true",
            help="Optionally include this flag if you wish to change the displayed output to " \
                 "JSON format. Preserving the JSON data structure makes the information easier to " \
                 "parse.",
            default=False,
        )
        customparser.add_argument(
            '--encryption',
            dest='encryption',
            help="Optionally include this flag to encrypt/decrypt a file using the key provided.",
            default=None,
        )
