"""
Helpers for IPv6 specific functionality
"""
import asyncio
import logging

from packaging import version

from preseem import NetworkMetadataReference, get_node_versions


class NetworkMetadataIpv6Settings:
    """
    IPv6 Settings Management.
    This class is responsible for determining whether IPv6 functionality
    (and also IPv4 subnet mapping functionality) is enabled.
    The logic for this is as follows.
      1. A setting for ipv6_enabled may be set, if this is set it is used.
      2. The network metadata model is checked for saved state.  If the
         saved state is set to enabled, we use it; once we switch to ipv6
         support, we never go back unless the config of (1) is set false.
         If the saved state is unset, we set it to ipv4.  We never return
         any result if we were unable to read from the metadata model; that
         is required for any result to be returned (else we raise an error).
      3. If the network metadata model was set to ipv4, we check the metrics
         to see if all nodes are running a version supporting ipv6.  If there
         are nodes and they all support ipv6, we enable ipv6 support.  This
         metrics polling is done periodically while we are still in ipv4 mode.
    """
    _version_getter = get_node_versions
    _lock = asyncio.Lock()
    _IPV6_VERSION = version.parse('1.11.1')
    _POLL_INTERVAL = 3600

    def __init__(self, model, telemetry_api, ipv6_enabled=None):
        self.model = model
        self.telemetry_api = telemetry_api
        if ipv6_enabled is not None:
            # we want checking this to be an error until it's explicitly set
            self.ipv6_enabled = ipv6_enabled
        self._ref = None
        self._task = None

    async def _get_saved_state(self, read_only=False):
        """Return state from metadata.  If not read_only, initialize it."""
        await self.model.update()
        refs = self.model.refs.get('preseem_state') or {}
        ref = refs.get('ipv6_settings')
        if ref:
            self._ref = ref
        elif not read_only:
            ref = NetworkMetadataReference('preseem_state', 'ipv6_settings',
                                           {'ipv6_enabled': False})
            await self.model.set_ref(ref)
            await asyncio.sleep(0.1)  # let the event update model.refs
            await self._get_saved_state(read_only=True)

    async def _poll(self):
        """Poll metrics for node versions"""
        while True:
            try:
                node_versions = await NetworkMetadataIpv6Settings._version_getter(
                    self.telemetry_api)
                if node_versions and min(node_versions.values()) >= self._IPV6_VERSION:
                    logging.info('IPv6 Capability Detected: %s', node_versions)
                    self._ref.attributes['ipv6_enabled'] = True
                    await self.model.set_ref(self._ref)
                    self.ipv6_enabled = True
                    break
            except asyncio.CancelledError:
                break
            except Exception as err:
                logging.info('%s error checking IPv6 capability: %s',
                             type(err).__name__, err)
            try:
                await asyncio.sleep(self._POLL_INTERVAL)
            except asyncio.CancelledError:
                break
        self._task = None

    async def close(self):
        """Release any resources used by this object."""
        if self._task is not None:
            self._task.cancel()
            await self._task
            self._task = None

    async def init(self):
        """Initialize the object."""
        async with self._lock:
            try:
                return self.ipv6_enabled
            except AttributeError:
                pass  # we need to initialize it
            ipv6_enabled = None
            await self._get_saved_state()
            assert self._ref
            ipv6_enabled = self._ref.attributes.get('ipv6_enabled')
            assert ipv6_enabled is not None
            if not ipv6_enabled and self._task is None:
                self._task = asyncio.get_event_loop().create_task(self._poll())
            self.ipv6_enabled = ipv6_enabled
