# Copyright (c) 2014 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 weakref

from eventlet import semaphore
import heatclient.client as hclient
import keystoneclient
import keystoneclient.auth.identity.access as access
import muranoclient.v1.client as muranoclient
import neutronclient.v2_0.client as nclient
from oslo_config import cfg

from murano.common import auth_utils
from muranoclient.glance import client as art_client

try:
    # integration with congress is optional
    import congressclient.v1.client as congress_client
except ImportError as congress_client_import_error:
    congress_client = None
try:
    import mistralclient.api.client as mistralclient
except ImportError as mistral_import_error:
    mistralclient = None

CONF = cfg.CONF


class ClientManager(object):
    def __init__(self, environment):
        self._trusts_keystone_client = None
        self._token_keystone_client = None
        self._cache = {}
        self._semaphore = semaphore.BoundedSemaphore()
        self._environment = weakref.proxy(environment)

    def get_client(self, name, use_trusts, client_factory):
        if not CONF.engine.use_trusts:
            use_trusts = False

        keystone_client = None if name == 'keystone' else \
            self.get_keystone_client(use_trusts)

        self._semaphore.acquire()
        try:
            client, used_token = self._cache.get(
                (name, use_trusts), (None, None))
            fresh_token = None if keystone_client is None \
                else keystone_client.auth_token
            if use_trusts and used_token != fresh_token:
                client = None
            if not client:
                token = fresh_token
                if not use_trusts:
                    token = self._environment.token
                client = client_factory(keystone_client, token)
                self._cache[(name, use_trusts)] = (client, token)
            return client
        finally:
            self._semaphore.release()

    def get_keystone_client(self, use_trusts=True):
        if not CONF.engine.use_trusts:
            use_trusts = False
        factory = lambda _1, _2: \
            auth_utils.get_client_for_trusts(self._environment.trust_id) \
            if use_trusts else auth_utils.get_client(
                self._environment.token, self._environment.tenant_id)

        return self.get_client('keystone', use_trusts, factory)

    def get_congress_client(self, use_trusts=True):
        """Client for congress services

        :return: initialized congress client
        :raise ImportError: in case that python-congressclient
        is not present on python path
        """

        if not congress_client:
            # congress client was not imported
            raise congress_client_import_error
        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            auth = access.AccessInfoPlugin(keystone_client.auth_ref)
            session = keystoneclient.session.Session(auth=auth)
            return congress_client.Client(session=session,
                                          service_type='policy')

        return self.get_client('congress', use_trusts, factory)

    def get_heat_client(self, use_trusts=True):
        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            heat_settings = CONF.heat

            heat_url = keystone_client.service_catalog.url_for(
                service_type='orchestration',
                endpoint_type=heat_settings.endpoint_type)

            kwargs = {
                'token': auth_token,
                'ca_file': heat_settings.ca_file or None,
                'cert_file': heat_settings.cert_file or None,
                'key_file': heat_settings.key_file or None,
                'insecure': heat_settings.insecure
            }

            if not CONF.engine.use_trusts:
                kwargs.update({
                    'username': 'badusername',
                    'password': 'badpassword'
                })
            return hclient.Client('1', heat_url, **kwargs)

        return self.get_client('heat', use_trusts, factory)

    def get_neutron_client(self, use_trusts=True):
        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            neutron_settings = CONF.neutron

            neutron_url = keystone_client.service_catalog.url_for(
                service_type='network',
                endpoint_type=neutron_settings.endpoint_type)

            return nclient.Client(
                endpoint_url=neutron_url,
                token=auth_token,
                ca_cert=neutron_settings.ca_cert or None,
                insecure=neutron_settings.insecure)

        return self.get_client('neutron', use_trusts, factory)

    def get_murano_client(self, use_trusts=True):
        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            murano_settings = CONF.murano

            murano_url = \
                murano_settings.url or keystone_client.service_catalog.url_for(
                    service_type='application_catalog',
                    endpoint_type=murano_settings.endpoint_type)

            if CONF.packages_opts.packages_service == 'glance':
                glance_settings = CONF.glance
                glance_url = (glance_settings.url or
                              keystone_client.service_catalog.url_for(
                                  service_type='image',
                                  endpoint_type=glance_settings.endpoint_type))

                arts = art_client.Client(
                    endpoint=glance_url, token=auth_token,
                    insecure=glance_settings.insecure,
                    key_file=glance_settings.key_file or None,
                    ca_file=glance_settings.ca_file or None,
                    cert_file=glance_settings.cert_file or None,
                    type_name='murano',
                    type_version=1)
            else:
                arts = None

            return muranoclient.Client(
                endpoint=murano_url,
                key_file=murano_settings.key_file or None,
                ca_file=murano_settings.cacert or None,
                cert_file=murano_settings.cert_file or None,
                insecure=murano_settings.insecure,
                auth_url=keystone_client.auth_url,
                token=auth_token,
                artifacts_client=arts)

        return self.get_client('murano', use_trusts, factory)

    def get_mistral_client(self, use_trusts=True):
        if not mistralclient:
            raise mistral_import_error

        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            mistral_settings = CONF.mistral

            endpoint_type = mistral_settings.endpoint_type
            service_type = mistral_settings.service_type

            mistral_url = keystone_client.service_catalog.url_for(
                service_type=service_type,
                endpoint_type=endpoint_type)

            return mistralclient.client(mistral_url=mistral_url,
                                        project_id=keystone_client.tenant_id,
                                        endpoint_type=endpoint_type,
                                        service_type=service_type,
                                        auth_token=auth_token,
                                        user_id=keystone_client.user_id)

        return self.get_client('mistral', use_trusts, factory)

    def get_artifacts_client(self, use_trusts=True):
        if not CONF.engine.use_trusts:
            use_trusts = False

        def factory(keystone_client, auth_token):
            glance_settings = CONF.glance

            glance_url = (glance_settings.url or
                          keystone_client.service_catalog.url_for(
                              service_type='image',
                              endpoint_type=glance_settings.endpoint_type))

            return art_client.Client(endpoint=glance_url, token=auth_token,
                                     insecure=glance_settings.insecure,
                                     key_file=glance_settings.key_file or None,
                                     cacert=glance_settings.cacert or None,
                                     cert_file=(glance_settings.cert_file or
                                                None),
                                     type_name='murano',
                                     type_version=1)
        return self.get_client('artifacts', use_trusts, factory)