#
# Copyright (c) 2019-2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

import os

import pytest

import libnmstate
from libnmstate.schema import Ethernet
from libnmstate.schema import Interface
from libnmstate.schema import InterfaceState

from .testlib import assertlib
from .testlib import statelib


MAC1 = "00:11:22:33:44:55"
MAC2 = "00:11:22:33:44:66"
MAC3 = "00:11:22:33:44:FF"
MAC_MIX_CASE = "00:11:22:33:44:Ff"

VF0_CONF = {
    Ethernet.SRIOV.VFS.ID: 0,
    Ethernet.SRIOV.VFS.SPOOF_CHECK: True,
    Ethernet.SRIOV.VFS.MAC_ADDRESS: MAC1,
    Ethernet.SRIOV.VFS.TRUST: False,
}

VF1_CONF = {
    Ethernet.SRIOV.VFS.ID: 1,
    Ethernet.SRIOV.VFS.SPOOF_CHECK: True,
    Ethernet.SRIOV.VFS.MAC_ADDRESS: MAC2,
    Ethernet.SRIOV.VFS.TRUST: False,
}


def _test_nic_name():
    return os.environ.get("TEST_REAL_NIC")


@pytest.fixture
def disable_sriov():
    pf_name = _test_nic_name()
    iface_info = {
        Interface.NAME: pf_name,
        Interface.STATE: InterfaceState.UP,
        Ethernet.CONFIG_SUBTREE: {
            Ethernet.SRIOV_SUBTREE: {
                Ethernet.SRIOV.TOTAL_VFS: 0,
                Ethernet.SRIOV.VFS_SUBTREE: [],
            }
        },
    }
    desired_state = {Interface.KEY: [iface_info]}
    libnmstate.apply(desired_state)
    yield
    libnmstate.apply(
        {
            Interface.KEY: [
                {
                    Interface.NAME: pf_name,
                    Interface.STATE: InterfaceState.ABSENT,
                }
            ]
        }
    )


@pytest.fixture
def sriov_interface(disable_sriov):
    pf_name = _test_nic_name()
    iface_info = {
        Interface.NAME: pf_name,
        Interface.STATE: InterfaceState.UP,
        Ethernet.CONFIG_SUBTREE: {
            Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2},
        },
    }
    desired_state = {Interface.KEY: [iface_info]}
    libnmstate.apply(desired_state)
    yield desired_state


@pytest.fixture
def sriov_iface_vf(disable_sriov):
    pf_name = _test_nic_name()
    desired_state = {
        Interface.KEY: [
            {
                Interface.NAME: pf_name,
                Ethernet.CONFIG_SUBTREE: {
                    Ethernet.SRIOV_SUBTREE: {
                        Ethernet.SRIOV.TOTAL_VFS: 2,
                        Ethernet.SRIOV.VFS_SUBTREE: [VF0_CONF, VF1_CONF],
                    }
                },
            }
        ]
    }
    libnmstate.apply(desired_state)
    yield desired_state


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_with_no_vfs_config(sriov_interface):
    assertlib.assert_state_match(sriov_interface)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_increase_vfs(sriov_interface):
    desired_state = sriov_interface
    eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
    eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 5
    libnmstate.apply(desired_state)
    assertlib.assert_state_match(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_decrease_vfs(sriov_interface):
    desired_state = sriov_interface
    eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
    eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 1
    eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = [VF0_CONF]
    libnmstate.apply(desired_state)
    assertlib.assert_state_match(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_create_vf_config(sriov_iface_vf):
    assertlib.assert_state_match(sriov_iface_vf)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_edit_vf_config(sriov_iface_vf):
    desired_state = sriov_iface_vf
    eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
    vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
    vf0[Ethernet.SRIOV.VFS.TRUST] = True
    vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC3
    libnmstate.apply(desired_state)
    assertlib.assert_state_match(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
@pytest.mark.xfail(
    raises=libnmstate.error.NmstateVerificationError,
    reason="https://github.com/nmstate/nmstate/issues/1454",
    strict=True,
)
def test_sriov_remove_vf_config(sriov_iface_vf):
    desired_state = sriov_iface_vf
    eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
    eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = []
    libnmstate.apply(desired_state)
    assertlib.assert_state_match(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_vf_mac_mixed_case(sriov_iface_vf):
    desired_state = sriov_iface_vf
    eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
    vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
    vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE
    libnmstate.apply(desired_state)

    vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE.upper()
    assertlib.assert_state_match(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_wait_sriov_vf_been_created():
    pf_name = _test_nic_name()
    desired_state = {
        Interface.KEY: [
            {
                Interface.NAME: pf_name,
                Ethernet.CONFIG_SUBTREE: {
                    Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2}
                },
            }
        ]
    }
    try:
        libnmstate.apply(desired_state)
        assertlib.assert_state_match(desired_state)
        current_state = statelib.show_only((f"{pf_name}v0", f"{pf_name}v1"))
        assert len(current_state[Interface.KEY]) == 2

    finally:
        desired_state[Interface.KEY][0][
            Interface.STATE
        ] = InterfaceState.ABSENT
        libnmstate.apply(desired_state)


@pytest.mark.skipif(
    not os.environ.get("TEST_REAL_NIC"),
    reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_show_saved_config_for_sriov_down(sriov_iface_vf):
    pf_name = _test_nic_name()
    running_state = statelib.show_only((pf_name,))
    libnmstate.apply(
        {
            Interface.KEY: [
                {
                    Interface.NAME: pf_name,
                    Interface.STATE: InterfaceState.DOWN,
                }
            ]
        }
    )
    saved_state = statelib.show_saved_config_only((pf_name,))
    iface_state = saved_state[Interface.KEY][0]

    assert saved_state[Interface.KEY][0][Interface.STATE] == InterfaceState.UP
    assert iface_state[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] == 2
    assertlib.assert_state_match_full(saved_state, running_state)
