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

"""
This module is a special module to define functions or other resources
which need to be imported outside of openstack_dashboard.api.nova
(like cinder.py) to avoid cyclic imports.
"""

from django.conf import settings
from glanceclient import exc as glance_exceptions
from novaclient import api_versions
from novaclient import client as nova_client

from horizon import exceptions as horizon_exceptions
from horizon.utils import memoized

from openstack_dashboard.api import base
from openstack_dashboard.api import glance
from openstack_dashboard.api import microversions
from openstack_dashboard.contrib.developer.profiler import api as profiler


# Supported compute versions
VERSIONS = base.APIVersionManager("compute", preferred_version=2)
VERSIONS.load_supported_version(1.1, {"client": nova_client, "version": 1.1})
VERSIONS.load_supported_version(2, {"client": nova_client, "version": 2})

INSECURE = settings.OPENSTACK_SSL_NO_VERIFY
CACERT = settings.OPENSTACK_SSL_CACERT


class Server(base.APIResourceWrapper):
    """Simple wrapper around novaclient.server.Server.

    Preserves the request info so image name can later be retrieved.
    """
    _attrs = ['addresses', 'attrs', 'id', 'image', 'links', 'description',
              'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
              'image_name', 'VirtualInterfaces', 'flavor', 'key_name', 'fault',
              'tenant_id', 'user_id', 'created', 'locked',
              'OS-EXT-STS:power_state', 'OS-EXT-STS:task_state',
              'OS-EXT-SRV-ATTR:instance_name', 'OS-EXT-SRV-ATTR:host',
              'OS-EXT-SRV-ATTR:hostname', 'OS-EXT-SRV-ATTR:kernel_id',
              'OS-EXT-SRV-ATTR:ramdisk_id', 'OS-EXT-SRV-ATTR:root_device_name',
              'OS-EXT-SRV-ATTR:root_device_name', 'OS-EXT-SRV-ATTR:user_data',
              'OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:launch_index',
              'OS-EXT-AZ:availability_zone', 'OS-DCF:diskConfig']

    def __init__(self, apiresource, request):
        super().__init__(apiresource)
        self.request = request

    # TODO(gabriel): deprecate making a call to Glance as a fallback.
    @property
    def image_name(self):
        if not self.image:
            return None
        if hasattr(self.image, 'name'):
            return self.image.name
        if 'name' in self.image:
            return self.image['name']
        try:
            image = glance.image_get(self.request, self.image['id'])
            self.image['name'] = image.name
            return image.name
        except (glance_exceptions.ClientException,
                horizon_exceptions.ServiceCatalogException):
            self.image['name'] = None
            return None

    @property
    def availability_zone(self):
        return getattr(self, 'OS-EXT-AZ:availability_zone', "")

    @property
    def has_extended_attrs(self):
        return any(getattr(self, attr, None) for attr in [
            'OS-EXT-SRV-ATTR:instance_name', 'OS-EXT-SRV-ATTR:host',
            'OS-EXT-SRV-ATTR:hostname', 'OS-EXT-SRV-ATTR:kernel_id',
            'OS-EXT-SRV-ATTR:ramdisk_id', 'OS-EXT-SRV-ATTR:root_device_name',
            'OS-EXT-SRV-ATTR:root_device_name', 'OS-EXT-SRV-ATTR:user_data',
            'OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:launch_index',
        ])

    @property
    def internal_name(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:instance_name', "")

    @property
    def host_server(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:host', "")

    @property
    def instance_name(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:instance_name', "")

    @property
    def reservation_id(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:reservation_id', "")

    @property
    def launch_index(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:launch_index', "")

    @property
    def hostname(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:hostname', "")

    @property
    def kernel_id(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:kernel_id', "")

    @property
    def ramdisk_id(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:ramdisk_id', "")

    @property
    def root_device_name(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:root_device_name', "")

    @property
    def user_data(self):
        return getattr(self, 'OS-EXT-SRV-ATTR:user_data', "")


@memoized.memoized
def get_microversion(request, features):
    client = novaclient(request)
    min_ver, max_ver = api_versions._get_server_version_range(client)
    return (microversions.get_microversion_for_features(
        'nova', features, api_versions.APIVersion, min_ver, max_ver))


def get_auth_params_from_request(request):
    """Extracts properties needed by novaclient call from the request object.

    These will be used to memoize the calls to novaclient.
    """
    return (
        request.user.username,
        request.user.token.id,
        request.user.tenant_id,
        request.user.token.project.get('domain_id'),
        base.url_for(request, 'compute'),
        base.url_for(request, 'identity')
    )


@memoized.memoized
def cached_novaclient(request, version=None):
    (
        username,
        token_id,
        project_id,
        project_domain_id,
        nova_url,
        auth_url
    ) = get_auth_params_from_request(request)
    if version is None:
        version = VERSIONS.get_active_version()['version']
    c = nova_client.Client(version,
                           username,
                           token_id,
                           project_id=project_id,
                           project_domain_id=project_domain_id,
                           auth_url=auth_url,
                           insecure=INSECURE,
                           cacert=CACERT,
                           http_log_debug=settings.DEBUG,
                           auth_token=token_id,
                           endpoint_override=nova_url)
    return c


def novaclient(request, version=None):
    if isinstance(version, api_versions.APIVersion):
        version = version.get_string()
    return cached_novaclient(request, version)


def get_novaclient_with_instance_desc(request):
    microversion = get_microversion(request, "instance_description")
    return novaclient(request, version=microversion)


@profiler.trace
def server_get(request, instance_id):
    return Server(get_novaclient_with_instance_desc(request).servers.get(
        instance_id), request)
