#    Copyright 2015 Mirantis, Inc.
#
#    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.

import collections
import operator
import os

import six

from fuelclient.commands import base
from fuelclient.common import data_utils
from fuelclient import utils


class NodeMixIn(object):
    entity_name = 'node'


class NodeList(NodeMixIn, base.BaseListCommand):
    """Show list of all available nodes."""

    columns = ('id',
               'name',
               'status',
               'os_platform',
               'roles',
               'ip',
               'mac',
               'cluster',
               'platform_name',
               'online')

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

        parser.add_argument(
            '-e',
            '--env',
            type=int,
            help='Show only nodes that are in the specified environment')

        parser.add_argument(
            '-l',
            '--labels',
            type=utils.str_to_unicode,
            nargs='+',
            help='Show only nodes that have specific labels')

        return parser

    def take_action(self, parsed_args):
        data = self.client.get_all(
            environment_id=parsed_args.env, labels=parsed_args.labels)
        data = data_utils.get_display_data_multi(self.columns, data)

        return (self.columns, data)


class NodeShow(NodeMixIn, base.BaseShowCommand):
    """Show info about node with given id."""
    numa_fields = (
        'numa_nodes',
        'supported_hugepages',
        'distances')

    columns = ('id',
               'name',
               'status',
               'os_platform',
               'roles',
               'kernel_params',
               'pending_roles',
               'ip',
               'mac',
               'error_type',
               'pending_addition',
               'hostname',
               'fqdn',
               'platform_name',
               'cluster',
               'online',
               'progress',
               'pending_deletion',
               'group_id',
               # TODO(romcheg): network_data mostly never fits the screen
               # 'network_data',
               'manufacturer')
    columns += numa_fields

    def take_action(self, parsed_args):
        data = self.client.get_by_id(parsed_args.id)

        numa_topology = data['meta'].get('numa_topology', {})
        for key in self.numa_fields:
            data[key] = numa_topology.get(key)

        data = data_utils.get_display_data_single(self.columns, data)
        return self.columns, data


class NodeUpdate(NodeMixIn, base.BaseShowCommand):
    """Change given attributes for a node."""

    columns = NodeShow.columns

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

        parser.add_argument(
            '-H',
            '--hostname',
            type=str,
            default=None,
            help='New hostname for node')

        parser.add_argument(
            '--name',
            type=lambda x: x.decode('utf-8') if six.PY2 else x,
            default=None,
            help='New name for node')

        return parser

    def take_action(self, parsed_args):
        updates = {}
        for attr in self.client._updatable_attributes:
            if getattr(parsed_args, attr, None):
                updates[attr] = getattr(parsed_args, attr)

        updated_node = self.client.update(
            parsed_args.id, **updates)
        updated_node = data_utils.get_display_data_single(
            self.columns, updated_node)

        return (self.columns, updated_node)


class NodeVmsList(NodeMixIn, base.BaseShowCommand):
    """Show list vms for node."""

    columns = ('vms_conf',)

    def take_action(self, parsed_args):
        data = self.client.get_node_vms_conf(parsed_args.id)
        data = data_utils.get_display_data_single(self.columns, data)

        return (self.columns, data)


class NodeCreateVMsConf(NodeMixIn, base.BaseCommand):
    """Create vms config in metadata for selected node."""

    def get_parser(self, prog_name):
        parser = super(NodeCreateVMsConf, self).get_parser(prog_name)
        parser.add_argument('id', type=int,
                            help='Id of the {0}.'.format(self.entity_name))
        parser.add_argument(
            '--conf',
            type=str,
            required=True,
            nargs='+',
            help='JSONs with VMs configuration',
        )

        return parser

    def take_action(self, parsed_args):
        confs = utils.parse_to_list_of_dicts(parsed_args.conf)
        data = self.client.node_vms_create(parsed_args.id, confs)
        msg = "{0}".format(data)
        self.app.stdout.write(msg)


class NodeLabelList(NodeMixIn, base.BaseListCommand):
    """Show list of all labels."""

    columns = (
        'node_id',
        'label_name',
        'label_value')

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

        parser.add_argument(
            '-n',
            '--nodes',
            nargs='+',
            help='Show labels for specific nodes')

        return parser

    def take_action(self, parsed_args):
        data = self.client.get_all_labels_for_nodes(
            node_ids=parsed_args.nodes)
        data = data_utils.get_display_data_multi(self.columns, data)

        return (self.columns, data)


class NodeLabelSet(NodeMixIn, base.BaseCommand):
    """Create or update specifc labels on nodes."""

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

        parser.add_argument(
            '-l',
            '--labels',
            required=True,
            nargs='+',
            help='List of labels for create or update')

        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument(
            '-n',
            '--nodes',
            nargs='+',
            help='Create or update labels only for specific nodes')
        group.add_argument(
            '--nodes-all',
            action='store_true',
            help='Create or update labels for all nodes')

        return parser

    def take_action(self, parsed_args):
        nodes_ids = None if parsed_args.nodes_all else parsed_args.nodes
        data = self.client.set_labels_for_nodes(
            labels=parsed_args.labels, node_ids=nodes_ids)
        msg = "Labels have been updated on nodes: {0} \n".format(
            ','.join(data))
        self.app.stdout.write(msg)


class NodeLabelDelete(NodeMixIn, base.BaseCommand):
    """Delete specific labels on nodes."""

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

        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument(
            '-l',
            '--labels',
            nargs='+',
            help='List of labels keys for delete')
        group.add_argument(
            '--labels-all',
            action='store_true',
            help='Delete all labels for node')

        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument(
            '-n',
            '--nodes',
            nargs='+',
            help='Delete labels only for specific nodes')
        group.add_argument(
            '--nodes-all',
            action='store_true',
            help='Delete labels for all nodes')

        return parser

    def take_action(self, parsed_args):
        nodes_ids = None if parsed_args.nodes_all else parsed_args.nodes
        labels = None if parsed_args.labels_all \
            else parsed_args.labels
        data = self.client.delete_labels_for_nodes(
            labels=labels, node_ids=nodes_ids)
        msg = "Labels have been deleted on nodes: {0} \n".format(
            ','.join(data))
        self.app.stdout.write(msg)


class NodeAttributesDownload(NodeMixIn, base.BaseCommand):
    """Download node attributes."""

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

        parser.add_argument(
            'id', type=int, help='Node ID')
        parser.add_argument(
            '--dir', type=str, help='Directory to save attributes')

        return parser

    def take_action(self, parsed_args):
        file_path = self.client.download_attributes(
            parsed_args.id, parsed_args.dir)
        self.app.stdout.write(
            "Attributes for node {0} were written to {1}"
            .format(parsed_args.id, file_path) + os.linesep)


class NodeAttributesUpload(NodeMixIn, base.BaseCommand):
    """Upload node attributes."""

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

        parser.add_argument(
            'id', type=int, help='Node ID')
        parser.add_argument(
            '--dir', type=str, help='Directory to read attributes from')

        return parser

    def take_action(self, parsed_args):
        self.client.upload_attributes(parsed_args.id, parsed_args.dir)
        self.app.stdout.write(
            "Attributes for node {0} were uploaded."
            .format(parsed_args.id) + os.linesep)


class NodeAnsibleInventory(NodeMixIn, base.BaseCommand):
    """Generate ansible inventory file based on the nodes list."""

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

        # if this is a required argument, we'll avoid ambiguity of having nodes
        # of multiple different clusters in the same inventory file
        parser.add_argument(
            '-e',
            '--env',
            type=int,
            required=True,
            help='Use only nodes that are in the specified environment')

        parser.add_argument(
            '-l',
            '--labels',
            type=utils.str_to_unicode,
            nargs='+',
            help='Use only nodes that have specific labels')

        return parser

    def take_action(self, parsed_args):
        data = self.client.get_all(environment_id=parsed_args.env,
                                   labels=parsed_args.labels)

        nodes_by_role = collections.defaultdict(list)
        for node in data:
            for role in node['roles']:
                nodes_by_role[role].append(node)

        for role, nodes in sorted(nodes_by_role.items()):
            self.app.stdout.write(u'[{role}]\n'.format(role=role))
            self.app.stdout.write(
                u'\n'.join(
                    u'{name} ansible_host={ip}'.format(name=node['hostname'],
                                                       ip=node['ip'])
                    for node in sorted(nodes_by_role[role],
                                       key=operator.itemgetter('hostname'))
                )
            )
            self.app.stdout.write(u'\n\n')
