# 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 importlib
import warnings

from keystoneauth1 import plugin
from os_client_config import constructors

from shade import _utils
from shade import exc


class LegacyClientFactoryMixin(object):
    """Mixin Class containing factory functions for legacy client objects.

    Methods in this class exist for backwards compatibility so will not go
    away any time - but they are all things whose use is discouraged. They're
    in a mixin to unclutter the main class file.
    """

    def _create_legacy_client(
            self, client, service, deprecated=True,
            module_name=None, **kwargs):
        if client not in self._legacy_clients:
            if deprecated:
                self._deprecated_import_check(client)
            if module_name:
                constructors.get_constructor_mapping()[service] = module_name
            self._legacy_clients[client] = self._get_client(service, **kwargs)
        return self._legacy_clients[client]

    def _deprecated_import_check(self, client):
        module_name = '{client}client'.format(client=client)
        warnings.warn(
            'Using shade to get a {module_name} object is deprecated. If you'
            ' need a {module_name} object, please use make_legacy_client in'
            ' os-client-config instead'.format(module_name=module_name))
        try:
            importlib.import_module(module_name)
        except ImportError:
            self.log.error(
                '{module_name} is no longer a dependency of shade. You need to'
                ' install python-{module_name} directly.'.format(
                    module_name=module_name))
            raise

    @property
    def trove_client(self):
        return self._create_legacy_client('trove', 'database')

    @property
    def magnum_client(self):
        return self._create_legacy_client('magnum', 'container-infra')

    @property
    def neutron_client(self):
        return self._create_legacy_client('neutron', 'network')

    @property
    def nova_client(self):
        return self._create_legacy_client('nova', 'compute', version='2.0')

    @property
    def glance_client(self):
        return self._create_legacy_client('glance', 'image')

    @property
    def heat_client(self):
        return self._create_legacy_client('heat', 'orchestration')

    @property
    def swift_client(self):
        return self._create_legacy_client('swift', 'object-store')

    @property
    def cinder_client(self):
        return self._create_legacy_client('cinder', 'volume')

    @property
    def designate_client(self):
        return self._create_legacy_client('designate', 'dns')

    @property
    def keystone_client(self):
        # Trigger discovery from ksa
        self._identity_client

        # Skip broken discovery in ksc. We're good thanks.
        from keystoneclient.v2_0 import client as v2_client
        from keystoneclient.v3 import client as v3_client
        if self.cloud_config.config['identity_api_version'] == '3':
            client_class = v3_client
        else:
            client_class = v2_client

        return self._create_legacy_client(
            'keystone', 'identity',
            client_class=client_class.Client,
            deprecated=True,
            endpoint=self._identity_client.get_endpoint(),
            endpoint_override=self._identity_client.get_endpoint())

    # Set the ironic API microversion to a known-good
    # supported/tested with the contents of shade.
    #
    # NOTE(TheJulia): Defaulted to version 1.6 as the ironic
    # state machine changes which will increment the version
    # and break an automatic transition of an enrolled node
    # to an available state. Locking the version is intended
    # to utilize the original transition until shade supports
    # calling for node inspection to allow the transition to
    # take place automatically.
    # NOTE(mordred): shade will handle microversions more
    # directly in the REST layer. This microversion property
    # will never change. When we implement REST, we should
    # start at 1.6 since that's what we've been requesting
    # via ironic_client
    @property
    def ironic_api_microversion(self):
        # NOTE(mordred) Abuse _legacy_clients to only show
        # this warning once
        if 'ironic-microversion' not in self._legacy_clients:
            warnings.warn(
                'shade is transitioning to direct REST calls which'
                ' will handle microversions with no action needed'
                ' on the part of the user. The ironic_api_microversion'
                ' property is only used by the legacy ironic_client'
                ' constructor and will never change. If you are using'
                ' it for any reason, either switch to just using'
                ' shade ironic-related API calls, or use os-client-config'
                ' make_legacy_client directly and pass os_ironic_api_version'
                ' to it as an argument. It is highly recommended to'
                ' stop using this property.')
            self._legacy_clients['ironic-microversion'] = True
        return self._get_legacy_ironic_microversion()

    def _get_legacy_ironic_microversion(self):
        return '1.6'

    def _join_ksa_version(self, version):
        return ".".join([str(x) for x in version])

    @property
    def ironic_client(self):
        # Trigger discovery from ksa. This will make ironicclient and
        # keystoneauth1.adapter.Adapter code paths both go through discovery.
        # ironicclient does its own magic with discovery, so we won't
        # pass an endpoint_override here like we do for keystoneclient.
        # Just so it's not wasted though, make sure we can handle the
        # min microversion we need.
        needed = self._get_legacy_ironic_microversion()

        # TODO(mordred) Bug in ksa - don't do microversion matching for
        # auth_type = admin_token. Remove this if when the fix lands.
        if (hasattr(plugin.BaseAuthPlugin, 'get_endpoint_data') or
                self.cloud_config.config['auth_type'] not in (
                    'admin_token', 'none')):
            # TODO(mordred) once we're on REST properly, we need a better
            # method for matching requested and available microversion
            endpoint_data = self._baremetal_client.get_endpoint_data()
            if not endpoint_data.min_microversion:
                raise exc.OpenStackCloudException(
                    "shade needs an ironic that supports microversions")
            if endpoint_data.min_microversion[1] > int(needed[-1]):
                raise exc.OpenStackCloudException(
                    "shade needs an ironic that supports microversion {needed}"
                    " but the ironic found has a minimum microversion"
                    " of {found}".format(
                        needed=needed,
                        found=self._join_ksa_version(
                            endpoint_data.min_microversion)))
            if endpoint_data.max_microversion[1] < int(needed[-1]):
                raise exc.OpenStackCloudException(
                    "shade needs an ironic that supports microversion {needed}"
                    " but the ironic found has a maximum microversion"
                    " of {found}".format(
                        needed=needed,
                        found=self._join_ksa_version(
                            endpoint_data.max_microversion)))

        return self._create_legacy_client(
            'ironic', 'baremetal',
            module_name='ironicclient.client.Client',
            os_ironic_api_version=self._get_legacy_ironic_microversion())

    def _get_swift_kwargs(self):
        auth_version = self.cloud_config.get_api_version('identity')
        auth_args = self.cloud_config.config.get('auth', {})
        os_options = {'auth_version': auth_version}
        if auth_version == '2.0':
            os_options['os_tenant_name'] = auth_args.get('project_name')
            os_options['os_tenant_id'] = auth_args.get('project_id')
        else:
            os_options['os_project_name'] = auth_args.get('project_name')
            os_options['os_project_id'] = auth_args.get('project_id')

        for key in (
                'username',
                'password',
                'auth_url',
                'user_id',
                'project_domain_id',
                'project_domain_name',
                'user_domain_id',
                'user_domain_name'):
            os_options['os_{key}'.format(key=key)] = auth_args.get(key)
        return os_options

    @property
    def swift_service(self):
        suppress_warning = 'swift-service' not in self._legacy_clients
        return self.make_swift_service_object(suppress_warning)

    def make_swift_service(self, suppress_warning=False):
        # NOTE(mordred): Not using helper functions because the
        #                error message needs to be different
        if not suppress_warning:
            warnings.warn(
                'Using shade to get a SwiftService object is deprecated. shade'
                ' will automatically do the things SwiftServices does as part'
                ' of the normal object resource calls. If you are having'
                ' trouble using those such that you still need to use'
                ' SwiftService, please file a bug with shade.'
                ' If you understand the issues and want to make this warning'
                ' go away, use cloud.make_swift_service(True) instead of'
                ' cloud.swift_service')
            # Abuse self._legacy_clients so that we only give the warning
            # once. We don't cache SwiftService objects.
            self._legacy_clients['swift-service'] = True
        try:
            import swiftclient.service
        except ImportError:
            self.log.error(
                'swiftclient is no longer a dependency of shade. You need to'
                ' install python-swiftclient directly.')
        with _utils.shade_exceptions("Error constructing SwiftService"):
            endpoint = self.get_session_endpoint(
                service_key='object-store')
            options = dict(os_auth_token=self.auth_token,
                           os_storage_url=endpoint,
                           os_region_name=self.region_name)
            options.update(self._get_swift_kwargs())
            return swiftclient.service.SwiftService(options=options)
