###
# Copyright 2020 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 -*-
"""Compatibility functionality in between iLO versions and generic Redfish/LegacyRest servers.
Used to provide convenient string variables that are usable on any iLO irrespective of version or
API type."""
# ---------Imports---------
import logging

from redfish import LegacyRestClient, RedfishClient
from redfish.rest.v1 import ServerDownOrUnreachableError
from redfish.ris.rmc_helper import (
    NothingSelectedError,
    UnableToObtainIloVersionError,
    UserNotAdminError,
)

# ---------End of imports---------

LOGGER = logging.getLogger(__name__)


# TODO: This will be a part of the compatability class
class Typesandpathdefines(object):
    """The global types and path definitions class. Holds information on a system and automatically
    creates the correct type strings along with some generic paths. Paths are meant to be used with
    iLO systems. Paths may be different on generic Redfish systems. Self variables are created when
    the `getgen` function is called.

    Useful self variables that are created include:

    * **url**: The url of the system that the defines were created for.
    * **defs**: The string defines for the system that was passed to `getgen`. Includes key
      property keys, paths, types, and flags to check what the system type is.
    * **ilogen**: The iLO generation of the system that the defines were created for. For non-iLO
      Redfish systems this is set to **5**.
    * **iloversion**: The iLO version of the system that the defines were created for.
    * **flagiften**: Flag is set to true if the system is Gen 10 or a non-iLO Redfish system.
    """

    def __init__(self):
        self.url = None
        self.defs = None
        self.ilogen = None
        self.iloversion = None
        self.flagiften = False
        self.adminpriv = True

    def getgen(
        self,
        gen=None,
        url=None,
        username=None,
        password=None,
        sessionid=None,
        logger=None,
        proxy=None,
        ca_cert_data={},
        isredfish=True,
        login_otp=None,
        log_dir=None,
    ):
        """Function designed to verify the servers platform. Will generate the `Typeandpathdefines`
        variables based on the system type that is detected.

        :param url: The URL to perform the request on.
        :type url: str
        :param username: The username to login with.
        :type username: str
        :param password: The password to login with.
        :type password: str
        :param proxy: The proxy to connect to the system with.
        :type proxy: str
        :param ca_certs: Dictionary including the TLS certificate information of user based
          authentication
        :type ca_certs: dict
        :param isredfish: The flag to force redfish conformance on iLO 4 systems. You will still
          need to call `updatedefinesflag` to update the defines to Redfish.
        :type isredfish: bool
        :param logger: The logger handler to log data too uses the default if none is provided.
        :type logger: str
        """

        if self.adminpriv is False and url.startswith("blob"):
            raise UserNotAdminError("")

        self.url = url
        self.is_redfish = isredfish
        self.gencompany = self.rootresp = False
        self.ilogen = 5  # If no iLO or Anonymous data , default to iLO 5 types
        logger = logger if not logger else LOGGER
        client = None
        self.noschemas = False
        self.schemapath = self.regpath = ""

        if not gen:
            try_count = 0
            try:
                client = RedfishClient(
                    base_url=self.url,
                    username=username,
                    password=password,
                    sessionid=sessionid,
                    proxy=proxy,
                    ca_cert_data=ca_cert_data,
                    login_otp=login_otp,
                    log_dir=log_dir,
                )
                client._get_root()
            except ServerDownOrUnreachableError as excp:
                if self.is_redfish:
                    raise excp
                try_count += 1
            if not self.is_redfish:
                try:
                    restclient = LegacyRestClient(
                        base_url=self.url,
                        username=username,
                        password=password,
                        sessionid=sessionid,
                        proxy=proxy,
                        ca_cert_data=ca_cert_data,
                        login_otp=login_otp
                    )
                    restclient._get_root()
                    # Check that the response is actually legacy rest and not a redirect
                    _ = restclient.root.obj.Type
                    self.is_redfish = False
                    client = restclient
                except Exception as excp:
                    try_count += 1
                    if not client:
                        logger.info("Gen get rest error:" + str(excp) + "\n")
                        raise excp
                    else:
                        self.is_redfish = True

            if try_count > 1:
                raise ServerDownOrUnreachableError(
                    "Server not reachable or does not support " "HPRest or Redfish \n"
                )

            rootresp = client.root.obj
            self.rootresp = rootresp
            client.logout()

            self.gencompany = next(iter(self.rootresp.get("Oem", {}).keys()), None) in (
                "Hpe",
                "Hp",
            )
            comp = "Hp" if self.gencompany else None
            comp = "Hpe" if rootresp.get("Oem", {}).get("Hpe", None) else comp
            if comp and next(iter(rootresp.get("Oem", {}).get(comp, {}).get("Manager", {}))).get("ManagerType", None):
                self.ilogen = next(iter(rootresp.get("Oem", {}).get(comp, {}).get("Manager", {}))).get("ManagerType")
                self.ilover = next(iter(rootresp.get("Oem", {}).get(comp, {}).get("Manager", {}))).get(
                    "ManagerFirmwareVersion"
                )
                if self.ilogen.split(" ")[-1] == "CM":
                    # Assume iLO 4 types in Moonshot
                    self.ilogen = 4
                    self.iloversion = None
                else:
                    self.iloversion = float(self.ilogen.split(" ")[-1] + "." + "".join(self.ilover.split(".")))
        else:
            self.ilogen = int(gen)

        try:
            if not isinstance(self.ilogen, int):
                self.ilogen = int(self.ilogen.split(" ")[-1])
            self.flagiften = True if int(self.ilogen) >= 5 else False
        except:
            raise UnableToObtainIloVersionError("Unable to find the iLO generation.")

        self.noschemas = (
            True
            if self.rootresp and "JsonSchemas" in self.rootresp and not self.rootresp.get("JsonSchemas", None)
            else False
        )
        if self.noschemas:
            self.ilogen = self.ilover = self.iloversion = None
        if self.rootresp and not self.noschemas:
            self.defineregschemapath(self.rootresp)

        if self.flagiften:
            self.defs = Definevalstenplus()
        else:
            self.defs = DefinevalsNine()

    def defineregschemapath(self, rootobj):
        """Defines the schema and registry paths using data in root path.

        :param rootobj: The root path data.
        :type rootobj: dict.
        """
        self.gencompany = next(iter(rootobj.get("Oem", {}).keys()), None) in ("Hpe", "Hp")
        self.schemapath = (
            rootobj["JsonSchemas"]["@odata.id"]
            if rootobj.get("JsonSchemas", None)
            else rootobj["links"]["Schemas"]["href"]
        )
        self.schemapath = (
            self.schemapath.rstrip("/") + "/?$expand=."
            if self.is_redfish and self.flagiften and self.gencompany
            else self.schemapath
        )
        self.regpath = (
            rootobj["Registries"]["@odata.id"]
            if rootobj.get("Registries", None)
            else rootobj["links"]["Registries"]["href"]
        )
        self.regpath = (
            self.regpath.rstrip("/") + "/?$expand=."
            if self.is_redfish and self.flagiften and self.gencompany
            else self.regpath
        )

    # TODO: Move these to a compatability class
    def updatedefinesflag(self, redfishflag=None):
        """Updates the redfish and rest flag depending on system and redfishflag input. On an iLO 5
        system or another Redfish system, this will do nothing. On an iLO 4 system with both Redfish
        and LegacyRest this will update the defines to redfish if the *redfishflag* is set to True
        and stay with the LegacyRest defines otherwise.

        :param redfishflag: User input for redfish
        :type redfishflag: bool
        :returns: True if the system should use Redfish, False for legacy Rest.
        :rtype: bool
        """
        if self.defs:
            is_redfish = redfishflag or self.defs.isgen10
            self.defs.flagforrest = not is_redfish
            if is_redfish:
                self.defs.redfishchange()

            return is_redfish
        else:
            return redfishflag

    def modifyselectorforgen(self, sel):
        """Changes the select to match the Generation's HP string based to the correct type for
        specific iLO versions.

        :param sel: query to be changed to match Generation's HP string
        :type sel: str
        :returns: A modified selector matching the Generation's HP string.
        :rtype: string
        """
        if not sel:
            raise NothingSelectedError()
        sel = sel.lower()
        returnval = sel

        if sel.startswith(("hpeeskm", "#hpeeskm", "hpeskm", "#hpeskm")):
            returnval = self.defs.hpeskmtype
        elif "bios." in sel[:9].lower():
            returnval = self.defs.biostype
        elif sel.startswith(("hpe", "#hpe")) and self.defs and self.defs.isgen9:
            returnval = sel[:4].replace("hpe", "hp") + sel[4:]
        elif not sel.startswith(("hpe", "#hpe")) and self.defs and self.defs.isgen10:
            returnval = sel[:3].replace("hp", "hpe") + sel[3:]

        return returnval


class Definevals(object):
    """Base class for setting platform dependent variables."""

    def __init__(self):
        pass


class Definevalstenplus(Definevals):
    """Platform dependent variables for iLO 5+ (Gen 10)."""

    # pylint: disable=too-many-instance-attributes
    # As a defines class this will need all the attributes
    def __init__(self):
        self.oemhp = "Hpe"

        self.oempath = "/Oem/Hpe"
        self.startpath = "/redfish/v1/"
        self.systempath = "/redfish/v1/Systems/1/"
        self.managerpath = "/redfish/v1/Managers/1/"
        self.biospath = "/redfish/v1/systems/1/bios/"
        self.addlicensepath = "/redfish/v1/Managers/1/LicenseService/"
        self.accountspath = "/redfish/v1/AccountService/Accounts/"
        self.federationpath = "/redfish/v1/Managers/1/FederationGroups/"
        self.resourcedirpath = "/redfish/v1/ResourceDirectory/"

        self.biostype = "Bios."
        self.hpeskmtype = "HpeESKM."
        self.hpcommontype = "HpeCommon"
        self.hpilossotype = "HpeiLOSSO."
        self.hpsecureboot = "SecureBoot."
        self.logservicetype = "#LogService."
        self.iscsisource = "iSCSISources"
        self.iscsiattemptinstance = "iSCSIAttemptInstance"
        self.iscsiattemptname = "iSCSIAttemptName"
        self.hphttpscerttype = "HpeHttpsCert."
        self.snmpservice = "HpeiLOSnmpService."
        self.attributenametype = "AttributeName"
        self.hpilodatetimetype = "HpeiLODateTime."
        self.attributeregtype = "#AttributeRegistry."
        self.hpilofirmwareupdatetype = "UpdateService."
        self.resourcedirectorytype = "HpeiLOResourceDirectory."
        self.hpilofederationgrouptype = "HpeiLOFederationGroup."
        self.managernetworkservicetype = "ManagerNetworkProtocol."
        self.schemafilecollectiontype = "#JsonSchemaFileCollection."
        self.regfilecollectiontype = "#MessageRegistryFileCollection."
        self.hpilolicensecollectiontype = "HpeiLOLicenseCollection."
        self.hpiloactivehealthsystemtype = "#HpeiLOActiveHealthSystem."
        self.securityservice = "HpeSecurityService."
        self.hpiscsisoftwareinitiatortype = "HpeiSCSISoftwareInitiator."
        self.hpilofederationgrouptypecoll = "HpeiLOFederationGroupCollection."
        self.bootoverridetargettype = "BootSourceOverrideTarget@Redfish.AllowableValues"
        self.messageregistrytype = "#MessageRegistry."

        self.typestring = "@odata.type"
        self.hrefstring = "@odata.id"
        self.collectionstring = "Members"
        self.biossettingsstring = "@Redfish.Settings"
        self.attname = "AttributeName"
        self.iscsistring = "iSCSISources"

        self.isgen9 = False
        self.isgen10 = True
        self.flagforrest = False
        super(Definevalstenplus, self).__init__()

    def redfishchange(self):
        """Empty function to update redfish variables (unneeded when the system is already redfish)."""
        pass


class DefinevalsNine(Definevals):
    """Platform dependent variables for iLO 4 LegacyRest (Gen 9)."""

    # pylint: disable=too-many-instance-attributes
    # As a defines class this will need all the attributes
    def __init__(self):
        self.oemhp = "Hp"

        self.oempath = "/Oem/Hp"
        self.startpath = "/rest/v1"
        self.systempath = "/rest/v1/Systems/1"
        self.managerpath = "/rest/v1/Managers/1"
        self.biospath = "/rest/v1/systems/1/bios"
        self.addlicensepath = "/rest/v1/Managers/1/LicenseService"
        self.accountspath = "/rest/v1/AccountService/Accounts"
        self.federationpath = "/rest/v1/Managers/1/FederationGroups"
        self.resourcedirpath = "/rest/v1/ResourceDirectory"

        self.biostype = "HpBios."
        self.hpeskmtype = "HpESKM."
        self.hpcommontype = "HpCommon"
        self.hpilossotype = "HpiLOSSO."
        self.snmpservice = "SnmpService."
        self.attributenametype = "Name"
        self.logservicetype = "LogService."
        self.iscsisource = "iSCSIBootSources"
        self.iscsiattemptinstance = "iSCSIBootAttemptInstance"
        self.iscsiattemptname = "iSCSIBootAttemptName"
        self.hpsecureboot = "HpSecureBoot."
        self.hphttpscerttype = "HpHttpsCert."
        self.hpilodatetimetype = "HpiLODateTime."
        self.hpilofirmwareupdatetype = "HpiLOFirmwareUpdate."
        self.resourcedirectorytype = "HpiLOResourceDirectory."
        self.hpilofederationgrouptype = "HpiLOFederationGroup."
        self.attributeregtype = "HpBiosAttributeRegistrySchema."
        self.schemafilecollectiontype = "#SchemaFileCollection."
        self.regfilecollectiontype = "#SchemaFileCollection."
        self.managernetworkservicetype = "ManagerNetworkService."
        self.hpiloactivehealthsystemtype = "HpiLOActiveHealthSystem."
        self.messageregistrytype = "MessageRegistry."
        self.hpilolicensecollectiontype = None
        self.hpilofederationgrouptypecoll = None
        self.bootoverridetargettype = "BootSourceOverrideSupported"
        self.hpiscsisoftwareinitiatortype = "HpiSCSISoftwareInitiator"

        self.typestring = "Type"
        self.hrefstring = "href"
        self.collectionstring = "Items"
        self.biossettingsstring = "SettingsResult"
        self.attname = "Name"
        self.iscsistring = "iSCSIBootSources"

        self.isgen9 = True
        self.isgen10 = False
        self.flagforrest = True
        super(DefinevalsNine, self).__init__()

    def redfishchange(self):
        """Function to update redfish variables from LegacyRest to iLO 4 Redfish (Gen 9)."""
        self.startpath = "/redfish/v1/"
        self.systempath = "/redfish/v1/Systems/1/"
        self.managerpath = "/redfish/v1/Managers/1/"
        self.biospath = "/redfish/v1/systems/1/bios/"
        self.addlicensepath = "/redfish/v1/Managers/1/LicenseService/"
        self.resourcedirpath = "/redfish/v1/ResourceDirectory/"

        self.typestring = "@odata.type"
        self.hrefstring = "@odata.id"
        self.collectionstring = "Members"

        self.logservicetype = "#LogService."
        self.hpiloactivehealthsystemtype = "#HpiLOActiveHealthSystem."
        self.hpilolicensecollectiontype = "HpiLOLicenseCollection."
        self.hpilofederationgrouptypecoll = "HpiLOFederationGroupCollection."
        self.managernetworkservicetype = "ManagerNetworkProtocol."

        self.flagforrest = False
