# Copyright (c) 2016 Nokia, Inc.
# 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 os
import sys

import click
import json
import pkg_resources
from requests import delete
from requests import get
from requests import post
from requests import put

from gluon.common import exception as exc
from gluon.particleGenerator.generator import load_model


def print_basic_usage(argv, model_list):
    print("Usage: %s --api <api_name> [OPTIONS] COMMAND[ARGS]..." %
          os.path.basename(argv[0]))
    print("\nOptions:")
    print("--api TEXT      Name of API, one of %s" % model_list)
    print("--port INTEGER  Port of endpoint (OS_PROTON_PORT)")
    print("--host TEXT     Host of endpoint (OS_PROTON_HOST)")
    print("--help          Show this message and exit.")


def get_api_model(argv, model_list):

    try:
        arg_idx = argv.index("--api")
        val_idx = arg_idx + 1
    except ValueError:
        # If there is only one API model, --api is not needed
        if len(model_list) == 1:
            return model_list[0]
        print("--api is not specified!\n")
        print_basic_usage(argv, model_list)
        sys.exit(-1)
    try:
        api_name = argv[val_idx]
    except IndexError:
        print("API name is not specified!\n")
        print_basic_usage(argv, model_list)
        sys.exit(-1)
    if api_name not in model_list:
        print("Invalid API name!\n")
        print_basic_usage(argv, model_list)
        sys.exit(-1)
    del argv[arg_idx]
    del argv[arg_idx]
    return api_name


def get_token():
    auth_url = os.environ.get('OS_AUTH_URL')
    tenant = os.environ.get('OS_TENANT_NAME')
    user = os.environ.get('OS_USERNAME')
    password = os.environ.get('OS_PASSWORD')

    if not auth_url or not tenant or not user or not password:
        return None

    auth_info = {'auth': {
        'tenantName': tenant,
        'passwordCredentials':
            {'username': user, 'password': password}
    }}

    headers = {'Accept': 'application/json',
               'Content-Type': 'application/json'}

    resp = post(auth_url + '/v2.0/tokens', json=auth_info, headers=headers)

    if resp.status_code != 200 and resp.status_code != 201:
        raise exc.GluonClientException('Bad return status %d'
                                       % resp.status_code,
                                       status_code=resp.status_code)

    json_resp = resp.json()

    return json_resp.get('access').get('token').get('id')


def json_get(url):
    headers = {'X-Auth-Token': get_token()}
    resp = get(url, headers=headers)

    if resp.status_code != 200:
        raise exc.GluonClientException('Bad return status %d'
                                       % resp.status_code,
                                       status_code=resp.status_code)
    try:
        rv = json.loads(resp.content)
    except Exception as e:
        raise exc.MalformedResponseBody(reason="JSON unreadable: %s on %s"
                                               % (e.args[0], resp.content))
    return rv


def do_delete(url):
    headers = {'X-Auth-Token': get_token()}
    resp = delete(url, headers=headers)

    if resp.status_code != 200 and resp.status_code != 204:
        raise exc.GluonClientException('Bad return status %d'
                                       % resp.status_code,
                                       status_code=resp.status_code)


def do_post(url, values):
    headers = {'X-Auth-Token': get_token()}
    resp = post(url, json=values, headers=headers)

    if resp.status_code != 200 and resp.status_code != 201:
        raise exc.GluonClientException('Bad return status %d'
                                       % resp.status_code,
                                       status_code=resp.status_code)
    try:
        rv = json.loads(resp.content)
    except Exception as e:
        raise exc.MalformedResponseBody(reason="JSON unreadable: %s on %s"
                                               % (e.args[0], resp.content))
    return rv


def do_put(url, values):
    headers = {'X-Auth-Token': get_token()}
    resp = put(url, json=values, headers=headers)

    if resp.status_code != 200:
        raise exc.GluonClientException('Bad return status %d'
                                       % resp.status_code,
                                       status_code=resp.status_code)
    try:
        rv = json.loads(resp.content)
    except Exception as e:
        raise exc.MalformedResponseBody(reason="JSON unreadable: %s on %s"
                                               % (e.args[0], resp.content))
    return rv


def make_url(host, port, *args):
    url = "http://%s:%d/proton" % (host, port)
    for arg in args:
        url = "%s/%s" % (url, arg)
    return url


def make_list_func(api_model, version, tablename):
    def list_func(**kwargs):
        url = make_url(kwargs["host"], kwargs["port"], api_model,
                       version, tablename)
        result = json_get(url)
        print(json.dumps(result, indent=4))
        return result

    return list_func


def make_show_func(api_model, version, tablename, primary_key):
    def show_func(**kwargs):
        url = make_url(kwargs["host"], kwargs["port"], api_model,
                       version, tablename, kwargs[primary_key])
        result = json_get(url)
        print(json.dumps(result, indent=4))
        return result

    return show_func


def make_create_func(api_model, version, tablename):
    def create_func(**kwargs):
        url = make_url(kwargs["host"], kwargs["port"], api_model,
                       version, tablename)
        del kwargs["host"]
        del kwargs["port"]
        data = {}
        for key, val in kwargs.items():
            if val is not None:
                data[key] = val
        result = do_post(url, data)
        print(json.dumps(result, indent=4))

    return create_func


def make_update_func(api_model, version, tablename, primary_key):
    def update_func(**kwargs):
        url = make_url(kwargs["host"], kwargs["port"], api_model,
                       version, tablename, kwargs[primary_key])
        del kwargs["host"]
        del kwargs["port"]
        del kwargs[primary_key]
        data = {}
        for key, val in kwargs.items():
            if val is not None:
                data[key] = val
        result = do_put(url, data)
        print(json.dumps(result, indent=4))

    return update_func


def make_delete_func(api_model, version, tablename, primary_key):
    def delete_func(**kwargs):
        url = make_url(kwargs["host"], kwargs["port"], api_model,
                       version, tablename, kwargs[primary_key])
        do_delete(url)

    return delete_func


def get_version(model):
    version = str(model['info']['version'])
    version = 'v' + version
    return version


def get_primary_key(table_data):
    primary = []
    for k, v in table_data['attributes'].items():
        if 'primary' in v:
            primary = k
            break
    # If not specified, a UUID is used as the PK
    if not primary:
        table_data['attributes']['uuid'] = \
            dict(type='string', length=36, primary=True, required=True)
        primary = 'uuid'
    table_data['primary'] = primary
    return primary


def set_type(kwargs, col_desc):
    if col_desc['type'] == 'string':
        pass
    elif col_desc['type'] == 'integer':
        kwargs["type"] = int
    elif col_desc['type'] == 'number':
        kwargs["type"] = float
    elif col_desc['type'] == 'boolean':
        kwargs["type"] = bool
    elif col_desc['type'] == 'enum':
        kwargs["type"] = click.Choice(col_desc['values'])
    else:
        raise Exception('Unknown column type %s' % col_desc['type'])


def proc_model(cli, package_name="unknown",
               model_dir="unknown",
               api_model="unknown",
               hostenv="unknown",
               portenv="unknown",
               hostdefault="unknown",
               portdefault=0):
    # print("loading model")
    model = load_model(package_name, model_dir, api_model)
    version = get_version(model)
    for table_name, table_data in model['api_objects'].items():
        get_primary_key(table_data)
    for table_name, table_data in model['api_objects'].items():
        try:
            attrs = {}
            for col_name, col_desc in table_data['attributes'].items():
                try:
                    # Step 1: deal with object xrefs
                    if col_desc['type'] in model['api_objects']:
                        # If referencing another object,
                        # get the type of its primary key
                        tgt_name = col_desc['type']
                        tgt_data = model['api_objects'][tgt_name]
                        primary_col = tgt_data['primary']
                        table_data["attributes"][col_name]['type'] = \
                            tgt_data["attributes"][primary_col]["type"]
                    # Step 2: convert our special types to ones a CLI likes
                    if col_desc['type'] == 'uuid':
                        # UUIDs, from a CLI perspective,  are a form of
                        # string
                        table_data["attributes"][col_name]['type'] = 'string'
                        table_data["attributes"][col_name]['length'] = 64
                    if col_desc.get('primary', False):
                        attrs['_primary_key'] = col_name
                except Exception:
                    print('During processing of attribute ', col_name)
                    raise
            if '_primary_key' not in attrs:
                raise Exception("One and only one primary key has to "
                                "be given to each column")
            attrs['__tablename__'] = table_data['api']['plural_name']

            # chop off training 's'
            attrs['__objname__'] = table_data['api']['name']
            #
            # Create CRUD commands for the table
            #
            hosthelp = "Host of endpoint (%s) " % hostenv
            porthelp = "Port of endpoint (%s) " % portenv
            list = make_list_func(api_model, version, attrs['__tablename__'])
            list.func_name = "%s-list" % (attrs['__objname__'])
            list = click.option("--host", envvar=hostenv,
                                default=hostdefault, help=hosthelp)(list)
            list = click.option("--port", envvar=portenv,
                                default=portdefault, help=porthelp)(list)
            cli.command()(list)

            show = make_show_func(api_model, version, attrs['__tablename__'],
                                  attrs['_primary_key'])
            show.func_name = "%s-show" % (attrs['__objname__'])
            show = click.option("--host", envvar=hostenv,
                                default=hostdefault, help=hosthelp)(show)
            show = click.option("--port", envvar=portenv,
                                default=portdefault, help=porthelp)(show)
            show = click.argument(attrs['_primary_key'])(show)
            cli.command()(show)

            create = make_create_func(api_model, version,
                                      attrs['__tablename__'])
            create.func_name = "%s-create" % (attrs['__objname__'])
            create = click.option("--host", envvar=hostenv,
                                  default=hostdefault, help=hosthelp)(create)
            create = click.option("--port", envvar=portenv,
                                  default=portdefault, help=porthelp)(create)
            for col_name, col_desc in table_data['attributes'].items():
                kwargs = {}
                option_name = "--" + col_name
                kwargs["default"] = None
                required = col_desc.get('required', False)
                kwargs["help"] = col_desc.get('description', "no description")
                if required:
                    kwargs["required"] = True
                set_type(kwargs, col_desc)
                create = click.option(option_name, **kwargs)(create)
            cli.command()(create)

            update = make_update_func(api_model, version,
                                      attrs['__tablename__'],
                                      attrs['_primary_key'])
            update.func_name = "%s-update" % (attrs['__objname__'])
            update = click.option("--host", envvar=hostenv,
                                  default=hostdefault, help=hosthelp)(update)
            update = click.option("--port", envvar=portenv,
                                  default=portdefault, help=porthelp)(update)
            for col_name, col_desc in table_data['attributes'].items():
                if col_name == attrs['_primary_key']:
                    continue
                kwargs = {}
                option_name = "--" + col_name
                kwargs["default"] = None
                kwargs["help"] = col_desc.get('description', "no description")
                set_type(kwargs, col_desc)
                update = click.option(option_name, **kwargs)(update)
            update = click.argument(attrs['_primary_key'])(update)
            cli.command()(update)

            del_func = make_delete_func(api_model, version,
                                        attrs['__tablename__'],
                                        attrs['_primary_key'])
            del_func.func_name = "%s-delete" % (attrs['__objname__'])
            del_func = click.option("--host", envvar=hostenv,
                                    default=hostdefault,
                                    help=hosthelp)(del_func)
            del_func = click.option("--port", envvar=portenv,
                                    default=portdefault,
                                    help=porthelp)(del_func)
            del_func = click.argument(attrs['_primary_key'])(del_func)
            cli.command()(del_func)

        except Exception:
            print('During processing of table ', table_name)
            raise
