# Copyright 2015 - 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 argparse
import collections
import inspect
import json
import os

from barbicanclient import base as barbican_base
from barbicanclient import client as barbicanclient
from ceilometerclient.v2 import client as ceilometerclient
from cinderclient.openstack.common.apiclient import base as cinder_base
from cinderclient.v2 import client as cinderclient
from glanceclient.v2 import client as glanceclient
from heatclient.openstack.common.apiclient import base as heat_base
from heatclient.v1 import client as heatclient
from ironicclient.common import base as ironic_base
from ironicclient.v1 import client as ironicclient
from keystoneclient import base as keystone_base
from keystoneclient.v3 import client as keystoneclient
from mistralclient.api import base as mistral_base
from mistralclient.api.v2 import client as mistralclient
from novaclient import base as nova_base
from novaclient import client as novaclient
from troveclient import base as trove_base
from troveclient.v1 import client as troveclient

# TODO(nmakhotkin): Find a rational way to do it for neutron.
# TODO(nmakhotkin): Implement recursive way of searching for managers
# TODO(nmakhotkin): (e.g. keystone).
# TODO(dprince): Need to update ironic_inspector_client before we can
# plug it in cleanly here.
# TODO(dprince): Swiftclient doesn't currently support discovery
# like we do in this class.
# TODO(therve): Zaqarclient doesn't currently support discovery
# like we do in this class.

"""It is simple CLI tool which allows to see and update mapping.json file
if needed. mapping.json contains all allowing OpenStack actions sorted by
service name. Usage example:

  python tools/get_action_list.py nova

The result will be simple JSON containing action name as a key and method
path as a value. For updating mapping.json it is need to copy all keys and
values of the result to corresponding section of mapping.json:

  ...mapping.json...
  "nova": {
      <put it here>
  },
  ...mapping.json...


Note: in case of Keystone service, correct OS_AUTH_URL v3 and the rest auth
info must be provided. It can be provided either via environment variables
or CLI arguments. See --help for details.
"""

BASE_HEAT_MANAGER = heat_base.HookableMixin
BASE_NOVA_MANAGER = nova_base.HookableMixin
BASE_KEYSTONE_MANAGER = keystone_base.Manager
BASE_CINDER_MANAGER = cinder_base.HookableMixin
BASE_MISTRAL_MANAGER = mistral_base.ResourceManager
BASE_TROVE_MANAGER = trove_base.Manager
BASE_IRONIC_MANAGER = ironic_base.Manager
BASE_BARBICAN_MANAGER = barbican_base.BaseEntityManager


def get_parser():
    parser = argparse.ArgumentParser(
        description='Gets All needed methods of OpenStack clients.',
        usage="python get_action_list.py <service_name>"
    )
    parser.add_argument(
        'service',
        choices=CLIENTS.keys(),
        help='Service name which methods need to be found.'
    )
    parser.add_argument(
        '--os-username',
        dest='username',
        default=os.environ.get('OS_USERNAME', 'admin'),
        help='Authentication username (Env: OS_USERNAME)'
    )
    parser.add_argument(
        '--os-password',
        dest='password',
        default=os.environ.get('OS_PASSWORD', 'openstack'),
        help='Authentication password (Env: OS_PASSWORD)'
    )
    parser.add_argument(
        '--os-tenant-name',
        dest='tenant_name',
        default=os.environ.get('OS_TENANT_NAME', 'Default'),
        help='Authentication tenant name (Env: OS_TENANT_NAME)'
    )
    parser.add_argument(
        '--os-auth-url',
        dest='auth_url',
        default=os.environ.get('OS_AUTH_URL'),
        help='Authentication URL (Env: OS_AUTH_URL)'
    )

    return parser


GLANCE_NAMESPACE_LIST = [
    'image_members', 'image_tags', 'images', 'schemas', 'tasks'
]

CEILOMETER_NAMESPACE_LIST = [
    'alarms', 'capabilities', 'event_types', 'events', 'meters',
    'new_samples', 'query_alarm_history', 'query_alarms', 'query_samples',
    'resources', 'samples', 'statistics', 'trait_descriptions', 'traits'
]


def get_nova_client(**kwargs):
    return novaclient.Client(2)


def get_keystone_client(**kwargs):
    return keystoneclient.Client(**kwargs)


def get_glance_client(**kwargs):
    return glanceclient.Client(kwargs.get('auth_url'))


def get_heat_client(**kwargs):
    return heatclient.Client('')


def get_ceilometer_client(**kwargs):
    return ceilometerclient.Client('')


def get_cinder_client(**kwargs):
    return cinderclient.Client()


def get_mistral_client(**kwargs):
    return mistralclient.Client()


def get_trove_client(**kwargs):
    return troveclient.Client('username', 'password')


def get_ironic_client(**kwargs):
    return ironicclient.Client("http://127.0.0.1:6385/")


def get_barbican_client(**kwargs):
    return barbicanclient.Client(
        project_id="1",
        endpoint="http://127.0.0.1:9311"
    )


CLIENTS = {
    'nova': get_nova_client,
    'heat': get_heat_client,
    'ceilometer': get_ceilometer_client,
    'cinder': get_cinder_client,
    'keystone': get_keystone_client,
    'glance': get_glance_client,
    'trove': get_trove_client,
    'ironic': get_ironic_client,
    'barbican': get_barbican_client,
    'mistral': get_mistral_client,
    # 'neutron': get_nova_client
    # 'baremetal_introspection': ...
    # 'swift': ...
    # 'zaqar': ...
}
BASE_MANAGERS = {
    'nova': BASE_NOVA_MANAGER,
    'heat': BASE_HEAT_MANAGER,
    'ceilometer': None,
    'cinder': BASE_CINDER_MANAGER,
    'keystone': BASE_KEYSTONE_MANAGER,
    'glance': None,
    'trove': BASE_TROVE_MANAGER,
    'ironic': BASE_IRONIC_MANAGER,
    'barbican': BASE_BARBICAN_MANAGER,
    'mistral': BASE_MISTRAL_MANAGER,
    # 'neutron': BASE_NOVA_MANAGER
    # 'baremetal_introspection': ...
    # 'swift': ...
    # 'zaqar': ...
}
NAMESPACES = {
    'glance': GLANCE_NAMESPACE_LIST,
    'ceilometer': CEILOMETER_NAMESPACE_LIST
}
ALLOWED_ATTRS = ['service_catalog', 'catalog']
FORBIDDEN_METHODS = [
    'add_hook', 'alternate_service_type', 'completion_cache', 'run_hooks',
    'write_to_completion_cache', 'model', 'build_key_only_query', 'build_url',
    'head', 'put'
]


def get_public_attrs(obj):
    all_attrs = dir(obj)

    return [a for a in all_attrs if not a.startswith('_')]


def get_public_methods(attr, client):
    hierarchy_list = attr.split('.')
    attribute = client

    for attr in hierarchy_list:
        attribute = getattr(attribute, attr)
    all_attributes_list = get_public_attrs(attribute)

    methods = []
    for a in all_attributes_list:
        allowed = a in ALLOWED_ATTRS
        forbidden = a in FORBIDDEN_METHODS

        if (not forbidden and
                (allowed or inspect.ismethod(getattr(attribute, a)))):
            methods.append(a)

    return methods


def get_manager_list(service_name, client):
    base_manager = BASE_MANAGERS[service_name]

    if not base_manager:
        return NAMESPACES[service_name]

    public_attrs = get_public_attrs(client)

    manager_list = []

    for attr in public_attrs:
        if (isinstance(getattr(client, attr), base_manager)
                or attr in ALLOWED_ATTRS):
            manager_list.append(attr)

    return manager_list


def get_mapping_for_service(service, client):
    mapping = collections.OrderedDict()
    for man in get_manager_list(service, client):
        public_methods = get_public_methods(man, client)
        for method in public_methods:
            key = "%s_%s" % (man, method)
            value = "%s.%s" % (man, method)
            mapping[key] = value

    return mapping


def print_mapping(mapping):
    print(json.dumps(mapping, indent=8, separators=(',', ': ')))


if __name__ == "__main__":
    args = get_parser().parse_args()

    auth_info = {
        'username': args.username,
        'tenant_name': args.tenant_name,
        'password': args.password,
        'auth_url': args.auth_url
    }

    service = args.service
    client = CLIENTS.get(service)(**auth_info)

    print("Find methods for service: %s..." % service)

    print_mapping(get_mapping_for_service(service, client))
