# -*- coding: utf-8 -*-
'''
Manage LXD images.

.. versionadded:: 2019.2.0

.. note:

    - `pylxd`_ version 2 is required to let this work,
      currently only available via pip.

        To install on Ubuntu:

        $ apt-get install libssl-dev python-pip
        $ pip install -U pylxd

    - you need lxd installed on the minion
      for the init() and version() methods.

    - for the config_get() and config_get() methods
      you need to have lxd-client installed.

.. _: https://github.com/lxc/pylxd/blob/master/doc/source/installation.rst

:maintainer: René Jochum <rene@jochums.at>
:maturity: new
:depends: python-pylxd
:platform: Linux
'''

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals

# Import salt libs
from salt.exceptions import CommandExecutionError
from salt.exceptions import SaltInvocationError
import salt.ext.six as six
from salt.ext.six.moves import map

__docformat__ = 'restructuredtext en'

__virtualname__ = 'lxd_image'


def __virtual__():
    '''
    Only load if the lxd module is available in __salt__
    '''
    return __virtualname__ if 'lxd.version' in __salt__ else False


def present(name,
            source,
            aliases=None,
            public=None,
            auto_update=None,
            remote_addr=None,
            cert=None,
            key=None,
            verify_cert=True):
    '''
    Ensure an image exists, copy it else from source

    name :
        An alias of the image, this is used to check if the image exists and
        it will be added as alias to the image on copy/create.

    source :
        Source dict.

        For an LXD to LXD copy:

        .. code-block:: yaml

            source:
                type: lxd
                name: ubuntu/xenial/amd64  # This can also be a fingerprint.
                remote_addr: https://images.linuxcontainers.org:8443
                cert: ~/.config/lxd/client.crt
                key: ~/.config/lxd/client.key
                verify_cert: False

        .. attention:

            For this kind of remote you also need to provide:
            - a https:// remote_addr
            - a cert and key
            - verify_cert

        From file:

        .. code-block:: yaml

            source:
                type: file
                filename: salt://lxd/files/busybox.tar.xz
                saltenv: base

        From simplestreams:

        .. code-block:: yaml

            source:
                type: simplestreams
                server: https://cloud-images.ubuntu.com/releases
                name: xenial/amd64

        From an URL:

        .. code-block:: yaml

            source:
                type: url
                url: https://dl.stgraber.org/lxd

    aliases :
        List of aliases to append, can be empty.

    public :
        Make this image public available on this instance?
            None on source_type LXD means copy source
            None on source_type file means False

    auto_update :
        Try to auto-update from the original source?
            None on source_type LXD means copy source
            source_type file does not have auto-update.

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if you
        provide remote_addr!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Zertifikate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normaly uses self-signed certificates.
    '''
    if aliases is None:
        aliases = []

    # Create a copy of aliases, since we're modifying it here
    aliases = aliases[:]
    ret = {
        'name': name,
        'source': source,
        'aliases': aliases,
        'public': public,
        'auto_update': auto_update,

        'remote_addr': remote_addr,
        'cert': cert,
        'key': key,
        'verify_cert': verify_cert,

        'changes': {}
    }

    image = None
    try:
        image = __salt__['lxd.image_get_by_alias'](
            name, remote_addr, cert, key, verify_cert, _raw=True
        )
    except CommandExecutionError as e:
        return _error(ret, six.text_type(e))
    except SaltInvocationError as e:
        # Image not found
        pass

    if image is None:
        if __opts__['test']:
            # Test is on, just return that we would create the image
            msg = 'Would create the image "{0}"'.format(name)
            ret['changes'] = {'created': msg}
            return _unchanged(ret, msg)

        try:
            if source['type'] == 'lxd':
                image = __salt__['lxd.image_copy_lxd'](
                    source['name'],
                    src_remote_addr=source['remote_addr'],
                    src_cert=source['cert'],
                    src_key=source['key'],
                    src_verify_cert=source.get('verify_cert', True),
                    remote_addr=remote_addr,
                    cert=cert,
                    key=key,
                    verify_cert=verify_cert,
                    aliases=aliases,
                    public=public,
                    auto_update=auto_update,
                    _raw=True
                )

            if source['type'] == 'file':
                if 'saltenv' not in source:
                    source['saltenv'] = __env__
                image = __salt__['lxd.image_from_file'](
                    source['filename'],
                    remote_addr=remote_addr,
                    cert=cert,
                    key=key,
                    verify_cert=verify_cert,
                    aliases=aliases,
                    public=False if public is None else public,
                    saltenv=source['saltenv'],
                    _raw=True
                )

            if source['type'] == 'simplestreams':
                image = __salt__['lxd.image_from_simplestreams'](
                    source['server'],
                    source['name'],
                    remote_addr=remote_addr,
                    cert=cert,
                    key=key,
                    verify_cert=verify_cert,
                    aliases=aliases,
                    public=False if public is None else public,
                    auto_update=False if auto_update is None else auto_update,
                    _raw=True
                )

            if source['type'] == 'url':
                image = __salt__['lxd.image_from_url'](
                    source['url'],
                    remote_addr=remote_addr,
                    cert=cert,
                    key=key,
                    verify_cert=verify_cert,
                    aliases=aliases,
                    public=False if public is None else public,
                    auto_update=False if auto_update is None else auto_update,
                    _raw=True
                )
        except CommandExecutionError as e:
            return _error(ret, six.text_type(e))

    # Sync aliases
    if name not in aliases:
        aliases.append(name)

    old_aliases = set([six.text_type(a['name']) for a in image.aliases])
    new_aliases = set(map(six.text_type, aliases))

    alias_changes = []
    # Removed aliases
    for k in old_aliases.difference(new_aliases):
        if not __opts__['test']:
            __salt__['lxd.image_alias_delete'](image, k)
            alias_changes.append('Removed alias "{0}"'.format(k))
        else:
            alias_changes.append('Would remove alias "{0}"'.format(k))

    # New aliases
    for k in new_aliases.difference(old_aliases):
        if not __opts__['test']:
            __salt__['lxd.image_alias_add'](image, k, '')
            alias_changes.append('Added alias "{0}"'.format(k))
        else:
            alias_changes.append('Would add alias "{0}"'.format(k))

    if alias_changes:
        ret['changes']['aliases'] = alias_changes

    # Set public
    if public is not None and image.public != public:
        if not __opts__['test']:
            ret['changes']['public'] = \
                'Setting the image public to {0!s}'.format(public)
            image.public = public
            __salt__['lxd.pylxd_save_object'](image)
        else:
            ret['changes']['public'] = \
                'Would set public to {0!s}'.format(public)

    if __opts__['test'] and ret['changes']:
        return _unchanged(
            ret,
            'Would do {0} changes'.format(len(ret['changes'].keys()))
        )

    return _success(ret, '{0} changes'.format(len(ret['changes'].keys())))


def absent(name,
           remote_addr=None,
           cert=None,
           key=None,
           verify_cert=True):
    '''
    name :
        An alias or fingerprint of the image to check and delete.

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if you
        provide remote_addr!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Zertifikate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normaly uses self-signed certificates.
    '''
    ret = {
        'name': name,

        'remote_addr': remote_addr,
        'cert': cert,
        'key': key,
        'verify_cert': verify_cert,

        'changes': {}
    }
    image = None
    try:
        image = __salt__['lxd.image_get_by_alias'](
            name, remote_addr, cert, key, verify_cert, _raw=True
        )
    except CommandExecutionError as e:
        return _error(ret, six.text_type(e))
    except SaltInvocationError as e:
        try:
            image = __salt__['lxd.image_get'](
                name, remote_addr, cert, key, verify_cert, _raw=True
            )
        except CommandExecutionError as e:
            return _error(ret, six.text_type(e))
        except SaltInvocationError as e:
            return _success(ret, 'Image "{0}" not found.'.format(name))

    if __opts__['test']:
        ret['changes'] = {
            'removed':
            'Image "{0}" would get deleted.'.format(name)
        }
        return _success(ret, ret['changes']['removed'])

    __salt__['lxd.image_delete'](
        image
    )

    ret['changes'] = {
        'removed':
        'Image "{0}" has been deleted.'.format(name)
    }
    return _success(ret, ret['changes']['removed'])


def _success(ret, success_msg):
    ret['result'] = True
    ret['comment'] = success_msg
    if 'changes' not in ret:
        ret['changes'] = {}
    return ret


def _unchanged(ret, msg):
    ret['result'] = None
    ret['comment'] = msg
    if 'changes' not in ret:
        ret['changes'] = {}
    return ret


def _error(ret, err_msg):
    ret['result'] = False
    ret['comment'] = err_msg
    if 'changes' not in ret:
        ret['changes'] = {}
    return ret
