#!/usr/bin/env python
# -*- coding: utf-8 -*-
import ConfigParser
from os import environ as env

# for reusing the module docstring
docstring = 'Execute CircleCI REST API commands from the CLI.'
__doc__ = docstring

SECTION_NAME = 'circlecli'
ENV_TOKEN_NAME = 'CIRCLE_TOKEN'


def read_config(config_file):
    """Load saved configuration parameters.

    Args:
        config_file (str): the path to the configuration file

    Returns:
        (dict) the configuration values
    """
    cp = ConfigParser.SafeConfigParser()
    cp.read(config_file)

    # build the config object
    config = {}
    try:
        [config.setdefault(k, v) for k, v in cp.items(SECTION_NAME)]
    except ConfigParser.NoSectionError:
        pass

    return config


def init_config(config_path):
    """Initialize configuration or create new one."""
    config = read_config(config_path)

    # environment variable overrides
    if env.get(ENV_TOKEN_NAME):
        config['token'] = env[ENV_TOKEN_NAME]

    if not config.get('token'):
        # read new token
        print("Enter your CircleCI API token at the prompt (https://circleci.com/account/api).")
        config['token'] = raw_input("Token: ").strip()

        # ask other questions since we're here
        if not config.get('default_username'):
            print("")
            print("A default username ensures you don't have to specify one for every command.")
            print("(e.g., /api/v1/project/:default_username/:project)")
            config['default_username'] = raw_input("Default username (leave blank for none): ").strip()

            # write new config
            config_fp = open(config_path, 'w')
            cp = ConfigParser.SafeConfigParser()
            cp.add_section(SECTION_NAME)
            cp.set(SECTION_NAME, 'token', config['token'])
            cp.set(SECTION_NAME, 'default_username', config['default_username'])
            cp.write(config_fp)
            config_fp.close()

    return config


if __name__ == "__main__":
    import argparse
    import sys
    from collections import OrderedDict
    from os import path as op

    from circlecli import CircleAPI, validate_circle_yml

    # read the config
    config = init_config(op.join(op.expanduser("~"), '.circlecli.ini'))

    # parse command line args
    parser = argparse.ArgumentParser(description=docstring,
                                     formatter_class=argparse.RawTextHelpFormatter,
                                     add_help=False)
    parser.add_argument('action', action='store', nargs='?',
                        help="An action to perform (see below)")
    parser.add_argument('help', nargs='?', help="Get help on a particular action")
    parser.add_argument('--help', '-h', dest='opt_help', action='store_true',
                        help="Display this help text")
    parser.add_argument('--project', '-p', dest='project', action='store',
                        help="The target project name")
    parser.add_argument('--username', '-u', dest='username', action='store',
                        default=config.get('default_username'),
                        help="The username of the project/repo owner")
    parser.add_argument('--build-num', '-b', dest='build_num', action='store',
                        type=int, help="The target build number")
    parser.add_argument('--config', '-c', dest='config', action='store_true',
                        default=False, help="Only print saved configuration values")
    parser.add_argument('--quiet', '-q', dest='quiet', action='store_true',
                        default=False, help="Suppress output")
    parser.add_argument('--set', '-s', dest='envvars', action='append',
                        default=[], help="Set a variable (used with 'env' action)")
    parser.add_argument('--filter', '-f', dest='filters', action='append',
                        default=[], help="Match only response objects matching filter")
    parser.add_argument('--verbose', '-v', dest='verbose', action='store_true',
                        default=False, help="Return original full output from CircleCI")

    args = parser.parse_args()

    def print_full_help():
        """Display the full parser help text, plus command help text."""
        parser.print_help()
        print_action_help(
            """
available actions:
  me\t\t{}
  projects\t{}
  builds\t{}
  artifacts\t{}
  retry\t\t{}
  cancel\t{}
  clear-cache\t{}
  env\t\t{}
  check\t\t{}
  <cmd> help\tDisplay help text for a particular action
            """.format(
                circle.me.__doc__.split('\n')[0],
                circle.projects.__doc__.split('\n')[0],
                circle.builds.__doc__.split('\n')[0],
                circle.artifacts.__doc__.split('\n')[0],
                circle.retry_build.__doc__.split('\n')[0],
                circle.cancel_build.__doc__.split('\n')[0],
                circle.clear_cache.__doc__.split('\n')[0],
                circle.envvar.__doc__.split('\n')[0],
                validate_circle_yml.__doc__.split('\n')[0]
            )
        )
        sys.exit(0)

    def build_filters(filters):
        """Build a dict of provided filters."""
        new_filters = {}
        for f in filters:
            if "=" in f:
                name, value = f.split("=")
                new_filters[name] = value
        return new_filters

    # set the filters
    filters = build_filters(args.filters)

    def next_action(arg, next_arg=None):
        """Check if the next arg matches a given action."""
        idx = sys.argv.index(arg)
        next_idx = idx + 1
        if len(sys.argv) < (next_idx + 1):
            return False
        if next_arg is None:
            return sys.argv[next_idx]  # just return the detected next arg
        return next_arg if sys.argv[next_idx] == next_arg else False

    def print_action_help(summary, kwargs={}, examples={}):
        """Print a help message for a particular action."""
        msgs = [summary, '']
        [msgs.append({k: v}) for k, v in kwargs.iteritems()]
        for msg in msgs:
            if not isinstance(msg, dict):
                print(msg)
            else:
                for k, v in msg.iteritems():
                    print("{}: {}".format(k, v))

        if examples:
            print("")
            print("EXAMPLES")
            for title, ex in examples.iteritems():
                print("{}:".format(title))
                print("\t{}".format(ex))
                print("")

    # instantiate the CircleAPI class
    circle = CircleAPI(config['token'])

    if args.config or args.action == 'config':
        for k, v in config.iteritems():
            print("{} = {}".format(k, v))
        sys.exit(0)

    # help action
    if args.opt_help or args.action == 'help':
        print_full_help()

    # required args
    if not args.action:
        parser.error("you must specify an action")

    if args.action == 'check':
        filepath = next_action('check')
        if not filepath or filepath == 'help':
            help_args = OrderedDict()
            help_args['  <file>'] = "\tFilepath to a circle.yml file to validate"
            help_args['  --quiet|-q'] = "\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['Ensure a circle.yml file is valid'] = "circlecli check /home/foo/projects/bar/circle.yml"
            print_action_help(validate_circle_yml.__doc__.split('\n')[0], help_args, ex_args)
        else:
            try:
                validate_circle_yml(filepath)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(1)
    elif args.action == 'me':
        if next_action('me', 'help'):
            help_args = OrderedDict()
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['Print information about your account'] = "circlecli me"
            print_action_help(circle.me.__doc__.split('\n')[0], help_args, ex_args)
        else:
            try:
                res = circle.me(args.verbose)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(2)
            if not args.verbose:
                for k, v in res.iteritems():
                    print("{}: {}".format(k, v))
            else:
                print(res)
    elif args.action == 'projects':
        if next_action('projects', 'help'):
            help_args = OrderedDict()
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --filter|-f KEY=VAL'] = "\t(optional) Filter by key/value pair (can include multiple)"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['List projects followed by your account'] = "circlecli projects"
            ex_args['List only projects that build on OSX'] = "circlecli projects -f feature_flags.osx=true"
            print_action_help(circle.projects.__doc__.split('\n')[0], help_args, ex_args)
        else:
            try:
                res = circle.projects(args.verbose, filters=filters)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(3)
            if not args.verbose:
                for k in res:
                    print(k)
            else:
                print(res)
    elif args.action == 'builds':
        if next_action('builds', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\t(optional) A project to get build info"
            help_args['  --build_num|-b <int>'] = "(optional) A build number to get specific build details"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --filter|-f KEY=VAL'] = "\t(optional) Filter by key/value pair (can include multiple)"

            ex_args = OrderedDict()
            ex_args['List builds for the account'] = "circlecli builds"
            ex_args['List only successful builds'] = "circlecli builds -f status=success"
            ex_args['List builds for a project'] = "circlecli builds -u foo_user -p foo_project"
            ex_args['List only successful builds for master branch (w/ default user)'] = "circlecli builds -p foo_project -f branch=master -f status=success"
            ex_args["List a specific build's details"] = "circlecli builds -u foo_user -p foo_project -b 14"
            print_action_help(circle.builds.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if args.project and not args.username:
                parser.error("username (-u) is required")
            try:
                res = circle.builds(username=args.username, project=args.project,
                                    build_num=args.build_num, verbose=args.verbose,
                                    filters=filters)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(4)
            if not args.verbose:
                for build in res:
                    for k, v in build.iteritems():
                        print("{}: {}".format(k, v))
                    print("")
            else:
                print(res)
    elif args.action == 'artifacts':
        if next_action('artifacts', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\t(optional) A project to get build info"
            help_args['  --build_num|-b <int>'] = "(optional) A build number to get artifacts"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --filter|-f KEY=VAL'] = "\t(optional) Filter by key/value pair (can include multiple)"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['List artifacts for a build'] = "circlecli artifacts -u foo_user -p foo_project -b 14"
            print_action_help(circle.artifacts.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if args.project and not args.username:
                parser.error("username (-u) is required")
            if not args.build_num:
                parser.error("build-num (-b) is required")
            try:
                res = circle.artifacts(username=args.username, project=args.project,
                                       build_num=args.build_num, verbose=args.verbose,
                                       filters=filters)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(5)
            if not args.verbose:
                for k in res:
                    print(k)
            else:
                print(res)
    elif args.action == 'retry':
        if next_action('retry', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\t(optional) A project to get build info"
            help_args['  --build_num|-b <int>'] = "(optional) A build number to retry"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --filter|-f KEY=VAL'] = "\t(optional) Filter by key/value pair (can include multiple)"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['Retry a build'] = "circlecli retry -u foo_user -p foo_project -b 14"
            print_action_help(circle.retry_build.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if args.project and not args.username:
                parser.error("username (-u) is required")
            if not args.build_num:
                parser.error("build-num (-b) is required")
            try:
                res = circle.retry_build(username=args.username, project=args.project,
                                         build_num=args.build_num, verbose=args.verbose,
                                         filters=filters)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(6)
            if not args.verbose:
                for k, v in res.iteritems():
                    print("{}: {}".format(k, v))
            else:
                print(res)
    elif args.action == 'cancel':
        if next_action('cancel', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\t(optional) A project to get build info"
            help_args['  --build_num|-b <int>'] = "(optional) A build number to cancel"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --filter|-f KEY=VAL'] = "\t(optional) Filter by key/value pair (can include multiple)"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args['Cancel a build'] = "circlecli cancel -u foo_user -p foo_project -b 14"
            print_action_help(circle.cancel_build.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if args.project and not args.username:
                parser.error("username (-u) is required")
            if not args.build_num:
                parser.error("build-num (-b) is required")
            try:
                res = circle.cancel_build(username=args.username, project=args.project,
                                          build_num=args.build_num, verbose=args.verbose,
                                          filters=filters)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(7)
            if not args.verbose:
                for k, v in res.iteritems():
                    print("{}: {}".format(k, v))
            else:
                print(res)
    elif args.action == 'clear-cache':
        if next_action('clear-cache', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\t(optional) The project's cache to clear"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args["Clear a project's cache"] = "circlecli clear-cache -u foo_user -p foo_project"
            print_action_help(circle.clear_cache.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if args.project and not args.username:
                parser.error("username (-u) is required")
            try:
                res = circle.clear_cache(username=args.username, project=args.project,
                                         verbose=args.verbose)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(8)
            if not args.verbose:
                for k, v in res.iteritems():
                    print("{}: {}".format(k, v))
            else:
                print(res)
    elif args.action == 'env':
        if next_action('env', 'help'):
            help_args = OrderedDict()
            help_args['  --username|-u <str>'] = "\tThe target username (or default if not provided)"
            help_args['  --project|-p <str>'] = "\tThe project to list or set environment variables"
            help_args['  --verbose|-v'] = "\t(optional) Print original full output from CircleCI"
            help_args['  --set|-s KEY=VAL'] = "\t(optional) Set a key/value pair (can include multiple)"
            help_args['  --quiet|-q'] = "\t\t(optional) Suppress output"

            ex_args = OrderedDict()
            ex_args["List a project's environment variables"] = "circlecli env -u foo_user -p foo_project"
            ex_args["Set two environment variables"] = "circlecli env -u foo_user -p foo_project -s FOO=BAR -s BAZ=QUX"
            print_action_help(circle.envvar.__doc__.split('\n')[0], help_args, ex_args)
        else:
            if not args.project:
                parser.error("project (-p) is required")
            if args.project and not args.username:
                parser.error("username (-u) is required")
            envvars = {}
            for envvar in args.envvars:
                if "=" in envvar:
                    name, value = envvar.split("=")
                    envvars[name] = value
            try:
                res = circle.envvar(username=args.username, project=args.project,
                                    verbose=args.verbose, **envvars)
            except Exception as e:
                if not args.quiet:
                    print(e.message)
                sys.exit(9)
            if not args.verbose:
                for var in res:
                    print(var)
            else:
                print(res)
    else:
        print("Unknown action: {}".format(args.action))