"""Test Network Manager Connection object."""

from unittest.mock import MagicMock, PropertyMock

from awesomeversion import AwesomeVersion
from dbus_fast import Variant
from dbus_fast.aio.message_bus import MessageBus
import pytest

from supervisor.dbus.network import NetworkManager
from supervisor.dbus.network.interface import NetworkInterface
from supervisor.dbus.network.setting import NetworkSetting
from supervisor.dbus.network.setting.generate import get_connection_from_interface
from supervisor.host.configuration import Ip6Setting
from supervisor.host.const import InterfaceMethod
from supervisor.host.network import Interface

from tests.dbus_service_mocks.network_connection_settings import (
    ConnectionSettings as ConnectionSettingsService,
)
from tests.dbus_service_mocks.network_device import (
    ETHERNET_DEVICE_OBJECT_PATH,
    WIRELESS_DEVICE_OBJECT_PATH,
)

pytestmark = pytest.mark.usefixtures("connection_settings_service")


@pytest.fixture(name="dbus_interface")
async def fixture_dbus_interface(
    dbus_session_bus: MessageBus, device_object_path: str = ETHERNET_DEVICE_OBJECT_PATH
) -> NetworkInterface:
    """Get connected dbus interface."""
    dbus_interface = NetworkInterface(device_object_path)
    await dbus_interface.connect(dbus_session_bus)
    yield dbus_interface


@pytest.mark.parametrize(
    "dbus_interface",
    [ETHERNET_DEVICE_OBJECT_PATH, WIRELESS_DEVICE_OBJECT_PATH],
    indirect=True,
)
async def test_ethernet_update(
    dbus_interface: NetworkInterface,
    connection_settings_service: ConnectionSettingsService,
    network_manager: NetworkManager,
):
    """Test network manager update."""
    connection_settings_service.Update.calls.clear()

    interface = Interface.from_dbus_interface(dbus_interface)
    conn = get_connection_from_interface(
        interface,
        network_manager,
        name=dbus_interface.settings.connection.id,
        uuid=dbus_interface.settings.connection.uuid,
    )

    await dbus_interface.settings.update(conn)

    assert len(connection_settings_service.Update.calls) == 1
    settings = connection_settings_service.Update.calls[0][0]

    assert settings["connection"]["id"] == Variant("s", "Supervisor eth0")
    assert "interface-name" not in settings["connection"]
    assert settings["connection"]["uuid"] == Variant(
        "s", "0c23631e-2118-355c-bbb0-8943229cb0d6"
    )
    assert settings["connection"]["autoconnect"] == Variant("b", True)

    assert settings["match"] == {"path": Variant("as", ["platform-ff3f0000.ethernet"])}

    assert "ipv4" in settings
    assert settings["ipv4"]["method"] == Variant("s", "auto")
    assert "gateway" not in settings["ipv4"]
    # Only DNS settings need to be preserved with auto
    assert settings["ipv4"]["dns"] == Variant("au", [16951488])
    assert "dns-data" not in settings["ipv4"]
    assert "address-data" not in settings["ipv4"]
    assert "addresses" not in settings["ipv4"]
    assert len(settings["ipv4"]["route-data"].value) == 1
    assert settings["ipv4"]["route-data"].value[0]["dest"] == Variant(
        "s", "192.168.122.0"
    )
    assert settings["ipv4"]["route-data"].value[0]["prefix"] == Variant("u", 24)
    assert settings["ipv4"]["route-data"].value[0]["next-hop"] == Variant(
        "s", "10.10.10.1"
    )
    assert settings["ipv4"]["routes"] == Variant("aau", [[8038592, 24, 17435146, 0]])

    assert "ipv6" in settings
    assert settings["ipv6"]["method"] == Variant("s", "auto")
    assert "gateway" not in settings["ipv6"]
    # Only DNS settings need to be preserved with auto
    assert settings["ipv6"]["dns"] == Variant(
        "aay", [bytearray(b" \x01H`H`\x00\x00\x00\x00\x00\x00\x00\x00\x88\x88")]
    )
    assert "dns-data" not in settings["ipv6"]
    assert "address-data" not in settings["ipv6"]
    assert "addresses" not in settings["ipv6"]
    assert settings["ipv6"]["addr-gen-mode"] == Variant("i", 0)

    assert "proxy" in settings

    assert "vlan" not in settings

    if settings["connection"]["type"] == "802-3-ethernet":
        assert "802-3-ethernet" in settings
        assert settings["802-3-ethernet"]["auto-negotiate"] == Variant("b", False)

        assert "802-11-wireless" not in settings
        assert "802-11-wireless-security" not in settings

    if settings["connection"]["type"] == "802-11-wireless":
        assert "802-11-wireless" in settings
        assert settings["802-11-wireless"]["ssid"] == Variant("ay", b"NETT")
        assert "mode" not in settings["802-11-wireless"]
        assert "powersave" not in settings["802-11-wireless"]

        assert "802-11-wireless-security" not in settings


async def test_ipv6_disabled_is_link_local(
    dbus_interface: NetworkInterface, network_manager: NetworkManager
):
    """Test disabled equals link local for ipv6."""
    interface = Interface.from_dbus_interface(dbus_interface)
    interface.ipv4setting.method = InterfaceMethod.DISABLED
    interface.ipv6setting.method = InterfaceMethod.DISABLED
    conn = get_connection_from_interface(
        interface,
        network_manager,
        name=dbus_interface.settings.connection.id,
        uuid=dbus_interface.settings.connection.uuid,
    )

    assert conn["ipv4"]["method"] == Variant("s", "disabled")
    assert conn["ipv6"]["method"] == Variant("s", "link-local")


@pytest.mark.parametrize(
    ["version", "addr_gen_mode"],
    [
        ("1.38.0", 1),
        ("1.40.0", 3),
    ],
)
async def test_ipv6_addr_gen_mode(
    dbus_interface: NetworkInterface, version: str, addr_gen_mode: int
):
    """Test addr_gen_mode with various NetworkManager versions."""
    interface = Interface.from_dbus_interface(dbus_interface)
    interface.ipv6setting = Ip6Setting(InterfaceMethod.AUTO, [], None, None, [])

    network_manager = MagicMock()
    type(network_manager).version = PropertyMock(return_value=AwesomeVersion(version))
    conn = get_connection_from_interface(
        interface,
        network_manager,
        name=dbus_interface.settings.connection.id,
        uuid=dbus_interface.settings.connection.uuid,
    )

    assert conn["ipv6"]["method"] == Variant("s", "auto")
    assert conn["ipv6"]["addr-gen-mode"] == Variant("i", addr_gen_mode)


async def test_watching_updated_signal(
    connection_settings_service: ConnectionSettingsService, dbus_session_bus: MessageBus
):
    """Test get settings called on update signal."""
    connection_settings_service.GetSettings.calls.clear()
    settings = NetworkSetting("/org/freedesktop/NetworkManager/Settings/1")
    await settings.connect(dbus_session_bus)

    assert connection_settings_service.GetSettings.calls == [()]

    connection_settings_service.Updated()
    await connection_settings_service.ping()
    await connection_settings_service.ping()
    assert connection_settings_service.GetSettings.calls == [(), ()]
