#
# Copyright (c) 2019-2022 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

"""Common interface utility and helper functions."""

import collections
import copy
import re

from oslo_log import log
from sysinv.common import constants

LOG = log.getLogger(__name__)


# NOTE: updates in the following functions should also be reflected in
#       its corresponding duplicate in openstack-armada-app nova plugin
#       - openstack-armada-app/python3-k8sapp-openstack/k8sapp_openstack/k8sapp_openstack/helm/nova.py
#
#       _get_port_interface_id_index()
#       _get_interface_name_index()
#       _get_interface_name_datanets()
#       _get_address_interface_name_index()
def _get_port_interface_id_index(dbapi, host):
    """
    Builds a dictionary of ports indexed by interface id.
    """
    ports = {}
    for port in dbapi.ethernet_port_get_by_host(host.id):
        ports[port.interface_id] = port
    return ports


def _get_interface_name_index(host_interfaces):
    """
    Builds a dictionary of interfaces indexed by interface name.
    """
    interfaces = {}
    for iface in host_interfaces:
        interfaces[iface.ifname] = iface
    return interfaces


def _get_interface_name_datanets(dbapi, hostname, host_interfaces):
    """
    Builds a dictionary of datanets indexed by interface name.
    """
    datanets = {}
    for iface in host_interfaces:
        ifdatanets = dbapi.interface_datanetwork_get_by_interface(iface.uuid)

        datanetworks = []
        for ifdatanet in ifdatanets:
            datanetworks.append(ifdatanet.datanetwork_uuid)

        datanetworks_list = []
        for datanetwork in datanetworks:
            dn = dbapi.datanetwork_get(datanetwork)
            datanetwork_dict = \
                {'name': dn.name,
                 'uuid': dn.uuid,
                 'network_type': dn.network_type,
                 'mtu': dn.mtu}
            if dn.network_type == constants.DATANETWORK_TYPE_VXLAN:
                datanetwork_dict.update(
                    {'multicast_group': dn.multicast_group,
                     'port_num': dn.port_num,
                     'ttl': dn.ttl,
                     'mode': dn.mode})
            datanetworks_list.append(datanetwork_dict)
        datanets[iface.ifname] = datanetworks_list

    LOG.debug('_get_interface_name_datanets '
              'host=%s, datanets=%s', hostname, datanets)

    return datanets


def _get_address_interface_name_index(dbapi, host):
    """
    Builds a dictionary of address lists indexed by interface name.
    """
    addresses = collections.defaultdict(list)
    for address in dbapi.addresses_get_by_host(host.id):
        addresses[address.ifname].append(address)
    return addresses


def get_interface_datanets(context, iface):
    """
    Return the list of data networks of the supplied interface
    """
    return context['interfaces_datanets'][iface.ifname]


def _get_datanetwork_names(context, iface):
    """
    Return the CSV list of data networks of the supplied interface
    """
    dnets = get_interface_datanets(context, iface)
    dnames_list = [dnet['name'] for dnet in dnets]
    dnames = ",".join(dnames_list)
    return dnames


def get_interface_port(context, iface):
    """
    Determine the port of the underlying device.
    """
    assert iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET
    try:
        # only for pxeboot on sriov ethernet interface case
        # other cases have no lower ethernet interface used
        iface = get_lower_interface(context, iface)
    finally:
        return context['ports'][iface['id']]


def get_lower_interface(context, iface):
    assert iface['iftype'] in [constants.INTERFACE_TYPE_VLAN,
                               constants.INTERFACE_TYPE_VF,
                               constants.INTERFACE_TYPE_ETHERNET]
    lower_ifname = iface['uses'][0]
    return context['interfaces'][lower_ifname]


def get_pci_device_id(port):
    """
    Determine the PCI device id of given port.
    """
    # The device id can be found by inspecting the '[xxxx]' at the
    # end of the port's pdevice field
    if not port or not port.get('pdevice', None):
        return None
    device_id = re.search(r'\[([0-9a-fA-F]{1,4})\]$', port['pdevice'])
    if device_id:
        device_id = device_id.group(1)
        return str(device_id)

    return None


def get_sriov_interface_port(context, iface):
    """
    Determine the underlying port of the SR-IOV interface.
    """
    if iface['iftype'] == constants.INTERFACE_TYPE_VF:
        lower_iface = get_lower_interface(context, iface)
        return get_sriov_interface_port(context, lower_iface)
    else:
        assert iface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV
        return get_interface_port(context, iface)


def get_sriov_interface_device_id(context, iface):
    """
    Determine the underlying PCI device id of the SR-IOV interface.
    """
    port = get_sriov_interface_port(context, iface)
    if not port:
        return None
    return get_pci_device_id(port)


def get_sriov_interface_vf_addrs(context, iface, vf_addr_list):
    """
    Determine the virtual function addresses of SR-IOV interface,
    given the list of vf addresses on the port.
    """
    vf_addrs = copy.deepcopy(vf_addr_list)

    if iface['uses']:
        lower_iface = get_lower_interface(context, iface)
        lower_vf_addrs = get_sriov_interface_vf_addrs(context, lower_iface, vf_addr_list)

        # Remove the VF addresses reserved for the lower SR-IOV interface
        vf_addrs = [addr for addr in vf_addr_list if addr not in lower_vf_addrs]

        sibling_ifaces = lower_iface['used_by']
        for sibling_ifname in sibling_ifaces:
            sibling_iface = context['interfaces'][sibling_ifname]
            if sibling_iface['iftype'] != constants.INTERFACE_TYPE_VF:
                continue
            sibling_numvfs = sibling_iface['sriov_numvfs']
            if sibling_ifname == iface['ifname']:
                # Reserve the appropriate number of VF addresses from
                # the end of the list for the interface.
                vf_addrs = vf_addrs[-iface['sriov_numvfs']:]
                break
            else:
                # Remove the VF addresses reserved for any sibling SR-IOV
                # interface
                del vf_addrs[-sibling_numvfs:]

    if iface['used_by']:
        upper_ifaces = iface['used_by']
        for upper_ifname in upper_ifaces:
            upper_iface = context['interfaces'][upper_ifname]
            if upper_iface['iftype'] != constants.INTERFACE_TYPE_VF:
                continue
            upper_numvfs = upper_iface['sriov_numvfs']
            if upper_numvfs:
                # Remove the VF addresses reserved for any child
                # SR-IOV interface
                del vf_addrs[-upper_numvfs:]

    return vf_addrs
