"""
smartaws.ec2.network_interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module manages Amazon ENI objects and related resources.

:copyright: (c) 2016 by Yann Lambret.
:license: MIT, see LICENSE for more details.
"""
from boto3 import Session
from botocore.exceptions import ClientError
from botocore.exceptions import ParamValidationError

from smartaws import log
from smartaws.ec2 import base
from smartaws.utils import camel_case


logger = log.get_logger(__name__)


class NetworkInterfaceWrapper(base.BaseWrapper):
    """
    Wrapper class for :class:`boto3.resources.factory.ec2.NetworkInterface`
    objects.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    @property
    def tags(self):
        return self.tag_set


def _add_wrapper(base_classes, **kwargs):
    """
    Register the :class:`NetworkInterfaceWrapper` class.

    See `Boto 3 Extensibility Guide
    <http://boto3.readthedocs.org/en/latest/guide/events.html>`_
    """
    base_classes.insert(0, NetworkInterfaceWrapper)


class NetworkInterfaceHandler(base.BaseHandler):
    """Manage a :class:`NetworkInterfaceWrapper` object's lifecycle."""

    def __init__(self, ctx, credentials):
        """Create boto3 session and clients."""
        super().__init__()

        # Create a boto session and add extra
        # features to the base class EC2.NetworkInterface
        event = 'creating-resource-class.ec2.NetworkInterface'
        self._session = Session(**credentials)
        self._session.events.register(event, _add_wrapper)

        # boto3 clients
        self._ec2 = self._session.resource('ec2')
        self._client = self._session.client('ec2')

        # Context for the resource we want
        # to create, update or delete
        self._ctx = ctx
        self._name = ctx.get('name')

    def create_resource(self):
        """Create a network interface, or update it if it already exists."""
        try:
            self._resource = self._load(self._name, manager_name='network_interfaces')
        except ValueError as err:
            logger.critical(err)
            return

        if self._resource:
            logger.info('Network interface %s already exists.', self._name)
        else:
            self._create_network_interface()

        # Store the object in the cache for future use
        with self._lock:
            self._cache[self._name] = self._resource

    def update_resource(self):
        """Update a network interface object."""
        try:
            self._resource = self._load(self._name, manager_name='network_interfaces')
        except ValueError as err:
            logger.critical(err)
            return

        if self._resource:
            self._update_network_interface()

            # Store the object in the cache for future use
            with self._lock:
                self._cache[self._name] = self._resource
        else:
            logger.error('Network interface %s does not exist.', self._name)

    def delete_resource(self):
        """Delete a network interface object."""
        try:
            self._resource = self._load(self._name, manager_name='network_interfaces')
        except ValueError as err:
            logger.critical(err)
            return

        if self._resource:
            self._delete_network_interface()
        else:
            logger.error('Network interface %s does not exist.', self._name)

    def _associate_eip(self):
        """Associate an EIP with the managed network interface."""
        try:
            response = self._client.allocate_address(Domain='vpc')
        except ClientError as err:
            logger.error('Unable to allocate EIP: %s', err)
            return

        public_ip = response['PublicIp']
        allocation_id = response['AllocationId']

        params = {
            'NetworkInterfaceId': self._resource.id,
            'AllocationId': allocation_id
        }

        self._resource.reload()
        params['PrivateIpAddress'] = self._resource.private_ip_address

        try:
            self._client.associate_address(**params)
        except (ClientError, ParamValidationError) as err:
            logger.error(
                'Unable to associate EIP %s with ENI %s: %s',