# 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 collections import namedtuple
import logging
from operator import itemgetter

from django.conf import settings
from django.utils.translation import gettext_lazy as _

from horizon import exceptions
from openstack_dashboard import api

LOG = logging.getLogger(__name__)


def flavor_list(request):
    """Utility method to retrieve a list of flavors."""
    try:
        return api.nova.flavor_list(request)
    except Exception:
        exceptions.handle(request,
                          _('Unable to retrieve instance flavors.'))
        return []


def sort_flavor_list(request, flavors, with_menu_label=True):
    """Utility method to sort a list of flavors.

    By default, returns the available flavors, sorted by RAM usage (ascending).
    Override these behaviours with a ``CREATE_INSTANCE_FLAVOR_SORT`` dict
    in ``local_settings.py``.
    """
    def get_key(flavor, sort_key):
        try:
            return getattr(flavor, sort_key)
        except AttributeError:
            LOG.warning('Could not find sort key "%s". Using the default '
                        '"ram" instead.', sort_key)
            return getattr(flavor, 'ram')
    try:
        flavor_sort = settings.CREATE_INSTANCE_FLAVOR_SORT
        sort_key = flavor_sort.get('key', 'ram')
        rev = flavor_sort.get('reverse', False)
        if not callable(sort_key):
            def key(flavor):
                return get_key(flavor, sort_key)
        else:
            key = sort_key

        if with_menu_label:
            flavor_list = [(flavor.id, '%s' % flavor.name)
                           for flavor in sorted(flavors, key=key, reverse=rev)]
        else:
            flavor_list = sorted(flavors, key=key, reverse=rev)
        return flavor_list
    except Exception:
        exceptions.handle(request,
                          _('Unable to sort instance flavors.'))
        return []


def server_group_list(request):
    """Utility method to retrieve a list of server groups."""
    try:
        return api.nova.server_group_list(request)
    except Exception:
        exceptions.handle(request,
                          _('Unable to retrieve Nova server groups.'))
        return []


def network_field_data(request, include_empty_option=False, with_cidr=False,
                       for_launch=False):
    """Returns a list of tuples of all networks.

    Generates a list of networks available to the user (request). And returns
    a list of (id, name) tuples.

    :param request: django http request object
    :param include_empty_option: flag to include a empty tuple in the front of
         the list
    :param with_cidr: flag to include subnets cidr in field name
    :return: list of (id, name) tuples
    """
    tenant_id = request.user.tenant_id
    networks = []
    if api.base.is_service_enabled(request, 'network'):
        extra_params = {}
        if for_launch:
            extra_params['include_pre_auto_allocate'] = True
        try:
            networks = api.neutron.network_list_for_tenant(
                request, tenant_id, **extra_params)
        except Exception:
            msg = _('Failed to get network list.')
            exceptions.handle(request, msg)

        _networks = []
        for n in networks:
            if not n['subnet_ids']:
                continue
            v = n.name_or_id
            if with_cidr:
                cidrs = ([subnet.cidr for subnet in n['subnets']
                          if subnet.ip_version == 4] +
                         [subnet.cidr for subnet in n['subnets']
                          if subnet.ip_version == 6])
                v += ' (%s)' % ', '.join(cidrs)
            _networks.append((n.id, v))
        networks = sorted(_networks, key=itemgetter(1))

    if not networks:
        if include_empty_option:
            return [("", _("No networks available")), ]
        return []

    if include_empty_option:
        return [("", _("Select Network")), ] + networks
    return networks


def keypair_field_data(request, include_empty_option=False):
    """Returns a list of tuples of all keypairs.

    Generates a list of keypairs available to the user (request). And returns
    a list of (id, name) tuples.

    :param request: django http request object
    :param include_empty_option: flag to include a empty tuple in the front of
        the list
    :return: list of (id, name) tuples
    """
    keypair_list = []
    try:
        keypairs = api.nova.keypair_list(request)
        keypair_list = [(kp.name, kp.name) for kp in keypairs]
    except Exception:
        exceptions.handle(request, _('Unable to retrieve key pairs.'))

    if not keypair_list:
        if include_empty_option:
            return [("", _("No key pairs available")), ]
        return []

    if include_empty_option:
        return [("", _("Select a key pair")), ] + keypair_list
    return keypair_list


def flavor_field_data(request, include_empty_option=False):
    """Returns a list of tuples of all image flavors.

    Generates a list of image flavors available. And returns a list of
    (id, name) tuples.

    :param request: django http request object
    :param include_empty_option: flag to include a empty tuple in the front of
        the list
    :return: list of (id, name) tuples
    """
    flavors = flavor_list(request)
    if flavors:
        flavors_list = sort_flavor_list(request, flavors)
        if include_empty_option:
            return [("", _("Select Flavor")), ] + flavors_list
        return flavors_list

    if include_empty_option:
        return [("", _("No flavors available")), ]
    return []


def port_field_data(request, with_network=False):
    """Returns a list of tuples of all ports available for the tenant.

    Generates a list of ports that have no device_owner based on the networks
    available to the tenant doing the request.

    :param request: django http request object
    :param with_network: include network name in field name
    :return: list of (id, name) tuples
    """

    def add_more_info_port_name(port, network):
        # add more info to the port for the display
        port_name = "{} ({})".format(
            port.name_or_id, ",".join(
                [ip['ip_address'] for ip in port['fixed_ips']]))
        if with_network:
            port_name += " - {}".format(network.name_or_id)
        return port_name

    ports = []
    if api.base.is_service_enabled(request, 'network'):
        network_list = api.neutron.network_list_for_tenant(
            request, request.user.tenant_id)
        network_dict = dict((n.id, n) for n in network_list)
        ports = [
            (port.id,
             add_more_info_port_name(port, network_dict[port.network_id]))
            for port
            in api.neutron.port_list_with_trunk_types(
                request, tenant_id=request.user.tenant_id)
            if (not port.device_owner and
                not isinstance(port, api.neutron.PortTrunkSubport))
        ]
    ports.sort(key=lambda obj: obj[1])
    return ports


def server_group_field_data(request):
    """Returns a list of tuples of all server groups.

    Generates a list of server groups available. And returns a list of
    (id, name) tuples.

    :param request: django http request object
    :return: list of (id, name) tuples
    """
    server_groups = server_group_list(request)
    if server_groups:
        server_groups_list = [(sg.id, sg.name) for sg in server_groups]
        server_groups_list.sort(key=lambda obj: obj[1])
        return [("", _("Select Server Group")), ] + server_groups_list

    return [("", _("No server groups available")), ]


def resolve_flavor(request, instance, flavors=None, **kwargs):
    """Resolves name of instance flavor independent of API microversion

    :param request: django http request object
    :param instance: api._nova.Server instance to resolve flavor
    :param flavors: dict of flavors already retrieved
    :param kwargs: flavor parameters to return if hit some flavor discrepancy
    :return: flavor name or default placeholder
    """
    def flavor_from_dict(flavor_dict):
        """Creates flavor-like objects from dictionary

        :param flavor_dict: dictionary contains vcpu, ram, name, etc. values
        :return: novaclient.v2.flavors.Flavor like object
        """
        return namedtuple('Flavor', flavor_dict.keys())(*flavor_dict.values())

    if flavors is None:
        flavors = {}
    flavor_id = instance.flavor.get('id')
    if flavor_id:  # Nova API <=2.46
        if flavor_id in flavors:
            return flavors[flavor_id]
        try:
            return api.nova.flavor_get(request, flavor_id)
        except Exception:
            msg = _('Unable to retrieve flavor information '
                    'for instance "%s".') % instance.id
            exceptions.handle(request, msg, ignore=True)
            fallback_flavor = {
                'vcpus': 0, 'ram': 0, 'disk': 0, 'ephemeral': 0, 'swap': 0,
                'name': _('Not available'),
                'original_name': _('Not available'),
                'extra_specs': {},
            }
            fallback_flavor.update(kwargs)
            return flavor_from_dict(fallback_flavor)
    else:
        instance.flavor['name'] = instance.flavor['original_name']
        return flavor_from_dict(instance.flavor)
