# 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 osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging


CLUSTER_TEMPLATE_ATTRIBUTES = [
    'insecure_registry',
    'labels',
    'updated_at',
    'floating_ip_enabled',
    'fixed_subnet',
    'master_flavor_id',
    'uuid',
    'no_proxy',
    'https_proxy',
    'tls_disabled',
    'keypair_id',
    'public',
    'http_proxy',
    'docker_volume_size',
    'server_type',
    'external_network_id',
    'cluster_distro',
    'image_id',
    'volume_driver',
    'registry_enabled',
    'docker_storage_driver',
    'apiserver_port',
    'name',
    'created_at',
    'network_driver',
    'fixed_network',
    'coe',
    'flavor_id',
    'master_lb_enabled',
    'dns_nameserver',
    'project_id',
    'hidden',
    'tags',
]


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(
            '--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. Supported: devicemapper, '
                   'overlay, overlay2. 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,
            '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]

        deprecated = ['devicemapper', 'overlay']
        if args['docker_storage_driver'] in deprecated:
            print("WARNING: Docker storage drivers %s are deprecated and will "
                  "be removed in a future release. Use overlay2 instead." %
                  deprecated)

        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 cluster template to update'))

        parser.add_argument(
            'op',
            metavar='<op>',
            nargs='?',
            choices=['add', 'replace', 'remove'],
            default=None,
            help=_("Operations: one of 'add', 'replace' or 'remove'"))

        parser.add_argument(
            'attributes',
            metavar='<path=value>',
            nargs='*',
            action='append',
            default=[],
            help=_(
                "Attributes to add/replace or remove (only PATH is necessary "
                "on remove)"))

        parser.set_defaults(hidden=None)
        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
        raw_attrs = parsed_args.attributes[0] if parsed_args.attributes else []
        if parsed_args.op:
            patch = magnum_utils.args_array_to_patch(parsed_args.op, raw_attrs)
        else:
            patch = []
        if parsed_args.hidden is not None:
            patch += [{'op': 'replace', 'path': '/hidden',
                       'value': parsed_args.hidden}]
        if not patch:
            raise InvalidAttribute('At least one of op/attributes or '
                                   '--hidden/--visible must be specified.')
        name = getattr(parsed_args, 'cluster-template', None)
        ct = mag_client.cluster_templates.update(name, patch)
        return _show_cluster_template(ct)
