"""
.. module: cloudaux.gcp.auth
    :platform: Unix
    :copyright: (c) 2016 by Google Inc., see AUTHORS for more
    :license: Apache, see LICENSE for more details.
.. moduleauthor:: Tom Melendez (@supertom) <supertom@google.com>
"""
import importlib

from httplib2 import Http
from apiclient.discovery import build
from googleapiclient.http import set_user_agent
from oauth2client.client import GoogleCredentials
from oauth2client.service_account import ServiceAccountCredentials

from cloudaux.gcp.config import USE_GAX, GOOGLE_CLIENT_MAP, DEFAULT_SCOPES
from cloudaux.gcp.decorators import gcp_cache, gcp_stats
from cloudaux.gcp.utils import get_user_agent


@gcp_stats()
@gcp_cache(future_expiration_minutes=15)
def get_client(service, service_type='client', **conn_args):
    """
    User function to get the correct client.

    Based on the GOOGLE_CLIENT_MAP dictionary, the return will be a cloud or general
    client that can interact with the desired service.

    :param service: GCP service to connect to. E.g. 'gce', 'iam'
    :type service: ``str``

    :param conn_args: Dictionary of connection arguments.  'project' is required.
                      'user_agent' can be specified and will be set in the client
                       returned.
    :type conn_args: ``dict``

    :return: client_details, client
    :rtype: ``tuple`` of ``dict``, ``object``
    """
    client_details = choose_client(service)
    user_agent = get_user_agent(**conn_args)
    if client_details:
        if client_details['client_type'] == 'cloud':
            client = get_gcp_client(
                mod_name=client_details['module_name'],
                pkg_name=conn_args.get('pkg_name', 'google.cloud'),
                key_file=conn_args.get('key_file', None),
                project=conn_args['project'], user_agent=user_agent)
        else:
            client = get_google_client(
                mod_name=client_details['module_name'],
                key_file=conn_args.get('key_file', None),
                user_agent=user_agent, api_version=conn_args.get('api_version', 'v1'))
    else:
        # There is no client known for this service. We can try the standard API.
        try:
            client = get_google_client(
                mod_name=service, key_file=conn_args.get('key_file', None),
                user_agent=user_agent, api_version=conn_args.get('api_version', 'v1'))
        except Exception as e:
            raise e

    return client_details, client


def choose_client(service):
    """
    Logic to choose the appropriate client.

    :param service: Google Cloud service name. Examples: 'iam', 'gce'.
    :type service: ``str``

    :return: specific dictionary recommended for a particular service.
    :rtype: ``dict``
    """
    client_options = get_available_clients(service)
    if client_options:
        # For now, choose the first one available
        return client_options[0]
    return None


def get_available_clients(service):
    """
    Return clients available for this service.

    :param service: Google Cloud service name. Examples: 'iam', 'gce'.
    :type service: ``str``

    :return: list of dictionaries describing the clients available.
    :rtype: ``list``
    """
    details = GOOGLE_CLIENT_MAP.get(service, None)
    if details:
        return [details]
    else:
        return None


def get_gcp_client(**kwargs):
    """Public GCP client builder."""
    return _gcp_client(project=kwargs['project'], mod_name=kwargs['mod_name'],
                       pkg_name=kwargs.get('pkg_name', 'google.cloud'),
                       key_file=kwargs.get('key_file', None),
                       http_auth=kwargs.get('http', None),
                       user_agent=kwargs.get('user_agent', None))


def _gcp_client(project, mod_name, pkg_name, key_file=None, http_auth=None,
                user_agent=None):
    """
    Private GCP client builder.

    :param project: Google Cloud project string.
    :type project: ``str``

    :param mod_name: Module name to load.  Should be found in sys.path.
    :type mod_name: ``str``

    :param pkg_name: package name that mod_name is part of.  Default is 'google.cloud' .
    :type pkg_name: ``str``

    :param key_file: Default is None.
    :type key_file: ``str`` or None

    :param http_auth: httplib2 authorized client. Default is None.
    :type http_auth: :class: `HTTPLib2`

    :param user_agent: User Agent string to use in requests. Default is None.
    :type http_auth: ``str`` or None

    :return: GCP client
    :rtype: ``object``
    """
    client = None
    if http_auth is None:
        http_auth = _googleauth(key_file=key_file, user_agent=user_agent)
    try:
        # Using a relative path, so we prefix with a dot (.)
        google_module = importlib.import_module('.' + mod_name,
                                                package=pkg_name)
        client = google_module.Client(use_GAX=USE_GAX, project=project,
                                      http=http_auth)
    except ImportError as ie:
        import_err = 'Unable to import %s.%s' % (pkg_name, mod_name)
        raise ImportError(import_err)
    except TypeError:
        # Not all clients use gRPC
        client = google_module.Client(project=project, http=http_auth)
    if user_agent and hasattr(client, 'user_agent'):
        client.user_agent = user_agent
    return client


def get_google_client(**kwargs):
    return _google_client(kwargs['mod_name'], key_file=kwargs['key_file'],
                          scopes=kwargs.get('scopes', []),
                          http_auth=kwargs.get('http_auth', None),
                          api_version=kwargs.get('api_version', 'v1'),
                          user_agent=kwargs.get('user_agent', None))


def _google_client(mod_name, key_file, scopes, http_auth, api_version,
                   user_agent):
    if http_auth is None:
        http_auth = _googleauth(key_file=key_file, scopes=scopes,
                                user_agent=user_agent)
    client = _build_google_client(service=mod_name, api_version=api_version,
                                  http_auth=http_auth)
    return client


def _googleauth(key_file=None, scopes=[], user_agent=None):
    """
    Google http_auth helper.

    If key_file is not specified, default credentials will be used.

    If scopes is specified (and key_file), will be used instead of DEFAULT_SCOPES

    :param key_file: path to key file to use. Default is None
    :type key_file: ``str``

    :param scopes: scopes to set.  Default is DEFAUL_SCOPES
    :type scopes: ``list``

    :param user_agent: User Agent string to use in requests. Default is None.
    :type http_auth: ``str`` or None

    :return: HTTPLib2 authorized client.
    :rtype: :class: `HTTPLib2`
    """
    if key_file:
        if not scopes:
            scopes = DEFAULT_SCOPES
        creds = ServiceAccountCredentials.from_json_keyfile_name(key_file,
                                                                 scopes=scopes)
    else:
        creds = GoogleCredentials.get_application_default()
    http = Http()
    if user_agent:
        http = set_user_agent(http, user_agent)
    http_auth = creds.authorize(http)
    return http_auth


def _build_google_client(service, api_version, http_auth):
    """
    Google build client helper.

    :param service: service to build client for
    :type service: ``str``

    :param api_version: API version to use.
    :type api_version: ``str``

    :param http_auth: Initialized HTTP client to use.
    :type http_auth: ``object``

    :return: google-python-api client initialized to use 'service'
    :rtype: ``object``
    """
    client = build(service, api_version, http=http_auth)
    return client