#
# 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.

"""
DRAC inspection interface
"""
from ironic.common import boot_modes
from ironic.drivers.modules.drac import utils as drac_utils
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import utils as redfish_utils
from oslo_log import log


_PXE_DEV_ENABLED_INTERFACES = [('PxeDev1EnDis', 'PxeDev1Interface'),
                               ('PxeDev2EnDis', 'PxeDev2Interface'),
                               ('PxeDev3EnDis', 'PxeDev3Interface'),
                               ('PxeDev4EnDis', 'PxeDev4Interface')]
_BIOS_ENABLED_VALUE = 'Enabled'
LOG = log.getLogger(__name__)

class DracRedfishInspect(redfish_inspect.RedfishInspect):
    """iDRAC Redfish interface for inspection-related actions."""

    def inspect_hardware(self, task):
        """Inspect hardware to get the hardware properties.

        Inspects hardware to get the essential properties.
        It fails if any of the essential properties
        are not received from the node.

        :param task: a TaskManager instance.
        :raises: HardwareInspectionFailure if essential properties
                 could not be retrieved successfully.
        :returns: The resulting state of inspection.

        """
        # Ensure we create a port for every NIC port found for consistency
        # with our previous WSMAN inspect behavior and to work around a bug
        # in some versions of the firmware where the port state is not being
        # reported correctly.

        ethernet_interfaces_mac = list(self._get_mac_address(task).values())
        inspect_utils.create_ports_if_not_exist(task, ethernet_interfaces_mac)

        return super(DracRedfishInspect, self).inspect_hardware(task)

    def _get_system_vendor_info(self, task, system):
        """Get system vendor information for Dell systems.

        Overrides the parent to use SKU (Dell service tag) as the
        serial number instead of the Redfish SerialNumber field,
        which on Dell systems contains the motherboard serial.

        :param task: a TaskManager instance.
        :param system: a Redfish system object.
        :returns: a dictionary of system vendor information.
        """
        system_vendor = super()._get_system_vendor_info(task, system)
        if system.sku:
            system_vendor['serial_number'] = str(system.sku)
        return system_vendor

    def _get_mac_address(self, task):
        """Get a list of MAC addresses

        :param task: a TaskManager instance.
        :returns: a mapping of interface identities to MAC addresses.
        """
        system = redfish_utils.get_system(task.node)
        # Get dictionary of ethernet interfaces
        if system.ethernet_interfaces and system.ethernet_interfaces.summary:
            ethernet_interfaces = system.ethernet_interfaces.get_members()
            ethernet_interfaces_mac = {
                interface.identity: interface.mac_address
                for interface in ethernet_interfaces}
            return ethernet_interfaces_mac
        else:
            return {}

    def _get_pxe_port_macs(self, task):
        """Get a list of PXE port MAC addresses.

        :param task: a TaskManager instance.
        :returns: Returns list of PXE port MAC addresses.
        """
        system = redfish_utils.get_system(task.node)
        ethernet_interfaces_mac = self._get_mac_address(task)
        pxe_port_macs = []

        if system.boot.mode == boot_modes.UEFI:
            # When a server is in UEFI boot mode, the PXE NIC ports are
            # stored in the PxeDevXEnDis and PxeDevXInterface BIOS
            # settings. Get the PXE NIC ports from these settings and
            # their MAC addresses.
            for param, nic in _PXE_DEV_ENABLED_INTERFACES:
                if system.bios.attributes[param] == _BIOS_ENABLED_VALUE:
                    nic_id = system.bios.attributes[nic]
                    # Get MAC address of the given nic_id
                    mac_address = ethernet_interfaces_mac[nic_id]
                    pxe_port_macs.append(mac_address)
        elif system.boot.mode == boot_modes.LEGACY_BIOS:
            # When a server is in BIOS boot mode, whether or not a
            # NIC port is set to PXE boot is stored on the NIC port
            # itself internally to the BMC. Getting this information
            # requires using an OEM extension to export the system
            # configuration, as the redfish standard does not specify
            # how to get it, and Dell does not have OEM redfish calls
            # to selectively retrieve it at this time.
            # Get instance of Sushy OEM manager object
            pxe_port_macs_list = drac_utils.execute_oem_manager_method(
                task, 'get PXE port MAC addresses',
                lambda m: m.get_pxe_port_macs_bios(ethernet_interfaces_mac))
            pxe_port_macs = [mac for mac in pxe_port_macs_list]

        return pxe_port_macs

    def _collect_lldp_data(self, task, system):
        """Collect LLDP data using Dell OEM SwitchConnection endpoints.

        Dell iDRAC provides LLDP neighbor information through OEM
        DellSwitchConnection endpoints. We return parsed LLDP data directly.

        :param task: A TaskManager instance
        :param system: Sushy system object
        :returns: Dict mapping interface names to parsed LLDP data
        """
        parsed_lldp = {}

        try:
            # Get Dell switch connection data
            switch_data = self._get_dell_switch_connections(task)

            # Convert directly to parsed LLDP format
            for connection in switch_data:
                # FQDD is Fully Qualified Device Descriptor (Dell term)
                # Example: NIC.Integrated.1-1-1
                fqdd = connection.get('FQDD')
                switch_mac = connection.get('SwitchConnectionID')
                switch_port = connection.get('SwitchPortConnectionID')

                # Skip unconnected interfaces
                if (not fqdd or not switch_mac or not switch_port
                    or switch_mac == 'No Link' or switch_port == 'No Link'):
                    continue

                parsed_lldp[fqdd] = {
                    'switch_chassis_id': switch_mac,
                    'switch_port_id': switch_port
                }

            LOG.debug("Generated parsed LLDP data for %d interfaces",
                      len(parsed_lldp))

        except Exception as e:
            LOG.debug("Dell OEM LLDP collection failed, falling back to "
                      "standard: %s", e)
            # Fallback to standard Redfish LLDP collection
            return super(DracRedfishInspect, self)._collect_lldp_data(
                task, system)

        return parsed_lldp

    def _get_dell_switch_connections(self, task):
        """Fetch Dell switch connection data via OEM.

        :param task: A TaskManager instance
        :returns: List of switch connection dictionaries
        """
        system = redfish_utils.get_system(task.node)

        # Access Sushy's private connection object
        try:
            conn = system._conn
            base_url = conn._url
        except AttributeError as e:
            LOG.debug("Failed to access Sushy connection object: %s", e)
            return []

        # Dell OEM endpoint for switch connections
        # This URL structure is specific to Dell iDRAC Redfish implementation
        switch_url = (f"{base_url}/redfish/v1/Systems/{system.identity}"
                      "/NetworkPorts/Oem/Dell/DellSwitchConnections")

        LOG.debug("Fetching Dell switch connections from: %s", switch_url)

        try:
            response = conn.get(switch_url)
            data = response.json()
            members = data.get('Members', [])
            LOG.debug("Retrieved %d Dell switch connections", len(members))
            return members
        except Exception as e:
            LOG.debug("Failed to get Dell switch connections: %s", e)
            return []
