# Copyright 2010 OpenStack Foundation
# All Rights Reserved.
#
#    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 urllib.parse as urlparse

from debtcollector import removals

from keystoneclient import exceptions
from keystoneclient import httpclient
from keystoneclient.i18n import _


_logger = logging.getLogger(__name__)


# NOTE(jamielennox): To be removed after Pike.
@removals.removed_class('keystoneclient.generic.client.Client',
                        message='Use keystoneauth discovery',
                        version='3.9.0',
                        removal_version='4.0.0')
class Client(httpclient.HTTPClient):
    """Client for the OpenStack Keystone pre-version calls API.

    :param string endpoint: A user-supplied endpoint URL for the keystone
                            service.
    :param integer timeout: Allows customization of the timeout for client
                            http requests. (optional)

    Example::

        >>> from keystoneclient.generic import client
        >>> root = client.Client(auth_url=KEYSTONE_URL)
        >>> versions = root.discover()
        ...
        >>> from keystoneclient.v2_0 import client as v2client
        >>> keystone = v2client.Client(auth_url=versions['v2.0']['url'])
        ...
        >>> user = keystone.users.get(USER_ID)
        >>> user.delete()

    """

    def __init__(self, endpoint=None, **kwargs):
        """Initialize a new client for the Keystone v2.0 API."""
        super(Client, self).__init__(endpoint=endpoint, **kwargs)
        self.endpoint = endpoint

    def discover(self, url=None):
        """Discover Keystone servers and return API versions supported.

        :param url: optional url to test (without version)

        Returns::

            {
                'message': 'Keystone found at http://127.0.0.1:5000/',
                'v2.0': {
                    'status': 'beta',
                    'url': 'http://127.0.0.1:5000/v2.0/',
                    'id': 'v2.0'
                },
            }

        """
        if url:
            return self._check_keystone_versions(url)
        else:
            return self._local_keystone_exists()

    def _local_keystone_exists(self):
        """Check if Keystone is available on default local port 35357."""
        results = self._check_keystone_versions("http://localhost:35357")
        if results is None:
            results = self._check_keystone_versions("https://localhost:35357")
        return results

    def _check_keystone_versions(self, url):
        """Call Keystone URL and detects the available API versions."""
        try:
            resp, body = self._request(url, "GET",
                                       headers={'Accept':
                                                'application/json'})
            # Multiple Choices status code is returned by the root
            # identity endpoint, with references to one or more
            # Identity API versions -- v3 spec
            # some cases we get No Content
            if resp.status_code in (200, 204, 300):
                try:
                    results = {}
                    if 'version' in body:
                        results['message'] = _("Keystone found at %s") % url
                        version = body['version']
                        # Stable/diablo incorrect format
                        id, status, version_url = (
                            self._get_version_info(version, url))
                        results[str(id)] = {"id": id,
                                            "status": status,
                                            "url": version_url}
                        return results
                    elif 'versions' in body:
                        # Correct format
                        results['message'] = _("Keystone found at %s") % url
                        for version in body['versions']['values']:
                            id, status, version_url = (
                                self._get_version_info(version, url))
                            results[str(id)] = {"id": id,
                                                "status": status,
                                                "url": version_url}
                        return results
                    else:
                        results['message'] = (
                            _("Unrecognized response from %s") % url)
                    return results
                except KeyError:
                    raise exceptions.AuthorizationFailure()
            elif resp.status_code == 305:
                return self._check_keystone_versions(resp['location'])
            else:
                raise exceptions.from_response(resp, "GET", url)
        except Exception:
            _logger.exception('Failed to detect available versions.')

    def discover_extensions(self, url=None):
        """Discover Keystone extensions supported.

        :param url: optional url to test (should have a version in it)

        Returns::

            {
                'message': 'Keystone extensions at http://127.0.0.1:35357/v2',
                'OS-KSEC2': 'OpenStack EC2 Credentials Extension',
            }

        """
        if url:
            return self._check_keystone_extensions(url)

    def _check_keystone_extensions(self, url):
        """Call Keystone URL and detects the available extensions."""
        try:
            if not url.endswith("/"):
                url += '/'
            resp, body = self._request("%sextensions" % url, "GET",
                                       headers={'Accept':
                                                'application/json'})
            if resp.status_code in (200, 204):  # some cases we get No Content
                if 'extensions' in body and 'values' in body['extensions']:
                    # Parse correct format (per contract)
                    extensions = body['extensions']['values']
                elif 'extensions' in body:
                    # Support incorrect, but prevalent format
                    extensions = body['extensions']
                else:
                    return dict(message=(
                        _('Unrecognized extensions response from %s') % url))

                return dict(self._get_extension_info(e) for e in extensions)
            elif resp.status_code == 305:
                return self._check_keystone_extensions(resp['location'])
            else:
                raise exceptions.from_response(
                    resp, "GET", "%sextensions" % url)
        except Exception:
            _logger.exception('Failed to check keystone extensions.')

    @staticmethod
    def _get_version_info(version, root_url):
        """Parse version information.

        :param version: a dict of a Keystone version response
        :param root_url: string url used to construct
            the version if no URL is provided.
        :returns: tuple - (verionId, versionStatus, versionUrl)
        """
        id = version['id']
        status = version['status']
        ref = urlparse.urljoin(root_url, id)
        if 'links' in version:
            for link in version['links']:
                if link['rel'] == 'self':
                    ref = link['href']
                    break
        return (id, status, ref)

    @staticmethod
    def _get_extension_info(extension):
        """Parse extension information.

        :param extension: a dict of a Keystone extension response
        :returns: tuple - (alias, name)
        """
        alias = extension['alias']
        name = extension['name']
        return (alias, name)
