import asyncio
import unittest

from packaging import version

from preseem import FakeNetworkMetadataModel, NetworkMetadataIpv6Settings, set_ipv6_settings, parse_ip_value

class TestIpv6(unittest.TestCase):
    """tests for IPv6 Settings class."""
    def setUp(self):
        self.loop = asyncio.new_event_loop() # needed to initialize asyncio
        self.model = FakeNetworkMetadataModel(refs_by_type=True)
        asyncio.set_event_loop(self.loop)
        NetworkMetadataIpv6Settings._version_getter = self.fake_get_node_versions
        self.settings = None
        self.version_polls = 0
        self.node_versions = {}

    def tearDown(self):
        set_ipv6_settings(None)
        self._close()
        self.model.close()
        self.loop.close()

    async def fake_get_node_versions(self, telemetry_api):
        self.version_polls += 1
        return self.node_versions

    def _init(self, obj):
        self.settings = obj
        return self.loop.run_until_complete(obj.init())

    def _close(self):
        if self.settings:
            self.loop.run_until_complete(self.settings.close())

    def _sleep(self, sec):
        return self.loop.run_until_complete(asyncio.sleep(sec))

    def check_ipv6_enabled_state(self):
        """Return the value of the enabled reference state"""
        state_refs = self.model.refs.get('preseem_state') or {}
        ref = state_refs.get('ipv6_settings')
        return ref.attributes.get('ipv6_enabled') if ref else None

    def test_config_1(self):
        """Test configuration enabling ipv6"""
        settings = NetworkMetadataIpv6Settings(self.model, None, True)
        self._init(settings)
        self.assertEqual(settings.ipv6_enabled, True)
        self.assertEqual(self.version_polls, 0)

    def test_config_2(self):
        """Test configuration disabling ipv6"""
        settings = NetworkMetadataIpv6Settings(self.model, None, False)
        self._init(settings)
        self.assertEqual(settings.ipv6_enabled, False)

    def test_init_1(self):
        """The default initial state is ipv6 disabled."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self.assertEqual(self.check_ipv6_enabled_state(), None)
        self._init(settings)
        self.assertEqual(settings.ipv6_enabled, False)
        self.assertEqual(self.check_ipv6_enabled_state(), False)

    def test_init_2(self):
        """Once the reference is saved, it is used, no polling required."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self.node_versions = {'-node1': version.parse('1.11.1')}
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 1)
        self.assertEqual(settings.ipv6_enabled, True)
        self.assertEqual(self.check_ipv6_enabled_state(), True)
        self._close()
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 1)

    def test_init_3(self):
        """Polling is done if reference is ipv6 disabled."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self.node_versions = {'-node1': version.parse('0.11.1')}
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 1)
        self.assertEqual(settings.ipv6_enabled, False)
        self.assertEqual(self.check_ipv6_enabled_state(), False)
        self._close()
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 2)
        self.assertEqual(settings.ipv6_enabled, False)

    def test_poll_1(self):
        """Node versions of 1.11.1 or higher result in ipv6 enabled."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self.node_versions = {'-node1': version.parse('1.11.1')}
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 1)
        self.assertEqual(settings.ipv6_enabled, True)
        self.assertEqual(self.check_ipv6_enabled_state(), True)

    def test_poll_2(self):
        """Polling happens if ipv6 disabled, and stops when enabled."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        settings._POLL_INTERVAL = 1
        self.node_versions = {'-node1': version.parse('1.10.9')}
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(self.version_polls, 1)
        self._sleep(1)
        self.assertEqual(self.version_polls, 2)
        self.assertEqual(settings.ipv6_enabled, False)
        self.node_versions = {'-node1': version.parse('1.12.0')}
        self._sleep(1)
        self.assertEqual(self.version_polls, 3)
        self.assertEqual(settings.ipv6_enabled, True)
        self._sleep(1)
        self.assertEqual(self.version_polls, 3)
        self.assertEqual(self.check_ipv6_enabled_state(), True)

    def test_poll_3(self):
        """If there is a mix of versions, minimum version is used."""
        settings = NetworkMetadataIpv6Settings(self.model, None)
        self.node_versions = {'-node1': version.parse('1.11.1'), '-node2': version.parse('1.11.0')}
        self._init(settings)
        self._sleep(0.1)
        self.assertEqual(settings.ipv6_enabled, False)

    def test_parse_ipv4_1(self):
        """An IPv4 subnet is parsed as a subnet if ipv6 is enabled."""
        self.assertEqual(set(parse_ip_value('192.168.0.0/30')), set(['192.168.0.0', '192.168.0.1', '192.168.0.2']))
        set_ipv6_settings(NetworkMetadataIpv6Settings(self.model, None, ipv6_enabled=True))
        self.assertEqual(parse_ip_value('192.168.0.0/30'), ['192.168.0.0/30'])


    def test_parse_ipv4_2(self):
        """An IPv4 range is parsed as a subnet if ipv6 is enabled."""
        self.assertEqual(parse_ip_value('192.168.0.0-3'), ['192.168.0.0', '192.168.0.1', '192.168.0.2', '192.168.0.3'])
        set_ipv6_settings(NetworkMetadataIpv6Settings(self.model, None, ipv6_enabled=True))
        self.assertEqual(parse_ip_value('192.168.0.0-3'), ['192.168.0.0/30'])

    def test_parse_ipv6_1(self):
        """An IPv6 subnet is supported whether or not ipv6 is enabled."""
        self.assertEqual(parse_ip_value('2620:149:a43:301::/64'), ['2620:149:a43:301::/64'])
        set_ipv6_settings(NetworkMetadataIpv6Settings(self.model, None, ipv6_enabled=True))
        self.assertEqual(parse_ip_value('2620:149:a43:301::/64'), ['2620:149:a43:301::/64'])
        # quick normalization check
        self.assertEqual(parse_ip_value('2620:0149:a43:301::/64'), ['2620:149:a43:301::/64'])
