#!/usr/bin/env python

import argparse
import uuid
import requests
import getpass
import sys

from subprocess import Popen, PIPE
from distutils import spawn

BROKER_IMAGE = 'ansibleapp/ansible-service-broker-ansibleapp'

BROKER_HEADERS = {
    'X-Broker-API-Version': '2.9'
}

AVAILABLE_COMMANDS = {
    'connect': 'Connect to a broker to interact',
    'up': 'Bring up a broker in a target cluster'
}


def req(url, **kwargs):
    # merge in broker headers if provided
    headers = BROKER_HEADERS
    if 'headers' in kwargs:
        headers = kwargs['headers']
        headers.update(BROKER_HEADERS)
    kwargs['headers'] = headers

    verb = kwargs.pop('verb', 'get')

    return getattr(requests, verb)(url, **kwargs)


class App(object):
    def __init__(self, broker_address):
        self.broker_address = broker_address
        self.catalog = None
        self.opts = {
                'b': ('bootstrap', self._cmd_bootstrap),
                'l': ('list services', self._cmd_list_services),
                'p': ('provision service', self._cmd_provision),
                '?': ('show menu', self._cmd_show_menu)
        }

    def run(self):
        opt = ''

        self._cmd_show_menu()
        while opt != 'q':
            opt = raw_input("# ")

            if opt == 'q':
                break

            if opt not in self.opts:
                print 'ERROR: Unknown option!'
                continue

            should_quit = self.opts[opt][1]()
            if should_quit:
                break

        print 'Quitting ansibleapp client!'

    def _cmd_show_menu(self):
        print '============================================================'
        print "Select an option ('q' to quit):"
        print '============================================================'
        for k in self.opts:
            print '  %s | %s' % (k, self.opts[k][0])

    def _cmd_bootstrap(self):
        print 'Bootstrapping broker...'
        self._req_bootstrap()
        return False

    def _cmd_list_services(self):
        print 'Available services:'

        if not self.catalog:
            print ('NOTE: No services loaded in client,'
                   ' requesting catalog from broker...')
            self._req_catalog()

        self._print_service_options()
        return False

    def _cmd_provision(self):
        if not self.catalog:
            print ('No services loaded in client,'
                   ' requesting catalog from broker...')
            self._req_catalog()

        opt = None
        valid_choices = [service['num'] for service in self.catalog]
        while True:
            print 'Select a service to provision:'
            self._print_service_options()
            opt = int(raw_input('# '))  # TODO: Do this better.
            if int(opt) not in valid_choices:
                print 'ERROR: Invalid choice!'
            else:
                break
        catalog_entry = self.catalog[opt - 1]
        service = catalog_entry['service']
        print '[ %s ] selected for provisioning...' % catalog_entry['name']
        answers = self._get_answers(service)
        print 'Service configured! Requesting broker to provision...'
        res = self._req_provision(service['id'], answers)
        print 'Provision returned status code: %s' % res.status_code

        if res.status_code != 201 or res.status_code == 200:
            print 'ERROR: Got a non-2xx code back from the broker...'
        else:
            print 'Broker reported provisioning success!'
            return True  # tell app loop to quit

    def _get_answers(self, service):
        print 'Configure your service:'

        answers = {}
        for param in service['metadata']['parameters']:
            answer = None
            while True:
                supports_default = param['default'] is not None

                # print "name: %s" % param['name']
                # print "required: %s" % param['required']
                # print "supports_default: %s" % supports_default

                if param['required'] is False and not supports_default:
                    query = '%s (generated if blank) # ' % param['name']
                elif supports_default:
                    query = '%s (default: %s) # ' % \
                        (param['name'], param['default'])
                else:
                    query = '%s # ' % param['name']
                answer = raw_input(query)

                # TODO: This is fugly. Fix it.
                should_pass = answer == '' and param['required'] is False \
                    and not supports_default

                if should_pass:
                    pass
                elif answer == '' and not supports_default:
                    print 'ERROR: Must provide an answer for this parameter'
                    continue
                elif answer == '' and supports_default:
                    answer = param['default']

                answers[param['name']] = answer
                break

        _answers = answers.copy()
        for k, v in answers.iteritems():
            if v == '':
                del _answers[k]

        return _answers

    def _req_bootstrap(self):
        res = req(self._url('/bootstrap'), verb='post')
        spec_count = res.json()['SpecCount']
        print 'Bootstrapped %d specs into broker from dockerhub!' % spec_count

    def _req_catalog(self):
        res = req(self._url('/catalog'))
        self.catalog = [{
            'num': idx + 1,
            'name': service['name'],
            'id': service['id'],
            'service': service,
        } for idx, service in enumerate(res.json()['services'])]
        print 'Loaded %d services from broker catalog:' % len(self.catalog)

############################################################
# 6 instanceUUID="688eea24-9cf9-43e3-9942-d1863b2a16af"
# 7 planUUID="4c10ff42-be89-420a-9bab-27a9bef9aed8"
# 9 serviceUUID="4240b39c-116b-40b9-9b77-2d30e229f0dc"
# 10
# 11 req="{
# 12   \"plan_id\": \"$planUUID\",
# 13   \"service_id\": \"$serviceUUID\",
# 14   \"parameters\": {
# 15     \"MYSQL_USER\": \"username\"
# 16   }
# 17 }"
# 18
# 19 curl \
# 20   -X PUT \
# 21   -H 'X-Broker-API-Version: 2.9' \
# 22   -H 'Content-Type: application/json' \
# 23   -d "$req" \
# 24   -v \
# 25   http://cap.example.com:1338/v2/service_instances/$instanceUUID
############################################################
    def _req_provision(self, service_id, answers):
        # Generate a uuid instance ID acting as a Service Catalog would do
        instance_id = str(uuid.uuid4())
        plan_id = str(uuid.uuid4())  # not clear how this is used yet, req tho
        path = '/service_instances/%s' % instance_id
        post_body = {
            'plan_id': plan_id,
            'service_id': service_id,
            'parameters': answers
        }
        return req(self._url(path), verb='put', json=post_body)

    def _print_service_options(self):
        if self.catalog:
            for service in self.catalog:
                print '  %d | %s' % (service['num'], service['name'])
        else:
            print 'No available ansibleapps found!'

    def _url(self, path):
        return 'http://%s/v2%s' % (self.broker_address, path)


def subcmd_connect_parser(parser, subcmd):
    subcmd.add_argument(
        'broker_address', action='store',
        help=u'Service Broker address <host>:<port>, ex: localhost:1338'
    )


def subcmd_up_parser(parser, subcmd):
    subcmd.add_argument(
        'cluster_address', action='store',
        help=u'Address of the cluster where the broker will be deployed'
    )
    subcmd.add_argument(
        '--cluster-user', action='store', dest='openshift_user',
        help=u'Cluster user that will be used to deploy the broker',
        required=True
    )
    subcmd.add_argument(
        '--cluster-pass', action='store', dest='openshift_pass',
        help=u'Cluster password that will be used to deploy the broker'
    )
    subcmd.add_argument(
        '--dockerhub-user', action='store', dest='dockerhub_user',
        help=u'Dockerhub user. Used to communicate with dockerhub registry.',
        required=True
    )
    subcmd.add_argument(
        '--dockerhub-pass', action='store', dest='dockerhub_pass',
        help=u'Dockerhub pass. Used to communicate with dockerhub registry.',
    )


def cmdrun_connect(**kwargs):
    print "cmdrun_connect"
    App(kwargs['broker_address']).run()


def cmdrun_up(**kwargs):
    cluster = kwargs['cluster_address']
    openshift_user = kwargs['openshift_user']
    openshift_pass = kwargs['openshift_pass']
    dockerhub_user = kwargs['dockerhub_user']
    dockerhub_pass = kwargs['dockerhub_pass']

    if not openshift_pass:
        openshift_pass = getpass.getpass('Enter cluster password: ')
    if not dockerhub_pass:
        dockerhub_pass = getpass.getpass('Enter dockerhub password: ')

    docker_bin = spawn.find_executable('docker')
    if not docker_bin:
        raise Exception('ERROR: Could not find docker executable.')

    # TODO: Right now, the broker will provision to the same cluster that it
    # is deployed to. Could make this configurable.
    brokerup_cmd = [
        docker_bin, 'run',
        '-e', 'OPENSHIFT_TARGET=%s' % cluster,
        '-e', 'OPENSHIFT_USER=%s' % openshift_user,
        '-e', 'OPENSHIFT_PASS=%s' % openshift_pass,
        BROKER_IMAGE, 'provision',
        '-e', 'dockerhub_user=%s' % dockerhub_user,
        '-e', 'dockerhub_pass=%s' % dockerhub_pass,
        '-e', 'openshift_target=%s' % cluster,
        '-e', 'openshift_user=%s' % openshift_user,
        '-e', 'openshift_pass=%s' % openshift_pass,
    ]

    brokerup_p = Popen(brokerup_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    output, err = brokerup_p.communicate()

    if err:
        print "ERROR: Something went wrong trying to bring up a broker"
        raise Exception(err)

    print output


def main():
    parser = argparse.ArgumentParser(
            description=u'Utility acting as a ServiceCatalog+Client for'
            u' driving an ansible-service-broker')
    subparsers = parser.add_subparsers(title='subcommand', dest='subcommand')
    subparsers.required = True

    for subcommand in AVAILABLE_COMMANDS:
        subparser = subparsers.add_parser(
            subcommand, help=AVAILABLE_COMMANDS[subcommand]
        )
        globals()['subcmd_%s_parser' % subcommand](parser, subparser)

    args = parser.parse_args()

    try:
        globals()['cmdrun_%s' % args.subcommand](**vars(args))
    except Exception as e:
        print 'Exception occurred! %s' % e
        sys.exit(1)


if __name__ == '__main__':
    main()
