# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Openstack, LLC
# Copyright 2012 Nebula, 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 logging
import urlparse

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

from keystoneclient import service_catalog
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient.v2_0 import tokens

from horizon.api import base
from horizon import exceptions


LOG = logging.getLogger(__name__)
DEFAULT_ROLE = None


class Service(base.APIDictWrapper):
    """ Wrapper for a dict based on the service data from keystone. """
    _attrs = ['id', 'type', 'name']

    def __init__(self, service, *args, **kwargs):
        super(Service, self).__init__(service, *args, **kwargs)
        self.url = service['endpoints'][0]['internalURL']
        self.host = urlparse.urlparse(self.url).hostname
        self.region = service['endpoints'][0]['region']
        self.disabled = None

    def __unicode__(self):
        if(self.type == "identity"):
            return _("%(type)s (%(backend)s backend)") \
                     % {"type": self.type,
                        "backend": keystone_backend_name()}
        else:
            return self.type

    def __repr__(self):
        return "<Service: %s>" % unicode(self)


def _get_endpoint_url(request, endpoint_type, catalog=None):
    if getattr(request.user, "service_catalog", None):
        return base.url_for(request,
                            service_type='identity',
                            endpoint_type=endpoint_type)
    return request.session.get('region_endpoint',
                               getattr(settings, 'OPENSTACK_KEYSTONE_URL'))


def keystoneclient(request, username=None, password=None, tenant_id=None,
                   token_id=None, endpoint=None, endpoint_type=None,
                   admin=False):
    """Returns a client connected to the Keystone backend.

    Several forms of authentication are supported:

        * Username + password -> Unscoped authentication
        * Username + password + tenant id -> Scoped authentication
        * Unscoped token -> Unscoped authentication
        * Unscoped token + tenant id -> Scoped authentication
        * Scoped token -> Scoped authentication

    Available services and data from the backend will vary depending on
    whether the authentication was scoped or unscoped.

    Lazy authentication if an ``endpoint`` parameter is provided.

    Calls requiring the admin endpoint should have ``admin=True`` passed in
    as a keyword argument.

    The client is cached so that subsequent API calls during the same
    request/response cycle don't have to be re-authenticated.
    """
    user = request.user
    if admin:
        if not user.is_admin():
            raise exceptions.NotAuthorized
        endpoint_type = 'adminURL'
    else:
        endpoint_type = endpoint_type or getattr(settings,
                                                 'OPENSTACK_ENDPOINT_TYPE',
                                                 'internalURL')

    # Take care of client connection caching/fetching a new client.
    # Admin vs. non-admin clients are cached separately for token matching.
    cache_attr = "_keystone_admin" if admin else "_keystone"
    if hasattr(request, cache_attr) and (not token_id
            or getattr(request, cache_attr).auth_token == token_id):
        LOG.debug("Using cached client for token: %s" % user.token)
        conn = getattr(request, cache_attr)
    else:
        endpoint_lookup = _get_endpoint_url(request, endpoint_type)
        auth_url = endpoint or endpoint_lookup
        LOG.debug("Creating a new keystoneclient connection to %s." % auth_url)
        conn = keystone_client.Client(username=username or user.username,
                                      password=password,
                                      tenant_id=tenant_id or user.tenant_id,
                                      token=token_id or user.token,
                                      auth_url=auth_url,
                                      endpoint=endpoint)
        setattr(request, cache_attr, conn)

    # Fetch the correct endpoint if we've re-scoped the token.
    catalog = getattr(conn, 'service_catalog', None)
    if catalog and "serviceCatalog" in catalog.catalog.keys():
        catalog = catalog.catalog['serviceCatalog']
    endpoint = _get_endpoint_url(request, endpoint_type, catalog)
    conn.management_url = endpoint

    return conn


def tenant_name(request, tenant_id):
    return keystoneclient(request).tenants.get(tenant_id).name


def tenant_create(request, tenant_name, description, enabled):
    return keystoneclient(request, admin=True).tenants.create(tenant_name,
                                                  description,
                                                  enabled)


def tenant_get(request, tenant_id, admin=False):
    return keystoneclient(request, admin=admin).tenants.get(tenant_id)


def tenant_delete(request, tenant_id):
    keystoneclient(request, admin=True).tenants.delete(tenant_id)


def tenant_list(request, admin=False):
    return keystoneclient(request, admin=admin).tenants.list()


def tenant_update(request, tenant_id, tenant_name, description, enabled):
    return keystoneclient(request, admin=True).tenants.update(tenant_id,
                                                              tenant_name,
                                                              description,
                                                              enabled)


def tenant_list_for_token(request, token, endpoint_type=None):
    endpoint_type = endpoint_type or getattr(settings,
                                             'OPENSTACK_ENDPOINT_TYPE',
                                             'internalURL')
    c = keystoneclient(request,
                       token_id=token,
                       endpoint=_get_endpoint_url(request, endpoint_type),
                       endpoint_type=endpoint_type)
    return c.tenants.list()


def token_create(request, tenant, username, password):
    '''
    Creates a token using the username and password provided. If tenant
    is provided it will retrieve a scoped token and the service catalog for
    the given tenant. Otherwise it will return an unscoped token and without
    a service catalog.
    '''
    c = keystoneclient(request,
                       username=username,
                       password=password,
                       tenant_id=tenant,
                       endpoint=_get_endpoint_url(request, 'internalURL'))
    token = c.tokens.authenticate(username=username,
                                  password=password,
                                  tenant_id=tenant)
    return token


def token_create_scoped(request, tenant, token):
    '''
    Creates a scoped token using the tenant id and unscoped token; retrieves
    the service catalog for the given tenant.
    '''
    if hasattr(request, '_keystone'):
        del request._keystone
    c = keystoneclient(request,
                       tenant_id=tenant,
                       token_id=token,
                       endpoint=_get_endpoint_url(request, 'internalURL'))
    raw_token = c.tokens.authenticate(tenant_id=tenant,
                                      token=token,
                                      return_raw=True)
    c.service_catalog = service_catalog.ServiceCatalog(raw_token)
    if request.user.is_admin():
        c.management_url = c.service_catalog.url_for(service_type='identity',
                                                     endpoint_type='adminURL')
    else:
        endpoint_type = getattr(settings,
                                'OPENSTACK_ENDPOINT_TYPE',
                                'internalURL')
        c.management_url = c.service_catalog.url_for(
                service_type='identity', endpoint_type=endpoint_type)
    scoped_token = tokens.Token(tokens.TokenManager, raw_token)
    return scoped_token


def user_list(request, tenant_id=None):
    return keystoneclient(request, admin=True).users.list(tenant_id=tenant_id)


def user_create(request, user_id, email, password, tenant_id, enabled):
    return keystoneclient(request, admin=True).users.create(user_id,
                                                            password,
                                                            email,
                                                            tenant_id,
                                                            enabled)


def user_delete(request, user_id):
    keystoneclient(request, admin=True).users.delete(user_id)


def user_get(request, user_id, admin=True):
    return keystoneclient(request, admin=admin).users.get(user_id)


def user_update(request, user, **data):
    return keystoneclient(request, admin=True).users.update(user, **data)


def user_update_enabled(request, user_id, enabled):
    return keystoneclient(request, admin=True).users.update_enabled(user_id,
                                                                    enabled)


def user_update_password(request, user_id, password, admin=True):
    return keystoneclient(request, admin=admin).users.update_password(user_id,
                                                                      password)


def user_update_tenant(request, user_id, tenant_id, admin=True):
    return keystoneclient(request, admin=admin).users.update_tenant(user_id,
                                                                    tenant_id)


def role_list(request):
    """ Returns a global list of available roles. """
    return keystoneclient(request, admin=True).roles.list()


def add_tenant_user_role(request, tenant_id, user_id, role_id):
    """ Adds a role for a user on a tenant. """
    return keystoneclient(request, admin=True).roles.add_user_role(user_id,
                                                                   role_id,
                                                                   tenant_id)


def remove_tenant_user(request, tenant_id, user_id):
    """ Removes all roles from a user on a tenant, removing them from it. """
    client = keystoneclient(request, admin=True)
    roles = client.roles.roles_for_user(user_id, tenant_id)
    for role in roles:
        client.roles.remove_user_role(user_id, role.id, tenant_id)


def get_default_role(request):
    """
    Gets the default role object from Keystone and saves it as a global
    since this is configured in settings and should not change from request
    to request. Supports lookup by name or id.
    """
    global DEFAULT_ROLE
    default = getattr(settings, "OPENSTACK_KEYSTONE_DEFAULT_ROLE", None)
    if default and DEFAULT_ROLE is None:
        try:
            roles = keystoneclient(request, admin=True).roles.list()
        except:
            exceptions.handle(request)
        for role in roles:
            if role.id == default or role.name == default:
                DEFAULT_ROLE = role
                break
    return DEFAULT_ROLE


def list_ec2_credentials(request, user_id):
    return keystoneclient(request).ec2.list(user_id)


def create_ec2_credentials(request, user_id, tenant_id):
    return keystoneclient(request).ec2.create(user_id, tenant_id)


def get_user_ec2_credentials(request, user_id, access_token):
    return keystoneclient(request).ec2.get(user_id, access_token)


def keystone_can_edit_user():
    if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"):
        return settings.OPENSTACK_KEYSTONE_BACKEND['can_edit_user']
    else:
        return False


def keystone_backend_name():
    if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"):
        return settings.OPENSTACK_KEYSTONE_BACKEND['name']
    else:
        return 'unknown'
