#!/usr/bin/env python
# Copyright (c) 2019, Djaodjin Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
Command-line utillity to deploy to djaodjin
"""

import argparse, configparser, inspect, json, logging, os, sys

import requests

from deployutils import __version__
from deployutils.copy import download, upload, upload_theme


LOGGER = logging.getLogger(__name__)
CONFIG = configparser.ConfigParser()
CONFIG_FILENAME = None


def build_subcommands_parser(parser, module):
    """
    Returns a parser for the subcommands defined in the *module*
    (i.e. commands starting with a 'pub_' prefix).
    """
    mdefs = module.__dict__
    keys = list(mdefs.keys())
    keys.sort()
    subparsers = parser.add_subparsers(help='sub-command help')
    for command in keys:
        if command.startswith('pub_'):
            func = module.__dict__[command]
            parser = subparsers.add_parser(command[4:], help=func.__doc__)
            parser.set_defaults(func=func)
            argspec = inspect.signature(func)
            positionals = []
            short_opts = set([])
            for arg in argspec.parameters.values():
                if arg.default == inspect.Parameter.empty:
                    positionals += [arg]
                else:
                    param_name = arg.name.replace('_', '-')
                    short_opt = param_name[0]
                    if not (param_name.startswith('no') or
                        (short_opt in short_opts)):
                        opts = ['-%s' % short_opt, '--%s' % param_name]
                    else:
                        opts = ['--%s' % param_name]
                    short_opts |= set([short_opt])
                    if isinstance(arg.default, list):
                        parser.add_argument(*opts, action='append')
                    elif isinstance(arg.default, dict):
                        parser.add_argument(*opts, type=json.loads)
                    elif arg.default is False:
                        parser.add_argument(*opts, action='store_true')
                    elif arg.default is not None:
                        parser.add_argument(*opts, default=arg.default)
                    else:
                        parser.add_argument(*opts)
            if positionals:
                for arg in positionals[:-1]:
                    parser.add_argument(arg.name)
                parser.add_argument(positionals[-1].name, nargs='*')


def filter_subcommand_args(func, options):
    """
    Filter out all options which are not part of the function *func*
    prototype and returns a set that can be used as kwargs for calling func.
    """
    kwargs = {}
    sig = inspect.signature(func)
    for arg in sig.parameters.values():
        if arg.name in options:
            kwargs.update({arg.name: getattr(options, arg.name)})
    return kwargs


def get_project_config(base_url="", api_key="", project=""):
    if not project:
        project = os.path.basename(os.getcwd())
    if not base_url:
        base_url = CONFIG.get(project, 'base_url')
    if not base_url:
        domain = input("Please enter the domain for this project"\
            " (ex: %s.djaoapp.com):" % project)
        base_url = "https://%s" % domain
    if not api_key:
        api_key = CONFIG.get(project, 'api_key')
    if not api_key:
        api_key = input("Please enter the API Key to access %s:" % project)
    return base_url, api_key, project

def pub_deploy(args, base_url="", api_key="", project=""):
    """Deploy container for a project.
    """
    base_url, api_key, _ = get_project_config(
        base_url="https://djaodjin.com", api_key=api_key, project="djaodjin")
    _, _, project = get_project_config(
        base_url=base_url, api_key=api_key, project=project)
    api_container_url = \
        "%(base_url)s/api/containers/%(organization)s/apps/%(app)s/" % {
            'base_url': base_url,
            'organization': str(project),
            'app': str(project)}
    resp = requests.post(api_container_url, auth=(api_key, ""))
    LOGGER.info("POST %s returns %d %s",
            api_container_url, resp.status_code, resp.text)


def pub_download(args, location=None, prefix=""):
    """Download theme packages from the stage *location*.
    """
    download(location, remotes=args, prefix=prefix)


def pub_init(args, base_url="", api_key="", project=""):
    """Initialize the API key used to upload project theme and
assets.
    """
    base_url, api_key, project = get_project_config(
        base_url=base_url, api_key=api_key, project=project)
    CONFIG.add_section(project)
    CONFIG.set(project, 'api_key', api_key)
    CONFIG.set(project, 'base_url', base_url)
    if not os.path.exists(os.path.dirname(CONFIG_FILENAME)):
        os.makedirs(os.path.dirname(CONFIG_FILENAME))
    with open(CONFIG_FILENAME, 'w') as configfile:
        CONFIG.write(configfile)
    sys.stdout.write("saved configuration in %s\n" % CONFIG_FILENAME)


def pub_upload(args, base_url="", api_key="", project=""):
    """Upload a theme package for a project.
    """
    base_url, api_key, project = get_project_config(
        base_url=base_url, api_key=api_key, project=project)
    upload_theme(args, base_url, api_key, prefix=project)


def main(args):
    """
    Main Entry Point
    """
    global CONFIG_FILENAME
    try:
        import __main__
        parser = argparse.ArgumentParser(
            usage='%(prog)s [options] command\n\nVersion\n  %(prog)s version '
            + str(__version__),
            formatter_class=argparse.RawTextHelpFormatter)
        parser.add_argument('--version', action='version',
                            version='%(prog)s ' + str(__version__))
        parser.add_argument(
            '--config', action='store',
            default=os.path.join(os.getenv('HOME'), '.djd', 'credentials'),
            help='configuration file')
        build_subcommands_parser(parser, __main__)

        if len(args) <= 1:
            parser.print_help()
            return 1

        options = parser.parse_args(args[1:])
        CONFIG_FILENAME = options.config
        parsed_filenames = CONFIG.read(CONFIG_FILENAME)
        LOGGER.info("read configuration from %s", ",".join(parsed_filenames))
        for section in CONFIG.sections():
            LOGGER.info("[%s]", section)
            for key, val in CONFIG.items(section):
                LOGGER.info("%s = %s", key, val)

        # Filter out options with are not part of the function prototype.
        func_args = filter_subcommand_args(options.func, options)
        return options.func(**func_args)

    except RuntimeError as err:
        LOGGER.error(err)
        return err.code


if __name__ == '__main__':
    logging.basicConfig(level='INFO')
    main(sys.argv)
