###
# Copyright 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 -*-
""" unifiedcertificate Command for rdmc """

from argparse import ArgumentParser, RawDescriptionHelpFormatter

from rdmc_helper import (
    ReturnCodes,
    InvalidCommandLineError,
    InvalidCommandLineErrorOPTS,
    NoContentsFoundForOperationError,
    InvalidFileInputError,
    IncompatibleiLOVersionError,
    Encryption,
)

__filename__ = "certificate.txt"


class UnifiedCertificateCommand:
    """Commands Certificates actions to the server"""

    def __init__(self):
        self.ident = {
            "name": "unifiedcertificate",
            "usage": None,
            "description": "Generate a certificate signing request (CSR) or import/export X.509 "
            "formatted TLS/SSL, SSO and/or platform certificates.\nTo view help on "
            "specific sub-commands run: "
            "unifiedcertificate <sub-command> -h\n\nExample: unifiedcertificate import -h\n\n"
            "Example: unifiedcertificate export -h\n\n",
            "summary": "Command for importing and exporting X.509 TLS/SSL, SSO and platform "
            "certificates as well as generating and exporting certificate signing "
            "requests (CSR)",
            "aliases": [],
            "auxcommands": [],
        }
        self.cmdbase = None
        self.rdmc = None
        self.auxcommands = dict()

    def run(self, line, help_disp=False):
        """Main Certificates Command function

        :param options: list of options
        :type options: list.
        :param help_disp: display help flag
        :type line: bool.
        """
        if help_disp:
            self.parser.print_help()
            return ReturnCodes.SUCCESS
        try:
            (options, _) = self.rdmc.rdmc_parse_arglist(self, line)
        except (InvalidCommandLineErrorOPTS, SystemExit):
            if ("-h" in line) or ("--help" in line):
                return ReturnCodes.SUCCESS
            else:
                raise InvalidCommandLineErrorOPTS("")

        self.certificatesvalidation(options)

        if options.command == "gen_csr":
            self.gen_csr_helper(options)
        elif options.command == "get_csr":
            self.get_csr_helper(options)
        elif options.command == "import":
            self.importhelper(options)
        elif options.command == "export":
            self.exporthelper(options)

        self.cmdbase.logout_routine(self, options)
        # Return code
        return ReturnCodes.SUCCESS

    def importhelper(self, options):
        """Main helper function for importing certificates

        :param options: options attributes
        :type options: attributes
        """
        if (
            getattr(options, "TLSCERT")
            or getattr(options, "ROOTCACERT")
            or getattr(options, "USERCACERT")
        ):
            self.importtlscahelper(options)
        else:
            self.importplatformhelper(options)

    def importplatformhelper(self, options):
        """Helper function for importing a platform certificate

        :param options: options attributes
        :type options: attributes
        """

        ss_instance = next(
            iter(self.rdmc.app.select("SecurityService.", path_refresh=True))
        )
        if getattr(options, "LDEVID"):
            type = "iLOLDevID"
        elif getattr(options, "IDEVID"):
            type = "iLOIDevID"
        elif getattr(options, "SYSTEMIAK"):
            type = "SystemIAK"
        elif getattr(options, "SYSTEMIDEVID"):
            type = "SystemIDevID"
        elif getattr(options, "PLATFORMCERT"):
            type = "PlatformCert"
        else:
            raise InvalidCommandLineErrorOPTS(
                "An invalid set of options were selected...verify "
                " options were selected correctly and re-try."
            )

        instance_path_uri = (
            (
                ss_instance.dict[type]["Certificates"][
                    self.rdmc.app.typepath.defs.hrefstring
                ]
            )
            if ss_instance.dict.get("SystemIAK")
            else None
        )
        if instance_path_uri:
            certdata = None
            try:
                with open(options.file.strip('"'), "rb") as cf:
                    try:
                        certdata = str.decode(cf.read())
                    except:
                        certdata = cf.read()
            except:
                raise InvalidFileInputError("Error loading the specified file.")

            if certdata:
                self.rdmc.app.patch_handler(instance_path_uri, instance.data["Members"])

    def importtlscahelper(self, options):
        """Helper function for importing TLS certificate

        :param options: list of options
        :type options: list.
        """

        body = {}

        def actselect(bodydict, action_item):
            if self.rdmc.app.typepath.defs.isgen10:
                action = action_item.split("#")[-1]
            else:
                action = action_item
            path = bodydict["Actions"][action_item]["target"]
            return (path, action)

        if getattr(options, "from_url"):
            body.update({"ImportUri": next(iter(options.from_url))})
        else:
            try:
                with open(next(iter(options.filename))) as certfile:
                    body.update({"Certificate": certfile.read()})
            except:
                raise InvalidFileInputError(
                    "Error loading the specified file '%s'"
                    % next(iter(options.filename))
                )

        if getattr(options, "TLSCERT"):
            select = self.rdmc.app.typepath.defs.hphttpscerttype
        else:
            select = "HpeCertAuth."
        result = self.rdmc.app.select(selector=select, path_refresh=True)
        try:
            result = result[0]
        except:
            pass

        if not result:
            raise NoContentsFoundForOperationError(
                "Unable to find '%s'" % self.rdmc.app.selector
            )
        path = action = None
        bodydict = result.dict
        try:
            for item in bodydict["Actions"]:
                if getattr(options, "from_url"):
                    if "ImportCRL" in item:
                        path, action = actselect(bodydict, item)
                        break
                elif getattr(options, "TLSCERT"):
                    if "ImportCertificate" in item:
                        path, action = actselect(bodydict, item)
                        break
                elif "ImportCACertificate" in item:
                    path, action = actselect(bodydict, item)
                    break
        except:
            if getattr(options, "from_url"):
                action = "ImportCRL"
            elif getattr(options, "TLSCERT"):
                action = "ImportCertificate"
            else:
                action = "ImportCACertificate"

        if path and action:
            body.update({"Action": action})
            self.rdmc.app.post_handler(path, body)
        else:
            raise NoContentsFoundForOperationError("Unable to import certificate.")

    def exporthelper(self, options):

        data = None
        if getattr(options, "ROOTCACERT") or getattr(options, "USERCACERT"):
            result = self.exportcahelper(options)
            # Add entries for exporting TLS Certificate, RootCA and UserCA Certificates
        elif getattr(options, "TLSCERT"):
            raise InvalidCommandLineErrorOPTS(
                "This command is currently unable to export a TLS" " certificate."
            )
            # not seemingly possible to retrieve a TLS Cert
        else:
            result = self.exportplatformhelper(options)
        if result:
            if options.filename:
                self.file_handler(next(iter(options.filename)), result, options, "wb")
                self.rdmc.ui.printer(
                    "The certificate was saved to: %s\n" % next(iter(options.filename))
                )
            else:
                self.rdmc.ui.printer(
                    "The certificate retrieved is as follows:\n%s\n" % result
                )
        else:
            self.rdmc.ui.error(
                "An error occurred retrieving the requested certificate."
            )

    def exportplatformhelper(self, options):
        """Helper function for exporting a platform certificate

        :param options: list of options
        :type options: list.
        """

        type = "PlatformCert"  # assume platform certificate
        str_type = "Platform"
        ss_instance = next(
            iter(self.rdmc.app.select("SecurityService." + ".", path_refresh=True))
        )
        if options.LDEVID:
            type = "iLOLDevID"
            str_type = "iLO lDevID"
        elif options.IDEVID:
            type = "iLOIDevID"
            str_type = "iLO iDevID"
        elif options.SYSTEMIAK:
            type = "SystemIAK"
            str_type = "System IAK"
        elif options.SYSTEMIDEVID:
            type = "SystemIDevID"
            str_type = "System iDevID"
        instance_path_uri = (
            (
                ss_instance.dict[type]["Certificates"][
                    self.rdmc.app.typepath.defs.hrefstring
                ]
            )
            if ss_instance.dict.get("SystemIAK")
            else None
        )
        instance_data = self.rdmc.app.get_handler(instance_path_uri, silent=True)
        cert = None
        if instance_data.dict.get("Members"):
            cert = self.rdmc.app.get_handler(
                instance_data.dict["Members"][getattr(options, "id", 0) - 1].get(
                    self.rdmc.app.typepath.defs.hrefstring
                ),
                silent=True,
            ).dict
            return cert.get("CertificateString")
        else:
            raise NoContentsFoundForOperationError(
                "Unable to find specified certificate at "
                "positon %s." % getattr(options, "id", 0)
            )

    def exportcahelper(self, options):

        if not self.rdmc.app.typepath.flagiften:
            raise IncompatibleiLOVersionError(
                "This certificate is not available on this system."
            )

        try:
            cacerts_path = self.rdmc.app.select(
                selector="CertAuth.", path_refresh=True
            ).dict.get("CACertificates")
        except:
            try:
                cacerts_path = next(
                    iter(self.rdmc.app.select(selector="CertAuth.", path_refresh=True))
                ).dict.get("CACertificates")
            except:
                raise NoContentsFoundForOperationError("Unable to find CA Certificates")
        instance_data = self.rdmc.app.get_handler(
            cacerts_path.get(self.rdmc.app.typepath.defs.hrefstring), silent=True
        )
        if instance_data.dict.get("Members"):
            cert = self.rdmc.app.get_handler(
                instance_data.dict["Members"][getattr(options, "id", 0) - 1].get(
                    self.rdmc.app.typepath.defs.hrefstring
                ),
                silent=True,
            ).dict
            return cert.get("CertificateString")
        else:
            raise NoContentsFoundForOperationError(
                "Unable to find specified certificate at "
                "positon %s." % getattr(options, "id", 0)
            )

    def gen_csr_helper(self, options):
        """
        :param options: list of options
        :type options: attributes.
        """
        body = None
        path = None
        action = "GenerateCSR"
        if options.TLSCERT:
            instance = next(iter(self.rdmc.app.select("HttpsCert.", path_refresh=True)))
            body = {
                "Action": action,
                "OrgName": options.csr_orgname.strip('"'),
                "OrgUnit": options.csr_orgunit.strip('"'),
                "CommonName": options.csr_commonname.strip('"'),
                "Country": options.csr_country.strip('"'),
                "State": options.csr_state.strip('"'),
                "City": options.csr_city.strip('"'),
            }

        elif options.PLATFORM:
            # There is seemingly no way to update the Certificate subject data which
            # appears problematic.
            cs_instance = next(
                iter(self.rdmc.app.select("CertificateService.", path_refresh=True))
            )
            ss_instance = next(
                iter(self.rdmc.app.select("SecurityService.", path_refresh=True))
            )
            cert_obtain_path = ss_instance.dict.get("iLOLDevID")[
                next(iter(ss_instance.dict.get("iLOLDevID")))
            ].get(self.rdmc.app.typepath.defs.hrefstring)
            if not cert_obtain_path:
                raise NoContentsFoundForOperationError(
                    "Unable to find specified certificate path"
                    " for CSR request" % cert_key
                )
            instance = next(
                iter(self.rdmc.app.select("CertificateService.", path_refresh=True))
            )
            body = {
                "CertificateCollection": {
                    self.rdmc.app.typepath.defs.hrefstring: cert_obtain_path
                }
            }

        try:
            for act in instance.dict.get("Actions"):
                if "GenerateCSR" in act:
                    if self.rdmc.app.typepath.defs.isgen10:
                        action = act.split("#")[-1]
                    else:
                        action = "GenerateCSR"
                    path = instance.dict["Actions"][act]["target"]
                    break
        except:
            raise NoContentsFoundForOperationError(
                "Unable to find specified certificate action"
                "path for CSR request" % cert_key
            )

        self.rdmc.ui.printer(
            "iLO is creating a new certificate signing request. "
            "This request may take up to 10 minutes.\n"
        )
        return self.rdmc.app.post_handler(path, body)

    def get_csr_helper(self, options):

        result = None
        if options.TLSCERT:
            instance = next(iter(self.rdmc.app.select("HttpsCert.", path_refresh=True)))
            result = instance.dict.get("CertificateSigningRequest")
        elif options.PLATFORM:
            tmp = self.gen_csr_helper(options)
            if tmp:
                result = tmp.dict.get("CSRString")

        if result:
            if options.filename:
                self.file_handler(next(iter(options.filename)), result, options, "wb")
                self.rdmc.ui.printer(
                    "The CSR was saved to: %s\n" % next(iter(options.filename))
                )
            else:
                self.rdmc.ui.printer("The CSR retrieved is as follows:\n%s\n" % result)
        else:
            self.rdmc.ui.error("An error occurred retrieving a CSR.")

    def file_handler(self, filename, data, options, operation="rb"):
        """
        Wrapper function to read or write data to a respective file
        :param data: data to be written to output file
        :type data: container (list of dictionaries, dictionary, etc.)
        :param file: filename to be written
        :type file: string (generally this should be self.clone_file or tmp_clone_file
        :param operation: file operation to be performed
        :type operation: string ('w+', 'a+', 'r+')
        :param options: command line options
        :type options: attribute
        :returns: json file data
        """
        writeable_ops = ["wb", "w", "w+", "a", "a+"]
        readable_ops = ["rb", "r", "r+"]

        if operation in writeable_ops:
            with open(filename, operation) as fh:
                try:
                    data = data.encode("UTF-8")
                except IOError:
                    raise InvalidFileInputError(
                        "Unable to write to file '%s'" % filename
                    )
                except UnicodeEncodeError:
                    pass
                finally:
                    # No encryption...yet..?
                    # if options.encryption:
                    #    fh.write(Encryption().encrypt_file(data, options.encryption))
                    # else:
                    fh.write(data)
        else:
            # No encryption...yet...?
            # if options.encryption:
            #    fdata = Encryption().decrypt_file(fh.read(), options.encryption)
            with open(filename, operation) as fh:
                try:
                    return fdata.decode("UTF-8")
                except UnicodeDecodeError:
                    return fdata
                except IOError:
                    raise InvalidFileInputError(
                        "Unable to read from file '%s'" % filename
                    )

    def certificatesvalidation(self, options):
        """certificates validation function

        :param options: command line options
        :type options: list.
        """
        self.cmdbase.login_select_validation(self, options)

    @staticmethod
    def options_import_export_argument_group(parser):
        """Define option arguments group
        :param parser: The parser to add the login option group to
        :type parser: ArgumentParser/OptionParser
        """
        group = parser.add_argument_group()

        parser.add_argument(
            "--USERCA",
            dest="USERCACERT",
            help="specify for a user CA certificate.",
            action="store_true",
            default=None,
        )
        parser.add_argument(
            "--ROOTCA",
            dest="ROOTCACERT",
            help="specify for a root CA certificate.",
            action="store_true",
            default=None,
        )
        parser.add_argument(
            "--TLS_CERT",
            dest="TLSCERT",
            help="specify for a TLS/SSL certificate.",
            action="store_true",
            default=None,
        )

        parser.add_argument(
            "-f",
            "--filename",
            dest="filename",
            help="Use this flag to import a certificate from or export a certificate to a file.",
            action="append",
            default=None,
        )

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

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

        self.cmdbase.add_login_arguments_group(customparser)

        subcommand_parser = customparser.add_subparsers(dest="command")

        # gen csr sub-parser
        gen_csr_help = (
            "Generate a certificate signing request (CSR) for iLO SSL certificate "
            "authentication.\nNote: iLO will create a Base64 encoded CSR in PKCS "
            "#10 Format."
        )
        gen_csr_parser = subcommand_parser.add_parser(
            "gen_csr",
            help=gen_csr_help,
            description=gen_csr_help
            + "\nexample: unifiedcertificate csr [ORG_NAME] [ORG_UNIT]"
            " [COMMON_NAME] [COUNTRY] [STATE] [CITY]\n\nNOTE: please make "
            "certain the order of arguments is correct. **NOTE:** Use quotes "
            "to include parameters which contain whitespace when generating a CSR."
            '\nexample: unifiedcertificate csr "Hewlett Packard Enterprise"'
            '"iLORest Group" "CName"\n"United States" "Texas" "Houston"',
            formatter_class=RawDescriptionHelpFormatter,
        )
        gen_csr_parser.add_argument(
            dest="csr_orgname",
            nargs="?",
            help="Organization name. i.e. Hewlett Packard Enterprise.",
            metavar="ORGNAME",
        )
        gen_csr_parser.add_argument(
            dest="csr_orgunit",
            nargs="?",
            help="Organization unit. i.e. Intelligent Provisioning.",
            metavar="ORGUNIT",
        )
        gen_csr_parser.add_argument(
            dest="csr_commonname",
            nargs="?",
            help="Organization common name. i.e. Common Organization Name.",
            metavar="ORGNAME",
        )
        gen_csr_parser.add_argument(
            dest="csr_country",
            nargs="?",
            help="Organization country. i.e. United States.",
            metavar="ORGCOUNTRY",
        )
        gen_csr_parser.add_argument(
            dest="csr_state",
            nargs="?",
            help="Organization state. i.e. Texas.",
            metavar="ORGSTATE",
        )
        gen_csr_parser.add_argument(
            dest="csr_city",
            nargs="?",
            help="Organization city. i.e. Houston.",
            metavar="ORGCITY",
        )
        gen_csr_parser.add_argument(
            "--TLS_CERT",
            dest="TLSCERT",
            help="specify to generate a TLS/SSL certificate signing request.",
            action="store_true",
            default=None,
        )
        gen_csr_parser.add_argument(
            "--PLATFORM_CERT",
            dest="PLATFORM",
            help="specify to generate a platform certificate signing request.",
            action="store_true",
            default=None,
        )
        self.cmdbase.add_login_arguments_group(gen_csr_parser)

        # get csr
        get_csr_help = (
            "Retrieve the generated certificate signing request (CSR) printed to the "
            "console or to a json file."
        )
        get_csr_parser = subcommand_parser.add_parser(
            "get_csr",
            help=get_csr_help,
            description=get_csr_help
            + "\nExample: unifiedcertificate getcsr [CERTIFICATE] "
            "-f mycsrfile",
            formatter_class=RawDescriptionHelpFormatter,
        )
        get_csr_parser.add_argument(
            "--TLS_CERT",
            dest="TLSCERT",
            help="specify to retrieve a TLS/SSL certificate signing request.",
            action="store_true",
            default=None,
        )
        get_csr_parser.add_argument(
            "--PLATFORM_CERT",
            dest="PLATFORM",
            help="specify to retrieve a platform certificate signing request.",
            action="store_true",
            default=None,
        )

        get_csr_parser.add_argument(
            "-f",
            "--filename",
            dest="filename",
            help="Use this flag to specify a file for a certificate signing request (CSR)",
            action="append",
            default=None,
        )
        self.cmdbase.add_login_arguments_group(get_csr_parser)

        import_help = "Push an X.509 formatted platform certificate to iLO."
        import_parser = subcommand_parser.add_parser(
            "import",
            help=import_help,
            description=import_help
            + "\nExample: unifiedcertificate import [CERTIFICATE] -f "
            "mytlsfile\n**Note: Certificates can not be imported from command line.**",
            formatter_class=RawDescriptionHelpFormatter,
        )
        import_parser.add_argument(
            "--from_url",
            dest="from_url",
            help="Use this flag to specify a URL for certificate import",
            action="append",
            default=None,
        )
        self.options_import_export_argument_group(import_parser)
        self.cmdbase.add_login_arguments_group(import_parser)

        # export cert
        export_help = "Pull an X.509 formatted platform certificate from iLO."
        export_parser = subcommand_parser.add_parser(
            "export",
            help=export_help,
            description=export_help
            + "\nExample: unifiedcertificate export [CERTIFICATE] -f "
            "mytlsfile\n",
            formatter_class=RawDescriptionHelpFormatter,
        )

        export_parser.add_argument(
            "--IDEVID",
            dest="IDEVID",
            help="Specify for an IDEVID certificate. ",
            action="store_true",
            default=None,
        )
        export_parser.add_argument(
            "--LDEVID",
            dest="LDEVID",
            help="Specify for an LDEVID certificate. ",
            action="store_true",
            default=None,
        )
        export_parser.add_argument(
            "--SYSTEMIAK",
            dest="SYSTEMIAK",
            help="Specify for a system IAK certificate.",
            action="store_true",
            default=None,
        )
        export_parser.add_argument(
            "--SYSTEMIDEVID",
            dest="SYSTEMIDEVID",
            help="Specify for a system IDEVID certificate.",
            action="store_true",
            default=None,
        )
        export_parser.add_argument(
            "--PLATFORMCERT",
            dest="PLATFORMCERT",
            help="Specify for a platform certificate.",
            action="store_true",
            default=None,
        )
        export_parser.add_argument(
            "--id",
            dest="id",
            help="Optionally specify the certificate instance, if multiples are available. If"
            "the instance specified is not available, then the next is retrieved. Default is"
            "the first instance",
            default=1,
        )

        self.options_import_export_argument_group(export_parser)
        self.cmdbase.add_login_arguments_group(export_parser)
