# -*- coding: utf-8 -*-
'''
Module for running imgadm command on SmartOS
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import Python libs
import logging

# Import Salt libs
import salt.utils.json
import salt.utils.path
import salt.utils.platform

log = logging.getLogger(__name__)

# Function aliases
__func_alias__ = {
    'list_installed': 'list',
    'update_installed': 'update',
    'import_image': 'import'
}

# Define the module's virtual name
__virtualname__ = 'imgadm'


def __virtual__():
    '''
    Provides imgadm only on SmartOS
    '''
    if salt.utils.platform.is_smartos_globalzone() and \
            salt.utils.path.which('imgadm'):
        return __virtualname__
    return (
        False,
        '{0} module can only be loaded on SmartOS compute nodes'.format(
            __virtualname__
        )
    )


def _exit_status(retcode, stderr=None):
    '''
    Translate exit status of imgadm
    '''
    ret = {0: 'Successful completion.',
           1: 'An error occurred.' if not stderr else stderr,
           2: 'Usage error.',
           3: 'Image not installed.'}[retcode]
    return ret


def _parse_image_meta(image=None, detail=False):
    ret = None

    if image and 'Error' in image:
        ret = image
    elif image:
        name = image['manifest']['name']
        version = image['manifest']['version']
        os = image['manifest']['os']
        description = image['manifest']['description']
        published = image['manifest']['published_at']
        source = image['source']
        if image['manifest']['name'] == 'docker-layer':
            # NOTE: skip docker-layer unless it has a docker:repo and docker:tag
            name = None
            docker_repo = None
            docker_tag = None
            for tag in image['manifest']['tags']:
                if tag.startswith('docker:tag:') and image['manifest']['tags'][tag]:
                    docker_tag = tag.split(':')[-1]
                elif tag == 'docker:repo':
                    docker_repo = image['manifest']['tags'][tag]

            if docker_repo and docker_tag:
                name = '{}:{}'.format(docker_repo, docker_tag)
                description = 'Docker image imported from {repo}:{tag} on {date}.'.format(
                    repo=docker_repo,
                    tag=docker_tag,
                    date=published,
                )

        if name and detail:
            ret = {
              'name': name,
              'version': version,
              'os': os,
              'description': description,
              'published': published,
              'source': source,
            }
        elif name:
            ret = '{name}@{version} [{published}]'.format(
                name=name,
                version=version,
                published=published,
            )

    return ret


def _split_docker_uuid(uuid):
    '''
    Split a smartos docker uuid into repo and tag
    '''
    if uuid:
        uuid = uuid.split(':')
        if len(uuid) == 2:
            tag = uuid[1]
            repo = uuid[0]
            if len(repo.split('/')) == 2:
                return repo, tag
    return None, None


def _is_uuid(uuid):
    '''
    Check if uuid is a valid smartos uuid

    Example: e69a0918-055d-11e5-8912-e3ceb6df4cf8
    '''
    if uuid and list((len(x) for x in uuid.split('-'))) == [8, 4, 4, 4, 12]:
        return True
    return False


def _is_docker_uuid(uuid):
    '''
    Check if uuid is a valid smartos docker uuid

    Example plexinc/pms-docker:plexpass
    '''
    repo, tag = _split_docker_uuid(uuid)
    return not (not repo and not tag)


def version():
    '''
    Return imgadm version

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.version
    '''
    ret = {}
    cmd = 'imgadm --version'
    res = __salt__['cmd.run'](cmd).splitlines()
    ret = res[0].split()
    return ret[-1]


def docker_to_uuid(uuid):
    '''
    Get the image uuid from an imported docker image

    .. versionadded:: 2019.2.0
    '''
    if _is_uuid(uuid):
        return uuid
    if _is_docker_uuid(uuid):
        images = list_installed(verbose=True)
        for image_uuid in images:
            if images[image_uuid]['name'] == uuid:
                return image_uuid
    return None


def update_installed(uuid=''):
    '''
    Gather info on unknown image(s) (locally installed)

    uuid : string
        optional uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.update [uuid]
    '''
    cmd = 'imgadm update {0}'.format(uuid).rstrip()
    __salt__['cmd.run'](cmd)
    return {}


def avail(search=None, verbose=False):
    '''
    Return a list of available images

    search : string
        search keyword
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.avail [percona]
        salt '*' imgadm.avail verbose=True
    '''
    ret = {}
    cmd = 'imgadm avail -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        if image['manifest']['disabled'] or not image['manifest']['public']:
            continue
        if search and search not in image['manifest']['name']:
            # we skip if we are searching but don't have a match
            continue
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret


def list_installed(verbose=False):
    '''
    Return a list of installed images

    verbose : boolean (False)
        toggle verbose output

    .. versionchanged:: 2019.2.0

        Docker images are now also listed

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.list
        salt '*' imgadm.list docker=True
        salt '*' imgadm.list verbose=True
    '''
    ret = {}
    cmd = 'imgadm list -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret


def show(uuid):
    '''
    Show manifest of a given image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.show e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.show plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_uuid(uuid) or _is_docker_uuid(uuid):
        cmd = 'imgadm show {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret


def get(uuid):
    '''
    Return info on an installed image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.get e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.get plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_docker_uuid(uuid):
        uuid = docker_to_uuid(uuid)

    if _is_uuid(uuid):
        cmd = 'imgadm get {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret


def import_image(uuid, verbose=False):
    '''
    Import an image from the repository

    uuid : string
        uuid to import
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.import e42f8c84-bbea-11e2-b920-078fab2aab1f [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm import {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    uuid = docker_to_uuid(uuid)
    data = _parse_image_meta(get(uuid), verbose)
    return {uuid: data}


def delete(uuid):
    '''
    Remove an installed image

    uuid : string
        Specifies uuid to import

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.delete e42f8c84-bbea-11e2-b920-078fab2aab1f
    '''
    ret = {}
    cmd = 'imgadm delete {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc
    result = []
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result.append(image[2])

    return result


def vacuum(verbose=False):
    '''
    Remove unused images

    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.vacuum [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm vacuum -f'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc (lx-centos-6@20150601)
    result = {}
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result[image[2]] = {
            'name': image[3][1:image[3].index('@')],
            'version': image[3][image[3].index('@')+1:-1]
        }
    if verbose:
        return result
    else:
        return list(result.keys())


def sources(verbose=False):
    '''
    Return a list of available sources

    verbose : boolean (False)
        toggle verbose output

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.sources
    '''
    ret = {}
    cmd = 'imgadm sources -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for src in salt.utils.json.loads(res['stdout']):
        ret[src['url']] = src
        del src['url']

    if not verbose:
        ret = list(ret)

    return ret


def source_delete(source):
    '''
    Delete a source

    source : string
        source url to delete

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_delete https://updates.joyent.com
    '''
    ret = {}
    cmd = 'imgadm sources -d {0}'.format(source)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)


def source_add(source, source_type='imgapi'):
    '''
    Add a new source

    source : string
        source url to add
    source_trype : string (imgapi)
        source type, either imgapi or docker

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_add https://updates.joyent.com
        salt '*' imgadm.source_add https://docker.io docker
    '''
    ret = {}

    # NOTE: there are some undocumented deprecated source types
    #       so we just warn instead of error on those
    if source_type not in ['imgapi', 'docker']:
        log.warning('Possible unsupported imgage source type specified!')

    cmd = 'imgadm sources -a {0} -t {1}'.format(source, source_type)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
