# Copyright 2016 EasyStack.  All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from magnumclient.common import utils as magnum_utils
from magnumclient.exceptions import InvalidAttribute
from magnumclient.i18n import _
from magnumclient.v1.cluster_templates import (
    CLUSTER_TEMPLATE_ATTRIBUTES
)

from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging


def _show_cluster_template(cluster_template):
    del cluster_template._info['links']
    for field in cluster_template._info:
        if cluster_template._info[field] is None:
            setattr(cluster_template, field, '-')
    columns = CLUSTER_TEMPLATE_ATTRIBUTES
    return columns, osc_utils.get_item_properties(cluster_template, columns)


class CreateClusterTemplate(command.ShowOne):
    """Create a Cluster Template."""
    _description = _("Create a Cluster Template.")

    def get_parser(self, prog_name):
        parser = super(CreateClusterTemplate, self).get_parser(prog_name)

        parser.add_argument(
            'name',
            metavar='<name>',
            help=_('Name of the cluster template to create.'))
        parser.add_argument(
            '--coe',
            required=True,
            metavar='<coe>',
            help=_('Specify the Container Orchestration Engine to use.'))
        parser.add_argument(
            '--image',
            required=True,
            metavar='<image>',
            help=_('The name or UUID of the base image to customize for the '
                   'Cluster.'))
        parser.add_argument(
            '--external-network',
            dest='external_network',
            required=True,
            metavar='<external-network>',
            help=_('The external Neutron network name or UUID to connect to '
                   'this Cluster Template.'))
        parser.add_argument(
            '--keypair',
            metavar='<keypair>',
            help=_('The name or UUID of the SSH keypair to load into the '
                   'Cluster nodes.'))
        parser.add_argument(
            '--fixed-network',
            dest='fixed_network',
            metavar='<fixed-network>',
            help=_('The private Neutron network name to connect to this '
                   'Cluster model.'))
        parser.add_argument(
            '--fixed-subnet',
            dest='fixed_subnet',
            metavar='<fixed-subnet>',
            help=_('The private Neutron subnet name to connect to Cluster.'))
        parser.add_argument(
            '--network-driver',
            dest='network_driver',
            metavar='<network-driver>',
            help=_('The network driver name for instantiating container '
                   'networks.'))
        parser.add_argument(
            '--volume-driver',
            dest='volume_driver',
            metavar='<volume-driver>',
            help=_('The volume driver name for instantiating container '
                   'volume.'))
        parser.add_argument(
            '--driver',
            dest='driver',
            metavar='<driver>',
            help=_('The name of the Magnum driver to use for this cluster '
                   'template.'))
        parser.add_argument(
            '--dns-nameserver',
            dest='dns_nameserver',
            metavar='<dns-nameserver>',
            default='8.8.8.8',
            help=_('The DNS nameserver to use for this cluster template.'))
        parser.add_argument(
            '--flavor',
            metavar='<flavor>',
            default='m1.medium',
            help=_('The nova flavor name or UUID to use when launching the '
                   'Cluster.'))
        parser.add_argument(
            '--master-flavor',
            dest='master_flavor',
            metavar='<master-flavor>',
            help=_('The nova flavor name or UUID to use when launching the '
                   'master node of the Cluster.'))
        parser.add_argument(
            '--docker-volume-size',
            dest='docker_volume_size',
            metavar='<docker-volume-size>',
            type=int,
            help=_('Specify the number of size in GB for the docker volume '
                   'to use.'))
        parser.add_argument(
            '--docker-storage-driver',
            dest='docker_storage_driver',
            metavar='<docker-storage-driver>',
            default='overlay2',
            help=_('Select a docker storage driver. Default: overlay2'))
        parser.add_argument(
            '--http-proxy',
            dest='http_proxy',
            metavar='<http-proxy>',
            help=_('The http_proxy address to use for nodes in Cluster.'))
        parser.add_argument(
            '--https-proxy',
            dest='https_proxy',
            metavar='<https-proxy>',
            help=_('The https_proxy address to use for nodes in Cluster.'))
        parser.add_argument(
            '--no-proxy',
            dest='no_proxy',
            metavar='<no-proxy>',
            help=_('The no_proxy address to use for nodes in Cluster.'))
        parser.add_argument(
            '--labels',
            metavar='<KEY1=VALUE1,KEY2=VALUE2;KEY3=VALUE3...>',
            action='append',
            default=[],
            help=_('Arbitrary labels in the form of key=value pairs to '
                   'associate with a cluster template. May be used multiple '
                   'times.'))
        parser.add_argument(
            '--tls-disabled',
            dest='tls_disabled',
            action='store_true',
            default=False,
            help=_('Disable TLS in the Cluster.'))
        parser.add_argument(
            '--public',
            action='store_true',
            default=False,
            help=_('Make cluster template public.'))
        parser.add_argument(
            '--registry-enabled',
            dest='registry_enabled',
            action='store_true',
            default=False,
            help=_('Enable docker registry in the Cluster'))
        parser.add_argument(
            '--insecure-registry',
            dest='insecure_registry',
            help=_('The URL pointing to users own private insecure docker'
                   'registry to deploy and run docker containers.'))
        parser.add_argument(
            '--server-type',
            dest='server_type',
            metavar='<server-type>',
            default='vm',
            help=_('Specify the server type to be used for example vm. '
                   'For this release default server type will be vm.'))
        parser.add_argument(
            '--master-lb-enabled',
            dest='master_lb_enabled',
            action='store_true',
            default=False,
            help=_('Indicates whether created Clusters should have a load '
                   'balancer for master nodes or not.'))
        parser.add_argument(
            '--floating-ip-enabled',
            dest='floating_ip_enabled',
            default=[],
            action='append_const',
            const=True,
            help=_('Indicates whether created Clusters should have a '
                   'floating ip.'))
        parser.add_argument(
            '--floating-ip-disabled',
            dest='floating_ip_enabled',
            action='append_const',
            const=False,
            help=_('Disables floating ip creation on the new Cluster'))
        parser.add_argument(
            '--hidden',
            dest='hidden',
            action='store_true',
            default=False,
            help=_('Indicates the cluster template should be hidden.'))
        parser.add_argument(
            '--visible',
            dest='hidden',
            action='store_false',
            help=_('Indicates the cluster template should be visible.'))
        parser.add_argument(
            '--tags',
            action='append',
            default=[],
            metavar='<--tags tag1 --tags tag2,tag3>',
            help=_('Tags to be added to the cluster template.'))

        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)
        mag_client = self.app.client_manager.container_infra
        args = {
            'name': parsed_args.name,
            'image_id': parsed_args.image,
            'keypair_id': parsed_args.keypair,
            'external_network_id': parsed_args.external_network,
            'coe': parsed_args.coe,
            'fixed_network': parsed_args.fixed_network,
            'fixed_subnet': parsed_args.fixed_subnet,
            'network_driver': parsed_args.network_driver,
            'volume_driver': parsed_args.volume_driver,
            'driver': parsed_args.driver,
            'dns_nameserver': parsed_args.dns_nameserver,
            'flavor_id': parsed_args.flavor,
            'master_flavor_id': parsed_args.master_flavor,
            'docker_volume_size': parsed_args.docker_volume_size,
            'docker_storage_driver': parsed_args.docker_storage_driver,
            'http_proxy': parsed_args.http_proxy,
            'https_proxy': parsed_args.https_proxy,
            'no_proxy': parsed_args.no_proxy,
            'labels': magnum_utils.handle_labels(parsed_args.labels),
            'tls_disabled': parsed_args.tls_disabled,
            'public': parsed_args.public,
            'registry_enabled': parsed_args.registry_enabled,
            'insecure_registry': parsed_args.insecure_registry,
            'server_type': parsed_args.server_type,
            'master_lb_enabled': parsed_args.master_lb_enabled,
        }

        # NOTE: Make new arguments backward compatible
        if parsed_args.hidden:
            args['hidden'] = parsed_args.hidden
        if parsed_args.tags:
            args['tags'] = ','.join(set(
                (','.join(parsed_args.tags)).split(',')))

        if len(parsed_args.floating_ip_enabled) > 1:
            raise InvalidAttribute('--floating-ip-enabled and '
                                   '--floating-ip-disabled are '
                                   'mutually exclusive and '
                                   'should be specified only once.')
        elif len(parsed_args.floating_ip_enabled) == 1:
            args['floating_ip_enabled'] = parsed_args.floating_ip_enabled[0]

        ct = mag_client.cluster_templates.create(**args)
        print("Request to create cluster template %s accepted"
              % parsed_args.name)
        return _show_cluster_template(ct)


class DeleteClusterTemplate(command.Command):
    """Delete a Cluster Template."""
    _description = _("Delete a Cluster Template.")

    def get_parser(self, prog_name):
        parser = super(DeleteClusterTemplate, self).get_parser(prog_name)
        parser.add_argument(
            'cluster-templates',
            metavar='<cluster-templates>',
            nargs='+',
            help=_('ID or name of the (cluster template)s to delete.'))

        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        mag_client = self.app.client_manager.container_infra

        for cluster_template in getattr(parsed_args, 'cluster-templates'):
            try:
                mag_client.cluster_templates.delete(cluster_template)
                print(
                    "Request to delete cluster template %s has been accepted."
                    % cluster_template)
            except Exception as e:
                print("Delete for cluster template "
                      "%(cluster_template)s failed: %(e)s" %
                      {'cluster_template': cluster_template, 'e': e})


class ListTemplateCluster(command.Lister):
    """List Cluster Templates."""
    _description = _("List Cluster Templates.")

    log = logging.getLogger(__name__ + ".ListTemplateCluster")

    def get_parser(self, prog_name):
        parser = super(ListTemplateCluster, self).get_parser(prog_name)
        parser.add_argument(
            '--limit',
            metavar='<limit>',
            type=int,
            help=_('Maximum number of cluster templates to return'))
        parser.add_argument(
            '--sort-key',
            metavar='<sort-key>',
            help=_('Column to sort results by'))
        parser.add_argument(
            '--sort-dir',
            metavar='<sort-dir>',
            choices=['desc', 'asc'],
            help=_('Direction to sort. "asc" or "desc".'))
        parser.add_argument(
            '--fields',
            default=None,
            metavar='<fields>',
            help=_('Comma-separated list of fields to display. '
                   'Available fields: uuid, name, coe, image_id, public, '
                   'link, apiserver_port, server_type, tls_disabled, '
                   'registry_enabled'
                   )
            )
        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        mag_client = self.app.client_manager.container_infra
        columns = ['uuid', 'name', 'tags']
        if parsed_args.fields:
            columns += parsed_args.fields.split(',')
        cts = mag_client.cluster_templates.list(limit=parsed_args.limit,
                                                sort_key=parsed_args.sort_key,
                                                sort_dir=parsed_args.sort_dir)
        return (
            columns,
            (osc_utils.get_item_properties(ct, columns) for ct in cts)
        )


class ShowClusterTemplate(command.ShowOne):
    """Show a Cluster Template."""
    _description = _("Show a Cluster Template.")

    log = logging.getLogger(__name__ + ".ShowClusterTemplate")

    def get_parser(self, prog_name):
        parser = super(ShowClusterTemplate, self).get_parser(prog_name)
        parser.add_argument(
            'cluster-template',
            metavar='<cluster-template>',
            help=_('ID or name of the cluster template to show.'))

        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        mag_client = self.app.client_manager.container_infra
        ct = getattr(parsed_args, 'cluster-template', None)
        cluster_template = mag_client.cluster_templates.get(ct)

        return _show_cluster_template(cluster_template)


class UpdateClusterTemplate(command.ShowOne):
    """Update a Cluster Template."""

    log = logging.getLogger(__name__ + ".UpdateClusterTemplate")

    def get_parser(self, prog_name):
        parser = super(UpdateClusterTemplate, self).get_parser(prog_name)
        parser.add_argument(
            'cluster-template',
            metavar='<cluster-template>',
            help=_('The name or UUID of the cluster template to update.'))
        parser.add_argument(
            '--name',
            metavar='<name>',
            help=_('New name for the cluster template.'))
        parser.add_argument(
            '--image',
            metavar='<image>',
            help=_('The name or UUID of the base image to use.'))
        parser.add_argument(
            '--flavor',
            metavar='<flavor>',
            help=_('The nova flavor name or UUID for cluster nodes.'))
        parser.add_argument(
            '--master-flavor',
            dest='master_flavor',
            metavar='<master-flavor>',
            help=_('The nova flavor name or UUID for master nodes.'))
        parser.add_argument(
            '--keypair',
            metavar='<keypair>',
            help=_('The name or UUID of the SSH keypair.'))
        parser.add_argument(
            '--external-network',
            dest='external_network',
            metavar='<external-network>',
            help=_('The external Neutron network name or UUID.'))
        parser.add_argument(
            '--fixed-network',
            dest='fixed_network',
            metavar='<fixed-network>',
            help=_('The private Neutron network name or UUID.'))
        parser.add_argument(
            '--fixed-subnet',
            dest='fixed_subnet',
            metavar='<fixed-subnet>',
            help=_('The private Neutron subnet name or UUID.'))
        parser.add_argument(
            '--network-driver',
            dest='network_driver',
            metavar='<network-driver>',
            help=_('The network driver name for container networks.'))
        parser.add_argument(
            '--volume-driver',
            dest='volume_driver',
            metavar='<volume-driver>',
            help=_('The volume driver name for container volumes.'))
        parser.add_argument(
            '--driver',
            metavar='<driver>',
            help=_('The Magnum driver to use for this cluster template.'))
        parser.add_argument(
            '--dns-nameserver',
            dest='dns_nameserver',
            metavar='<dns-nameserver>',
            help=_('The DNS nameserver for this cluster template.'))
        parser.add_argument(
            '--docker-volume-size',
            dest='docker_volume_size',
            metavar='<docker-volume-size>',
            type=int,
            help=_('Size in GB for the docker volume.'))
        parser.add_argument(
            '--docker-storage-driver',
            dest='docker_storage_driver',
            metavar='<docker-storage-driver>',
            help=_('Docker storage driver (overlay2 recommended).'))
        parser.add_argument(
            '--http-proxy',
            dest='http_proxy',
            metavar='<http-proxy>',
            help=_('The http_proxy address for cluster nodes.'))
        parser.add_argument(
            '--https-proxy',
            dest='https_proxy',
            metavar='<https-proxy>',
            help=_('The https_proxy address for cluster nodes.'))
        parser.add_argument(
            '--no-proxy',
            dest='no_proxy',
            metavar='<no-proxy>',
            help=_('The no_proxy address list for cluster nodes.'))
        parser.add_argument(
            '--insecure-registry',
            dest='insecure_registry',
            metavar='<insecure-registry>',
            help=_('URL of a private insecure docker registry.'))
        parser.add_argument(
            '--label',
            metavar='<key=value>',
            action='append',
            dest='labels',
            default=[],
            help=_('Label to add or update on the cluster template '
                   '(repeat to set multiple labels).'))
        parser.add_argument(
            '--no-label',
            metavar='<key>',
            action='append',
            dest='no_labels',
            default=[],
            help=_('Label key to remove from the cluster template '
                   '(repeat to remove multiple labels).'))
        parser.add_argument(
            '--tag',
            metavar='<tag>',
            action='append',
            dest='tags',
            default=[],
            help=_('Tag to add to the cluster template '
                   '(repeat to set multiple tags).'))
        parser.set_defaults(hidden=None, public=None, tls_disabled=None,
                            registry_enabled=None, master_lb_enabled=None,
                            floating_ip_enabled=None)
        parser.add_argument(
            '--public',
            dest='public',
            action='store_true',
            help=_('Make the cluster template public.'))
        parser.add_argument(
            '--private',
            dest='public',
            action='store_false',
            help=_('Make the cluster template private.'))
        parser.add_argument(
            '--tls-disabled',
            dest='tls_disabled',
            action='store_true',
            help=_('Disable TLS in the cluster.'))
        parser.add_argument(
            '--tls-enabled',
            dest='tls_disabled',
            action='store_false',
            help=_('Enable TLS in the cluster.'))
        parser.add_argument(
            '--registry-enabled',
            dest='registry_enabled',
            action='store_true',
            help=_('Enable docker registry in the cluster.'))
        parser.add_argument(
            '--registry-disabled',
            dest='registry_enabled',
            action='store_false',
            help=_('Disable docker registry in the cluster.'))
        parser.add_argument(
            '--master-lb-enabled',
            dest='master_lb_enabled',
            action='store_true',
            help=_('Enable load balancer for master nodes.'))
        parser.add_argument(
            '--master-lb-disabled',
            dest='master_lb_enabled',
            action='store_false',
            help=_('Disable load balancer for master nodes.'))
        parser.add_argument(
            '--floating-ip-enabled',
            dest='floating_ip_enabled',
            action='store_true',
            help=_('Enable floating IPs for cluster nodes.'))
        parser.add_argument(
            '--floating-ip-disabled',
            dest='floating_ip_enabled',
            action='store_false',
            help=_('Disable floating IPs for cluster nodes.'))
        parser.add_argument(
            '--hidden',
            dest='hidden',
            action='store_true',
            help=_('Hide the cluster template.'))
        parser.add_argument(
            '--visible',
            dest='hidden',
            action='store_false',
            help=_('Make the cluster template visible.'))
        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        mag_client = self.app.client_manager.container_infra

        patch = []

        str_fields = [
            ('name', '/name'),
            ('image', '/image_id'),
            ('flavor', '/flavor_id'),
            ('master_flavor', '/master_flavor_id'),
            ('keypair', '/keypair_id'),
            ('external_network', '/external_network_id'),
            ('fixed_network', '/fixed_network'),
            ('fixed_subnet', '/fixed_subnet'),
            ('network_driver', '/network_driver'),
            ('volume_driver', '/volume_driver'),
            ('driver', '/driver'),
            ('dns_nameserver', '/dns_nameserver'),
            ('docker_storage_driver', '/docker_storage_driver'),
            ('http_proxy', '/http_proxy'),
            ('https_proxy', '/https_proxy'),
            ('no_proxy', '/no_proxy'),
            ('insecure_registry', '/insecure_registry'),
        ]
        for attr, path in str_fields:
            value = getattr(parsed_args, attr, None)
            if value is not None:
                patch.append({'op': 'replace', 'path': path, 'value': value})

        if parsed_args.docker_volume_size is not None:
            patch.append({'op': 'replace', 'path': '/docker_volume_size',
                          'value': parsed_args.docker_volume_size})

        for boolean_attr in ('hidden', 'public', 'tls_disabled',
                             'registry_enabled', 'master_lb_enabled',
                             'floating_ip_enabled'):
            value = getattr(parsed_args, boolean_attr)
            if value is not None:
                patch.append({'op': 'replace',
                              'path': '/%s' % boolean_attr,
                              'value': value})

        for label in parsed_args.labels:
            k, v = label.split('=', 1)
            patch.append({'op': 'add', 'path': '/labels/%s' % k, 'value': v})

        for key in parsed_args.no_labels:
            patch.append({'op': 'remove', 'path': '/labels/%s' % key})

        if parsed_args.tags:
            patch.append({'op': 'replace', 'path': '/tags',
                          'value': ','.join(parsed_args.tags)})

        if not patch:
            raise InvalidAttribute('Nothing to update.')

        name = getattr(parsed_args, 'cluster-template', None)
        ct = mag_client.cluster_templates.update(name, patch)
        return _show_cluster_template(ct)
