# Copyright (c) 2019 Intel and/or its affiliates.
# 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.

"""Loadbalancer util library."""

from ipaddress import ip_address
from socket import htonl

from resources.libraries.python.topology import NodeType, Topology
from resources.libraries.python.PapiExecutor import PapiSocketExecutor


class LoadBalancerUtil:
    """Basic Loadbalancer parameter configuration."""

    @staticmethod
    def vpp_lb_conf(node, **kwargs):
        """Config global parameters for loadbalancer.

        :param node: Node where the interface is.
        :param kwargs: Optional key-value arguments:

            ip4_src_addr: IPv4 address to be used as source for IPv4 traffic.
                          (str)
            ip6_src_addr: IPv6 address to be used as source for IPv6 traffic.
                          (str)
            flow_timeout: Time in seconds after which, if no packet is received
                          for a given flow, the flow is removed from the
                          established flow table. (int)
            buckets_per_core: Number of buckets *per worker thread* in the
                              established flow table (int)

        :type node: dict
        :type kwargs: dict
        :returns: Nothing.
        :raises ValueError: If the node has an unknown node type.
        """
        if node[u"type"] == NodeType.DUT:
            ip4_src_addr = ip_address(
                kwargs.pop(u"ip4_src_addr", u"255.255.255.255")
            )
            ip6_src_addr = ip_address(
                kwargs.pop(
                    u"ip6_src_addr", u"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
                )
            )
            flow_timeout = kwargs.pop(u"flow_timeout", 40)
            sticky_buckets_per_core = kwargs.pop(u"buckets_per_core", 1024)

            cmd = u"lb_conf"
            err_msg = f"Failed to set lb conf on host {node[u'host']}"
            args = dict(
                ip4_src_address=str(ip4_src_addr),
                ip6_src_address=str(ip6_src_addr),
                sticky_buckets_per_core=sticky_buckets_per_core,
                flow_timeout=flow_timeout
            )

            with PapiSocketExecutor(node) as papi_exec:
                papi_exec.add(cmd, **args).get_reply(err_msg)
        else:
            raise ValueError(
                f"Node {node[u'host']} has unknown NodeType: '{node[u'type']}'"
            )

    @staticmethod
    def vpp_lb_add_del_vip(node, **kwargs):
        """Config vip for loadbalancer.

        :param node: Node where the interface is.
        :param kwargs: Optional key-value arguments:

            vip_addr: IPv4 address to be used as source for IPv4 traffic. (str)
            protocol: tcp or udp. (int)
            port: destination port. (int)
            encap: encap is ip4 GRE(0) or ip6 (1GRE) or L3DSR(2) or NAT4(3) or
                   NAT6(4). (int)
            dscp: dscp bit corresponding to VIP
            type: service type
            target_port: Pod's port corresponding to specific service
            node_port: Node's port
            new_len: Size of the new connections flow table used
                     for this VIP
            is_del: 1 if the VIP should be removed otherwise 0.

        :type node: dict
        :type kwargs: dict
        :returns: Nothing.
        :raises ValueError: If the node has an unknown node type.
        """
        if node[u"type"] == NodeType.DUT:
            vip_addr = kwargs.pop(u"vip_addr", "0.0.0.0")
            protocol = kwargs.pop(u"protocol", 255)
            port = kwargs.pop(u"port", 0)
            encap = kwargs.pop(u"encap", 0)
            dscp = kwargs.pop(u"dscp", 0)
            srv_type = kwargs.pop(u"srv_type", 0)
            target_port = kwargs.pop(u"target_port", 0)
            node_port = kwargs.pop(u"node_port", 0)
            new_len = kwargs.pop(u"new_len", 1024)
            is_del = kwargs.pop(u"is_del", 0)

            cmd = u"lb_add_del_vip"
            err_msg = f"Failed to add vip on host {node[u'host']}"

            vip_addr = ip_address(vip_addr).packed
            args = dict(
                pfx={
                    u"len": 128,
                    u"address": {u"un": {u"ip4": vip_addr}, u"af": 0}
                },
                protocol=protocol,
                port=port,
                encap=htonl(encap),
                dscp=dscp,
                type=srv_type,
                target_port=target_port,
                node_port=node_port,
                new_flows_table_length=int(new_len),
                is_del=is_del
            )

            with PapiSocketExecutor(node) as papi_exec:
                papi_exec.add(cmd, **args).get_reply(err_msg)
        else:
            raise ValueError(
                f"Node {node[u'host']} has unknown NodeType: '{node[u'type']}'"
            )

    @staticmethod
    def vpp_lb_add_del_as(node, **kwargs):
        """Config AS for Loadbalancer.

        :param node: Node where the interface is.
        :param kwargs: Optional key-value arguments:

            vip_addr: IPv4 address to be used as source for IPv4 traffic. (str)
            protocol: tcp or udp. (int)
            port: destination port. (int)
            as_addr: The application server address. (str)
            is_del: 1 if the VIP should be removed otherwise 0. (int)
            is_flush: 1 if the sessions related to this AS should be flushed
                      otherwise 0. (int)

        :type node: dict
        :type kwargs: dict
        :returns: Nothing.
        :raises ValueError: If the node has an unknown node type.
        """
        if node[u"type"] == NodeType.DUT:
            cmd = u"lb_add_del_as"
            err_msg = f"Failed to add lb as on host {node[u'host']}"

            vip_addr = kwargs.pop(u"vip_addr", "0.0.0.0")
            protocol = kwargs.pop(u"protocol", 255)
            port = kwargs.pop(u"port", 0)
            as_addr = kwargs.pop(u"as_addr", u"0.0.0.0")
            is_del = kwargs.pop(u"is_del", 0)
            is_flush = kwargs.pop(u"is_flush", 0)

            vip_addr = ip_address(vip_addr).packed
            as_addr = ip_address(as_addr).packed

            args = dict(
                pfx={
                    u"len": 128,
                    u"address": {u"un": {u"ip4": vip_addr}, u"af": 0}
                },
                protocol=protocol,
                port=port,
                as_address={u"un": {u"ip4": as_addr}, u"af": 0},
                is_del=is_del,
                is_flush=is_flush
            )

            with PapiSocketExecutor(node) as papi_exec:
                papi_exec.add(cmd, **args).get_reply(err_msg)
        else:
            raise ValueError(
                f"Node {node[u'host']} has unknown NodeType: '{node[u'type']}'"
            )

    @staticmethod
    def vpp_lb_add_del_intf_nat4(node, **kwargs):
        """Enable/disable NAT4 feature on the interface.

        :param node: Node where the interface is.
        :param kwargs: Optional key-value arguments:

            is_add: true if add, false if delete. (bool)
            interface: software index of the interface. (int)

        :type node: dict
        :type kwargs: dict
        :returns: Nothing.
        :raises ValueError: If the node has an unknown node type.
        """
        if node[u"type"] == NodeType.DUT:
            cmd = u"lb_add_del_intf_nat4"
            err_msg = f"Failed to add interface nat4 on host {node[u'host']}"

            is_add = kwargs.pop(u"is_add", True)
            interface = kwargs.pop(u"interface", 0)
            sw_if_index = Topology.get_interface_sw_index(node, interface)
            args = dict(
                is_add=is_add,
                sw_if_index=sw_if_index
            )

            with PapiSocketExecutor(node) as papi_exec:
                papi_exec.add(cmd, **args).get_reply(err_msg)
        else:
            raise ValueError(
                f"Node {node[u'host']} has unknown NodeType: '{node[u'type']}'"
            )