#!/usr/bin/python3
# SPDX-FileCopyrightText: 2024-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""Unit test for univention.management.console.modules.setup.netconf.ChangeSet"""
import os
# pylint: disable-msg=C0103,E0611,R0904
import unittest
from ipaddress import IPv4Interface, IPv6Interface

import univention.management.console.modules


try:
    univention.management.console.modules.__path__.insert(0, os.path.join(os.path.dirname(__file__), os.path.pardir, 'umc/python'))
except AttributeError:
    pass
from univention.management.console.modules.setup.netconf import ChangeSet  # noqa: E402
from univention.management.console.modules.setup.netconf.common import AddressMap  # noqa: E402


class IPv4Network(IPv4Interface):
    def __init__(self, address, strict=True):
        IPv4Interface.__init__(self, address)


class IPv6Network(IPv6Interface):
    def __init__(self, address, strict=True):
        IPv6Interface.__init__(self, address)


class DummyOption:

    def __init__(self):
        self.no_act = True


class TestChangeSetEmpty(unittest.TestCase):
    """No old and no new addresses."""

    def setUp(self):
        ucr = {}
        profile = {}
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert set() == self.cs.old_names
        assert set() == self.cs.new_names
        assert [] == self.cs.old_ipv4s
        assert [] == self.cs.new_ipv4s
        assert [] == self.cs.old_ipv6s
        assert [] == self.cs.new_ipv6s

    def test_am(self):
        am = AddressMap(self.cs)
        assert {} == am.net_changes


class TestChangeSetRemoved(unittest.TestCase):
    """Old address removed."""

    def setUp(self):
        ucr = {
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "1.2.3.4",
            "interfaces/eth0/network": "1.2.0.0",
            "interfaces/eth0/netmask": "255.255.0.0",
            "interfaces/eth0/broadcast": "1.2.255.255",
            "interfaces/eth0/ipv6/default/address": "1111:2222::3333",
            "interfaces/eth0/ipv6/default/prefix": "64",
        }
        profile = {key: None for key in ucr.keys() if key.startswith("interfaces/")}
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert {"eth0"} == self.cs.old_names
        assert set() == self.cs.new_names
        assert [IPv4Network("1.2.3.4/16", False)] == self.cs.old_ipv4s
        assert [] == self.cs.new_ipv4s
        assert [IPv6Network("1111:2222::3333/64", False)] == self.cs.old_ipv6s
        assert [] == self.cs.new_ipv6s

    def test_am(self):
        am = AddressMap(self.cs)
        ipv4 = IPv4Network("1.2.3.4/16", False)
        ipv6 = IPv6Network("1111:2222::3333/64", False)
        assert {ipv4: None, ipv6: None} == am.net_changes


class TestChangeSetAdded(unittest.TestCase):
    """New address added."""

    def setUp(self):
        ucr = {}
        profile = {
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "1.2.3.4",
            "interfaces/eth0/network": "1.2.0.0",
            "interfaces/eth0/netmask": "255.255.0.0",
            "interfaces/eth0/broadcast": "1.2.255.255",
            "interfaces/eth0/ipv6/default/address": "1111:2222::3333",
            "interfaces/eth0/ipv6/default/prefix": "64",
        }
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert set() == self.cs.old_names
        assert {"eth0"} == self.cs.new_names
        assert [] == self.cs.old_ipv4s
        assert [IPv4Network("1.2.3.4/16", False)] == self.cs.new_ipv4s
        assert [] == self.cs.old_ipv6s
        assert [IPv6Network("1111:2222::3333/64", False)] == self.cs.new_ipv6s

    def test_am(self):
        am = AddressMap(self.cs)
        assert {} == am.net_changes


class TestChangeSetChanged(unittest.TestCase):
    """Old address changed to new address."""

    def setUp(self):
        ucr = {
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "1.2.3.4",
            "interfaces/eth0/network": "1.2.0.0",
            "interfaces/eth0/netmask": "255.255.0.0",
            "interfaces/eth0/broadcast": "1.2.255.255",
            "interfaces/eth0/ipv6/default/address": "1111:2222::3333",
            "interfaces/eth0/ipv6/default/prefix": "64",
        }
        profile = {key: None for key in ucr.keys() if key.startswith("interfaces/")}
        profile.update({
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "2.3.4.5",
            "interfaces/eth0/network": "2.3.0.0",
            "interfaces/eth0/netmask": "255.255.255.0",
            "interfaces/eth0/broadcast": "2.3.4.255",
            "interfaces/eth0/ipv6/default/address": "2222:3333:4444::5555",
            "interfaces/eth0/ipv6/default/prefix": "80",
        })
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert {"eth0"} == self.cs.old_names
        assert {"eth0"} == self.cs.new_names
        assert [IPv4Network("1.2.3.4/16", False)] == self.cs.old_ipv4s
        assert [IPv4Network("2.3.4.5/24", False)] == self.cs.new_ipv4s
        assert [IPv6Network("1111:2222::3333/64", False)] == self.cs.old_ipv6s
        assert [IPv6Network("2222:3333:4444::5555/80", False)] == self.cs.new_ipv6s

    def test_am(self):
        am = AddressMap(self.cs)
        ipv4_old = IPv4Network("1.2.3.4/16", False)
        ipv4_new = IPv4Network("2.3.4.5/24", False)
        ipv6_old = IPv6Network("1111:2222::3333/64", False)
        ipv6_new = IPv6Network("2222:3333:4444::5555/80", False)
        assert {ipv4_old: ipv4_new, ipv6_old: ipv6_new} == am.net_changes


class TestChangeSetMoved(unittest.TestCase):
    """Old address moved to new interface."""

    def setUp(self):
        ucr = {
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "1.2.3.4",
            "interfaces/eth0/network": "1.2.0.0",
            "interfaces/eth0/netmask": "255.255.0.0",
            "interfaces/eth0/broadcast": "1.2.255.255",
            "interfaces/eth0/ipv6/default/address": "1111:2222::3333",
            "interfaces/eth0/ipv6/default/prefix": "64",
        }
        profile = {key: None for key in ucr.keys() if key.startswith("interfaces/")}
        profile.update({
            "interfaces/br0/type": "static",
            "interfaces/br0/start": "true",
            "interfaces/br0/address": "1.2.3.4",
            "interfaces/br0/network": "1.2.0.0",
            "interfaces/br0/netmask": "255.255.0.0",
            "interfaces/br0/broadcast": "1.2.255.255",
            "interfaces/br0/ipv6/default/address": "1111:2222::3333",
            "interfaces/br0/ipv6/default/prefix": "64",
        })
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert {"eth0"} == self.cs.old_names
        assert {"br0"} == self.cs.new_names
        assert [IPv4Network("1.2.3.4/16", False)] == self.cs.old_ipv4s
        assert [IPv4Network("1.2.3.4/16", False)] == self.cs.new_ipv4s
        assert [IPv6Network("1111:2222::3333/64", False)] == self.cs.old_ipv6s
        assert [IPv6Network("1111:2222::3333/64", False)] == self.cs.new_ipv6s

    def test_am(self):
        am = AddressMap(self.cs)
        assert {} == am.net_changes


class TestChangeSetMultipleSameSubnet(unittest.TestCase):
    """Test multiple addresses in the same subnet."""

    def setUp(self):
        ucr = {}
        profile = {
            "interfaces/eth0/type": "static",
            "interfaces/eth0/start": "true",
            "interfaces/eth0/address": "1.2.3.4",
            "interfaces/eth0/network": "1.2.0.0",
            "interfaces/eth0/netmask": "255.255.0.0",
            "interfaces/eth0/broadcast": "1.2.255.255",
            "interfaces/eth0/ipv6/default/address": "1111:2222::3333",
            "interfaces/eth0/ipv6/default/prefix": "64",
            "interfaces/eth1/type": "static",
            "interfaces/eth1/start": "true",
            "interfaces/eth1/address": "1.2.3.5",
            "interfaces/eth1/network": "1.2.0.0",
            "interfaces/eth1/netmask": "255.255.0.0",
            "interfaces/eth1/broadcast": "1.2.255.255",
            "interfaces/eth1/ipv6/default/address": "1111:2222::4444",
            "interfaces/eth1/ipv6/default/prefix": "64",
        }
        options = DummyOption()
        self.cs = ChangeSet(ucr, profile, options)

    def test_cs(self):
        assert set() == self.cs.old_names
        assert {"eth0", "eth1"} == self.cs.new_names
        assert [] == self.cs.old_ipv4s
        assert [IPv4Network("1.2.3.4/16", False), IPv4Network("1.2.3.5/16", False)] == sorted(self.cs.new_ipv4s)
        assert [] == self.cs.old_ipv6s
        assert [IPv6Network("1111:2222::3333/64", False), IPv6Network("1111:2222::4444/64", False)] == sorted(self.cs.new_ipv6s)

    def test_am(self):
        am = AddressMap(self.cs)
        assert {} == am.net_changes


if __name__ == '__main__':
    unittest.main()
