# 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 json

from functools import wraps
from multiprocessing import pool

from django.conf import settings
from openstack_dashboard import api as dashboard_api

from heat_dashboard import api as heat_api


try:
    API_TIMEOUT = settings.HEAT_TEMPLATE_GENERATOR_API_TIMEOUT
except AttributeError:
    API_TIMEOUT = 60

try:
    API_PARALLEL = settings.HEAT_TEMPLATE_GENERATOR_API_PARALLEL
except AttributeError:
    API_PARALLEL = 2


def handle_exception(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
        ret, err = None, None
        try:
            ret = func(*args, **kwargs)
        except Exception as error:
            err = error.message
        return ret if ret else [], err
    return wrapped


@handle_exception
def get_networks(request):
    return dashboard_api.neutron.network_list(request)


@handle_exception
def get_subnets(request):
    return dashboard_api.neutron.subnet_list(request)


@handle_exception
def get_volume_ids(request):
    return [{'id': vol.id,
             'name': vol.name if vol.name else '(%s)' % vol.id}
            for vol in dashboard_api.cinder.volume_list(request)]


@handle_exception
def get_volume_snapshots(request):
    return [{'id': volsnap.id,
             'name': volsnap.name if volsnap.name else '(%s)' % volsnap.id[:6]}
            for volsnap in dashboard_api.cinder.volume_snapshot_list(request)]


@handle_exception
def get_volume_types(request):
    return [{'id': voltype.id,
             'name': voltype.name if voltype.name else '(%s)' % voltype.id[:6]}
            for voltype in dashboard_api.cinder.volume_type_list(request)]


@handle_exception
def get_volume_backups(request):
    return [{'id': volbackup.id,
             'name': volbackup.name
             if volbackup.name else '(%s)' % volbackup.id[:6]}
            for volbackup in dashboard_api.cinder.volume_backup_list(request)]


@handle_exception
def get_images(request):
    images = dashboard_api.glance.image_list_detailed(request)
    if isinstance(images, tuple):
        images = images[0]
    return [{'id': img.id,
             'name': img.name if img.name else '(%s)' % img.id[:6]}
            for img in images]


@handle_exception
def get_floatingips(request):
    return [{'id': fip.id, 'name': fip.floating_ip_address}
            for fip in dashboard_api.neutron.tenant_floating_ip_list(
                request, True)]


@handle_exception
def get_ports(request):
    return [{'id': port.id,
             'name': port.name if port.name else '(%s)' % port.id[:6]}
            for port in dashboard_api.neutron.port_list(request)]


@handle_exception
def get_security_groups(request):
    return [{'id': secgroup.id,
             'name': secgroup.name
             if secgroup.name else '(%s)' % secgroup.id[:6]}
            for secgroup in dashboard_api.neutron.security_group_list(request)]


@handle_exception
def get_routers(request):
    return [{'id': router.id,
             'name': router.name if router.name else '(%s)' % router.id[:6]}
            for router in dashboard_api.neutron.router_list(request)]


@handle_exception
def get_qos_policies(request):
    return [{'id': policy.id,
             'name': policy.name
             if policy.name else '(%s)' % policy.id[:6]}
            for policy in dashboard_api.neutron.policy_list(request)]


@handle_exception
def get_availability_zones(request):
    return [{'id': az.zoneName, 'name': az.zoneName}
            for az in dashboard_api.nova.availability_zone_list(request)]


@handle_exception
def get_flavors(request):
    return [{'id': flavor.name, 'name': flavor.name}
            for flavor in dashboard_api.nova.flavor_list(request)]


@handle_exception
def get_instances(request):
    servers = dashboard_api.nova.server_list(request)
    if isinstance(servers, tuple):
        servers = servers[0]
    return [{'id': server.id,
             'name': server.name if server.name else '(%s)' % server.id[:6]}
            for server in servers]


@handle_exception
def get_keypairs(request):
    return [{'name': keypair.name}
            for keypair in dashboard_api.nova.keypair_list(request)]


@handle_exception
def get_template_versions(request):
    return [{'name': version.version, 'id': version.version}
            for version in heat_api.heat.template_version_list(request)
            if version.type == 'hot']


class APIThread(object):
    thread_pool = pool.ThreadPool(processes=API_PARALLEL)
    async_results = {}

    def add_thread(self, apikey, func, args):
        self.async_results[apikey] = self.thread_pool.apply_async(func, args)

    def get_async_result(self, apikey):
        if apikey not in self.async_results:
            return [], None
        try:
            ret, err = self.async_results[apikey].get(
                timeout=API_TIMEOUT)
        except Exception as error:
            ret, err = [], error.message
        return ret, err


def _get_network_resources(options, all_networks):
    try:
        if all_networks:
            options['networks'] = [
                {'id': nw.id,
                 'name': nw.name if nw.name else '(%s)' % nw.id[: 6]}
                for nw in all_networks if not getattr(nw, 'router:external')]
            options['floating_networks'] = [
                {'id': nw.id,
                 'name': nw.name if nw.name else '(%s)' % nw.id[: 6]}
                for nw in all_networks if getattr(nw, 'router:external')]
        else:
            options['networks'] = []
            options['floating_networks'] = []
    except Exception:
        options['networks'] = []
        options['floating_networks'] = []


def _get_subnet_resources(options, all_subnets):
    try:
        if all_subnets and options.get('floating_networks'):
            floating_network_ids = [nw.get('id')
                                    for nw in options['floating_networks']]
            options['subnets'] = [{'id': sb.id, 'name': sb.name}
                                  for sb in all_subnets
                                  if sb.network_id not in floating_network_ids]

            options['floating_subnets'] = [
                {'id': subnet.id, 'name': subnet.name}
                for subnet in all_subnets
                if subnet.network_id in floating_network_ids]
        else:
            options['subnets'] = []
            options['floating_subnets'] = []
    except Exception:
        options['subnets'] = []
        options['floating_subnets'] = []


def get_resource_options(request):
    api_threads = APIThread()
    api_mapping = {
        'volumes': get_volume_ids,
        'volume_snapshots': get_volume_snapshots,
        'volume_types': get_volume_types,
        'volume_backups': get_volume_backups,
        'images': get_images,
        'floatingips': get_floatingips,
        'networks': get_networks,
        'subnets': get_subnets,
        'ports': get_ports,
        'security_groups': get_security_groups,
        'routers': get_routers,
        'qos_policies': get_qos_policies,
        'availability_zones': get_availability_zones,
        'flavors': get_flavors,
        'instances': get_instances,
        'keypairs': get_keypairs,
        'template_versions': get_template_versions,
    }

    options = {}
    errors = {}

    for resource, method in api_mapping.items():
        api_threads.add_thread(resource, method, args=(request,))

    for resource in api_mapping.keys():
        ret, err = api_threads.get_async_result(resource)
        options[resource] = ret
        if err:
            errors[resource.replace('_', ' ').capitalize()] = err

    all_networks = options.pop('networks')
    _get_network_resources(options, all_networks)

    all_subnets = options.pop('subnets')
    _get_subnet_resources(options, all_subnets)

    role_names = []
    for role in request.user.roles:
        role_names.append(role.get('name'))
    options.update({
        'auth': {
            'tenant_id': request.user.tenant_id,
            'admin': 'admin' in role_names,
        },
    })

    if len(errors.keys()) > 0:
        options.update({'errors': errors})

    return json.dumps(options)
