#!/usr/bin/env python

""" Volt Grid Client """

import argparse
import os
import sys
import json
from requests.exceptions import ConnectionError
from voltgrid.client import Client
from voltgrid.error import ClientError


EXIT_ERROR = 128

try:
    import configparser
except ImportError:
    import ConfigParser as configparser  # Py 2.x


def find_config_file():
    for location in os.curdir, os.path.expanduser('~'), os.environ.get('VOLTGRID_CFG', ''):
        if os.path.exists(os.path.join(location, '.voltgrid.cfg')):
            return location
    return False


class APIClient(Client):
    def __init__(self):
        config = configparser.ConfigParser()

        cfg_file = os.path.join(find_config_file(), '.voltgrid.cfg')
        config.read(cfg_file)

        auth = {'username': config.get('voltgrid', 'username'),
                'password': config.get('voltgrid', 'password')}

        try:
            base_url = config.get('voltgrid', 'api_host')   # fallback can't be used with py 2.x
        except configparser.NoOptionError:
            base_url = 'https://api.voltgrid.com'

        options = {
            'base': base_url,
            'api_version': 'api/v1'}
        super(APIClient, self).__init__(auth=auth, options=options)

api = APIClient()


class ResultFormatter(object):
    """ Configurable output formatter """
    RESPONSE = {
        200: 'OK',
        201: 'Created',
        202: 'Accepted',
        204: 'Done',
    }

    def __init__(self, title, result, fields=None, verbose=False, json=False):
        self.title = title
        self.title_formatted = str.center(' ' + title + ' ', 40, '*')
        self.result = result
        self.fields = fields
        self.verbose = verbose
        self.json = json

    def _print_json(self, results):
        output = json.dumps(results, sort_keys=True, indent=4)
        print(output)

    def _print_item(self, item, fields):
        if fields is None:
            for k, v in sorted(item.items()):
                print('%s: %s' % (k, v))
        else:
            output = str()
            for f in fields:
                output += '%s ' % item[f]
            print(output)

    def _print_title(self):
        print('*' * len(self.title_formatted))
        print(self.title_formatted)
        print('*' * len(self.title_formatted))

    def _print_separator(self):
        print('*' * len(self.title_formatted))

    def _print_status(self):
        print("OK: %s (%s)" % (self.RESPONSE[self.result.code], self.result.code))

    def _print_verbose(self, results):
        self._print_title()
        if results is None or len(results) is 0:
            print('No results found')
            self._print_separator()
        elif isinstance(results, str):  # simple text response
            if results != '':
                print(results)
            else:
                print('No result returned')
            self._print_separator()
        elif isinstance(results, (list, tuple)):  # result list
            for r in results:
                self._print_item(r, fields=None)
                self._print_separator()
        else:  # result dict
            self._print_item(results, fields=None)
        self._print_status()

    def _print_regular(self, results):
        if results is None or len(results) is 0:
            self._print_status()
        elif isinstance(results, str):  # simple text response
            if results != '':
                print(results)
        elif isinstance(results, (list, tuple)):  # result list
            for r in results:
                self._print_item(r, fields=self.fields)
        else:  # result dict
            self._print_item(results, fields=self.fields)

    def output(self):
        if isinstance(self.result.body, str):  # simple text response
            results = self.result.body
        elif 'results' in self.result.body:  # result set (or no result)
            results = self.result.body.get('results')
        elif isinstance(self.result.body, dict):
            results = dict(sorted(self.result.body.items()))  # single result
        else:
            results = None
        # Catch results that are empty
        if len(results) == 0:
            results = None
        if self.json:  # JSON output
            self._print_json(results)
        elif self.verbose:  # Verbose output
            self._print_verbose(results)
        else:  # Regular output
            self._print_regular(results)

def account(args):

    if args.actions:
        result = api.account().actions()
        r = ResultFormatter(title='Account Actions', result=result, verbose=args.verbose, json=args.json)
        r.output()

    if args.databases:
        fields = ['name']
        result = api.account().databases()
        r = ResultFormatter(title='Account Databases', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.domains:
        fields = ['name']
        result = api.account().domains()
        r = ResultFormatter(title='Account Domains', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.domain_bindings:
        fields = ['domain']
        result = api.account().domain_bindings()
        r = ResultFormatter(title='Account Domain Bindings', result=result, fields=fields, verbose=args.verbose)
        r.output()

    if args.ssl:
        fields = ['description']
        result = api.account().ssl()
        r = ResultFormatter(title='Account SSL', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.ssh:
        fields = ['description']
        result = api.account().ssh_keys()
        r = ResultFormatter(title='Account SSH Keys', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.zones:
        fields = ['domain']
        result = api.account().zones()
        r = ResultFormatter(title='Account Zones', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()


def configs(args):

    if args.list:
        fields = ['id', 'name']
        result = api.configs().list()
        r = ResultFormatter(title='Configs', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    # TODO: Implement
    # if args.add:
    #     name = args.add
    #     result = api.configs().create(name=name)
    #     r = ResultFormatter(title='Add Config', result=result, verbose=args.verbose, json=args.json)
    #     r.output()
    #
    # if args.delete:
    #     name = args.delete
    #     result = api.config(name=name).destroy()
    #     r = ResultFormatter(title='Delete Config', result=result, verbose=args.verbose, json=args.json)
    #     r.output()


def domain(args):

    if args.list:
        fields = ['name']
        result = api.domains().list()
        r = ResultFormatter(title='Domains', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.add:
        name = args.add
        result = api.domains().create(name=name)
        r = ResultFormatter(title='Add Domain', result=result, verbose=args.verbose, json=args.json)
        r.output()

    if args.delete:
        name = args.delete
        result = api.domain(name=name).destroy()
        r = ResultFormatter(title='Delete Domain', result=result, verbose=args.verbose, json=args.json)
        r.output()


def domain_binding(args):
    if args.subparser_name == 'add':
        result = api.domain_bindings().create(
            domain=args.domain,
            public_ip=args.ip,
            include_www=args.include_www,
            include_wildcard=args.include_wildcard,
            timeout=args.timeout,
            internal_port=args.port,
            object_id=args.config,
            content_type='config'
            )
        r = ResultFormatter(title='Add Domain Binding', result=result, verbose=args.verbose, json=args.json)
        r.output()

    if args.list:
        fields = ['domain']
        result = api.domain_bindings().list()
        r = ResultFormatter(title='Domain Bindings', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.delete:
        domain = args.delete
        result = api.domain_binding(domain=domain).destroy()
        r = ResultFormatter(title='Delete Domain Binding', result=result, verbose=args.verbose, json=args.json)
        r.output()


def ssh_key(args):

    if args.get:
        id = args.get
        fields = ['domain']
        result = api.ssh_key(id=id).retrieve()
        r = ResultFormatter(title='Retrieve SSH Key', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.list:
        fields = ['id', 'description']
        result = api.ssh_keys().list()
        r = ResultFormatter(title='List SSH Keys', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    # if args.add:
    #     domain = args.add
    #     result = api.zones().create(domain=domain)
    #     r = ResultFormatter(title='Add Zone', result=result, verbose=args.verbose, json=args.json)
    #     r.output()

    if args.delete:
        id = args.delete
        result = api.ssh_key(id=id).destroy()
        r = ResultFormatter(title='Delete SSH Key', result=result, verbose=args.verbose, json=args.json)
        r.output()


def zone(args):

    if args.get:
        domain = args.get
        fields = ['domain']
        result = api.zone(domain=domain).retrieve()
        r = ResultFormatter(title='Zones', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.list:
        fields = ['domain']
        result = api.zones().list()
        r = ResultFormatter(title='Zones', result=result, fields=fields, verbose=args.verbose, json=args.json)
        r.output()

    if args.add:
        domain = args.add
        result = api.zones().create(domain=domain)
        r = ResultFormatter(title='Add Zone', result=result, verbose=args.verbose, json=args.json)
        r.output()

    if args.delete:
        domain = args.delete
        result = api.zone(domain=domain).destroy()
        r = ResultFormatter(title='Delete Zone', result=result, verbose=args.verbose, json=args.json)
        r.output()

    if args.upload:
        domain = args.upload[0]
        file_path = args.upload[1]
        data = open(file_path, mode='r').read()
        options = dict()
        options['request_type'] = 'raw'
        result = api.zone(domain=domain).upload(data, options)
        r = ResultFormatter(title='Upload Zone', result=result, verbose=args.verbose, json=args.json)
        r.output()


def main(argv):

    # Check config
    if not find_config_file():
        print('Please create .voltgrid.cfg config file.')
        sys.exit(1)

    parser = argparse.ArgumentParser(prog='vgc', description='Volt Grid Client')
    subparsers = parser.add_subparsers(dest='')

    # Verbose option
    verbose = argparse.ArgumentParser(add_help=False)
    verbose.add_argument('-v', '--verbose', action='store_true', help='Verbose Output')

    # JSON option
    json = argparse.ArgumentParser(add_help=False)
    json.add_argument('-j', '--json', action='store_true', help='JSON Output')

    # create the parser for the "account" command
    parser_account = subparsers.add_parser('account', description='List account objects', parents=[verbose, json])
    parser_account.add_argument('--actions', action='store_true', help='List actions')
    parser_account.add_argument('--databases', action='store_true', help='List databases')
    parser_account.add_argument('--domains', action='store_true', help='List domains')
    parser_account.add_argument('--domain-bindings', action='store_true', help='List domain bindings')
    parser_account.add_argument('--ssl', action='store_true', help='List SSL')
    parser_account.add_argument('--ssh', action='store_true', help='List SSH')
    parser_account.add_argument('--zones', action='store_true', help='List DNS Zones')
    parser_account.set_defaults(func=account)

    # create the parser for the "config" command
    parser_configs = subparsers.add_parser('config', description='Config actions', parents=[verbose, json])
    parser_configs.add_argument('-l', '--list', action='store_true', help='List configs')
    # parser_configs.add_argument('-a', '--add', type=str, help='Add config')
    # parser_configs.add_argument('-d', '--delete', type=str, help='Delete config')
    parser_configs.set_defaults(func=configs)

    # create the parser for the "domain" command
    parser_domains = subparsers.add_parser('domain', description='Domain actions', parents=[verbose, json])
    parser_domains.add_argument('-l', '--list', action='store_true', help='List domains')
    parser_domains.add_argument('-a', '--add', type=str, help='Add domain')
    parser_domains.add_argument('-d', '--delete', type=str, help='Delete domain')
    parser_domains.set_defaults(func=domain)

    # create the parser for the "domain-binding" command
    parser_domain_binding = subparsers.add_parser('domain-binding', description='Domain binding actions', parents=[verbose, json])

    # # add domain binding
    subparser_domain_binding_add = parser_domain_binding.add_subparsers(dest='subparser_name')
    parser_domain_binding_add = subparser_domain_binding_add.add_parser('add', help='Add domain binding', description='Add domain binding', parents=[verbose,])
    parser_domain_binding_add.add_argument('--domain', type=str, help='Domain name')
    parser_domain_binding_add.add_argument('--ip', type=str, help='IP address')
    parser_domain_binding_add.add_argument('--include-www', action='store_true', help='Include www domain')
    parser_domain_binding_add.add_argument('--include-wildcard', action='store_true', help='Include wildcard domain')
    parser_domain_binding_add.add_argument('--timeout', type=str, help='HTTP timeout')
    parser_domain_binding_add.add_argument('--port', type=int, help='Internal Port')
    parser_domain_binding_add.add_argument('--config', type=str, help='Config UUID')

    # #
    parser_domain_binding.add_argument('-l', '--list', action='store_true', help='List domain bindings')
    parser_domain_binding.add_argument('-d', '--delete', type=str, help='Delete domain binding')
    parser_domain_binding.set_defaults(func=domain_binding)

    # create the parser for the "ssh-key" command
    parser_ssh_key = subparsers.add_parser('ssh-key', description='SSH key actions', parents=[verbose, json])
    parser_ssh_key.add_argument('-l', '--list', action='store_true', help='List SSH Keys')
    parser_ssh_key.add_argument('-g', '--get', type=str, help='Get SSH Key')
    #parser_ssh_key.add_argument('-a', '--add', type=str, help='Add SSH Key')
    parser_ssh_key.add_argument('-d', '--delete', type=str, help='Delete SSH Key')
    parser_ssh_key.set_defaults(func=ssh_key)

    # create the parser for the "zone" command
    parser_zones = subparsers.add_parser('zone', description='DNS zone actions', parents=[verbose, json])
    parser_zones.add_argument('-l', '--list', action='store_true', help='List zones')
    parser_zones.add_argument('-g', '--get', type=str, help='Get zone')
    parser_zones.add_argument('-a', '--add', type=str, help='Add zone')
    parser_zones.add_argument('-d', '--delete', type=str, help='Delete zone')
    parser_zones.add_argument('-u', '--upload', nargs=2, type=str, help='Upload zone file')
    parser_zones.set_defaults(func=zone)

    # Init parser if args passed
    if len(sys.argv) > 1:
        # Init parser
        args = parser.parse_args()
        args.func(args)
    else:
        # Show help
        parser.print_help()

if __name__ == "__main__":
    try:
        main(sys.argv)
    except ClientError as e:
        print("Error: %s (%s)" % (e.message, e.code))
        exit(EXIT_ERROR)
    except ConnectionError as e:
        print("Error: %s" % (e))
        exit(EXIT_ERROR)