#!/usr/bin/env python3

import os
import yaml
try:
    yaml.warnings({'YAMLLoadWarning': False})
except:
    pass
import tempfile
import logging
from pathlib import Path

dir_me = Path(__file__).absolute().parents[1]

default_timeout = 5

import sys
import argparse

logger = logging.getLogger('roboger')

ap = argparse.ArgumentParser()

ap.add_argument('--debug', help=f'client debug mode', action='store_true')
ap.add_argument(
    '-T',
    '--timeout',
    help=f'API request timeout (in seconds, default: {default_timeout})',
    type=float,
    default=default_timeout,
    metavar='SEC')
ap.add_argument('-F',
                '--config-file',
                help='alternative server/client config file',
                metavar='FILE')
ap.add_argument('-o',
                '--output',
                help='output',
                choices=['raw', 'json', 'yaml'],
                default='raw')

sp = ap.add_subparsers(dest='command', help='command to execute', required=True)

sp_test = sp.add_parser('test', help='test server')
sp_deploy = sp.add_parser('deploy', help='deploy configuration')
sp_deploy.add_argument('--delete-everything',
                       help='DELETE ALL RESOURCES on server before deployment',
                       action='store_true')
sp_test = sp.add_parser('reset-addr-limits',
                        help='reset address limits (if used)')
sp_test = sp.add_parser('core-cleanup', help='core cleanup')
reqn = sp_deploy.add_argument_group('required named arguments')
reqn.add_argument('-f',
                  '--input-file',
                  help='input file ("stdin" for stdin)',
                  required=True)

for prs in [('addr', 'a'), ('endpoint', 'e', 'ep', 'end'),
            ('subscription', 's', 'sub')]:
    i = prs[0]
    iap = sp.add_parser(i, help=f'{i} management', aliases=prs[1:])
    funcs = [('list resources', 'list', 'ls', 'l'),
             ('create new sub-resource', 'create', 'cr'),
             ('describe resource', 'describe', 'des', 'desc', 'ds'),
             ('edit resource', 'edit', 'ed', 'e'),
             ('delete resource', 'delete', 'del'),
             ('disable resource', 'disable', 'dis'),
             ('enable resource', 'enable', 'en'),
             ('apply resource properties from JSON or YAML file', 'apply',
              'apl')]
    if i == 'addr':
        funcs.append(('re-generate address', 'change', 'ch'))
    elif i == 'endpoint':
        funcs.append((
            'copy subscriptions',
            'copysub',
        ))
    isp = iap.add_subparsers(dest='func', help=f'{i} commands', required=True)
    for fs in funcs:
        f = fs[1]
        x = isp.add_parser(f, help=fs[0], aliases=fs[2:])
        reqn = x.add_argument_group('required named arguments')
        if i != 'addr' or f not in ['list', 'create']:
            x.add_argument('resource', help='current/parent resource')
        if i == 'endpoint' and f == 'create':
            x.add_argument('plugin_name', help='plugin name')
        if i == 'addr' and f == 'change':
            x.add_argument('--to', help='change addres to the specified value')
        if i == 'endpoint' and f == 'copysub':
            x.add_argument('target_resource', help='current/parent resource')
            x.add_argument(
                '--replace',
                help='Replace (remove existing) target subscriptions',
                action='store_true')
        if f == 'apply':
            reqn.add_argument('-f',
                              '--input-file',
                              help='input file ("stdin" for stdin)',
                              required=True)

a = ap.parse_args()

if a.debug:
    logger.setLevel(level=logging.DEBUG)
    logging.basicConfig(level=logging.DEBUG)

import roboger_manager

client_config = True

if a.config_file:
    fname = a.config_file
else:
    fname = os.path.expanduser('~/.robogerctl.yml')
    if not Path(fname).exists():
        client_config = False
        fname = dir_me / 'etc/roboger.yml'
    if not Path(fname).exists():
        fname = '/usr/local/etc/roboger.yml'
logger.debug(f'Client using config file {fname}')
with open(fname) as fh:
    config = yaml.load(fh.read())

format_uri = lambda uri: 'http://{}'.format(uri.replace('0.0.0.0', '127.0.0.1'))

if client_config:
    api = roboger_manager.ManagementAPI(api_uri=config['uri'],
                                        api_key=config['key'],
                                        timeout=a.timeout)
    limits = config.get('limits')
else:
    api = roboger_manager.ManagementAPI(
        api_uri=format_uri(config['roboger']['gunicorn']['listen']),
        api_key=config['roboger']['master']['key'],
        timeout=a.timeout)
    limits = config['roboger'].get('limits')

if limits:
    roboger_manager.use_limits = True

roboger_manager.default_api = api

result = None

editor = os.environ.get('EDITOR', 'vi')


def format_dict(d, fields, xtra={}):
    from collections import OrderedDict
    d = dict(d)
    result = OrderedDict()
    for f in fields:
        if f in d.keys():
            result[f] = d[f]
        elif f in xtra.keys():
            result[f] = xtra[f]
    return result


def edit_obj(obj):
    obj.load()
    fd, fname = tempfile.mkstemp('.yaml')
    try:
        with os.fdopen(fd, 'w') as tmp:
            fc = yaml.dump(obj.serialize(include_protected_fields=False),
                           default_flow_style=False)
            tmp.write(fc)
        from hashlib import sha256
        oldhash = sha256(fc.encode()).digest()
        os.system(f'{editor} {fname}')
        with open(fname) as tmp:
            fc = tmp.read()
            newhash = sha256(fc.encode()).digest()
        if oldhash == newhash:
            return ''
        else:
            obj.load(data=yaml.load(fc), load_protected_fields=False)
            obj.save()
    finally:
        os.unlink(fname)


def process_obj_func(obj, format_func):
    if a.func in ['delete', 'del']:
        obj.delete()
    elif a.func in ['describe', 'des', 'desc', 'ds']:
        obj.load()
        return format_func(obj)
    elif a.func in ['edit', 'ed']:
        return edit_obj(obj)
    elif a.func in ['disable', 'dis']:
        obj.disable()
    elif a.func in ['enable', 'en']:
        obj.enable()
    elif a.func in ['apply', 'apl']:
        if a.input_file == 'stdin':
            fc = sys.stdin.read()
        else:
            with open(a.input_file) as fh:
                fc = fh.read()
        obj.load(data=yaml.load(fc), load_protected_fields=False)
        obj.save()


def format_addr(obj):
    d = dict(obj)
    d['res'] = d['id']
    del d['id']
    return d


def format_endpoint(obj):
    d = dict(obj)
    d['res'] = f'{d["addr_id"]}.{d["id"]}'
    del d['id']
    del d['addr_id']
    return d


def format_subscription(obj):
    d = dict(obj)
    d['res'] = (f'{d["addr_id"]}.' f'{d["endpoint_id"]}.{d["id"]}')
    del d['id']
    del d['addr_id']
    return d


def split_res(res, amnt):
    if res.count('.') != amnt:
        raise ValueError
    result = res.split('.', amnt)
    for x in result:
        if not x:
            raise ValueError
    return result


try:
    if a.command == 'test':
        result = api.test()
    elif a.command == 'reset-addr-limits':
        result = api.reset_addr_limits()
    elif a.command == 'core-cleanup':
        result = api.core_cleanup()
    elif a.command == 'deploy':
        if a.input_file == 'stdin':
            fc = sys.stdin.read()
        else:
            with open(a.input_file) as fh:
                fc = fh.read()
        data = yaml.load(fc)
        result = []
        if a.delete_everything:
            roboger_manager.delete_everything(confirm='YES')
        try:
            for acfg in data.get('addrs', []):
                addr = roboger_manager.create_addr()
                result.append(addr)
                addr_config = acfg.copy()
                try:
                    del addr_config['endpoints']
                except:
                    pass
                addr.load(data=addr_config, load_protected_fields=False)
                addr.save()
                if 'a' in acfg:
                    addr.change(to=acfg['a'])
                for ecfg in acfg.get('endpoints', []):
                    ep = addr.create_endpoint(plugin_name=ecfg['plugin_name'])
                    ep_config = ecfg.copy()
                    del ep_config['plugin_name']
                    try:
                        del ep_config['subscriptions']
                    except:
                        pass
                    ep.load(data=ep_config, load_protected_fields=False)
                    ep.save()
                    for scfg in ecfg.get('subscriptions', []):
                        s = ep.create_subscription()
                        s.load(data=scfg, load_protected_fields=False)
                        s.save()
            result = {'deployed': [(a.id) for a in result]}
        except Exception as e:
            for addr in result:
                try:
                    addr.delete()
                except:
                    pass
            result = None
            raise
    elif a.command in ['addr', 'a']:
        if a.func in ['list', 'ls', 'l']:
            result = [
                format_dict(d, ['res', 'a', 'active', 'lim_c', 'lim_s'],
                            {'res': d['id']})
                for d in roboger_manager.list_addr()
            ]
        elif a.func in ['create', 'cr']:
            result = format_addr(roboger_manager.create_addr())
        else:
            addr = roboger_manager.Addr(id=a.resource)
            if a.func in ['change', 'ch']:
                addr.change(to=a.to)
                result = addr.a
            else:
                result = process_obj_func(addr, format_addr)
    elif a.command in ['endpoint', 'e', 'ep', 'end']:
        if a.func in ['list', 'ls', 'l']:
            addr = roboger_manager.Addr(id=a.resource)
            result = [
                format_dict(
                    ep,
                    ['res', 'active', 'plugin_name', 'config', 'description'],
                    {'res': f'{ep.addr_id}.{ep.id}'})
                for ep in addr.list_endpoints()
            ]
        elif a.func in ['create', 'cr']:
            addr = roboger_manager.Addr(id=a.resource)
            result = format_endpoint(
                addr.create_endpoint(plugin_name=a.plugin_name))
        else:
            try:
                addr_id, ep_id = split_res(a.resource, 1)
            except ValueError:
                raise Exception(
                    'Please specify correct resource path: addr_id.endpoint_id')
            ep = roboger_manager.Endpoint(id=ep_id, addr_id=addr_id)
            if a.func in ['copysub']:
                try:
                    addr2_id, ep2_id = split_res(a.target_resource, 1)
                except ValueError:
                    raise Exception('Please specify correct target '
                                    'resource path: addr_id.endpoint_id')
                ep2 = roboger_manager.Endpoint(id=ep2_id, addr_id=addr2_id)
                ep.copysub(target=ep2, replace=a.replace)
            else:
                result = process_obj_func(ep, format_endpoint)
    elif a.command in ['subscription', 's', 'sub']:
        if a.func in ['list', 'create', 'ls', 'l', 'cr']:
            try:
                addr_id, ep_id = split_res(a.resource, 1)
            except ValueError:
                raise Exception(
                    'Please specify correct resource path: addr_id.endpoint_id')
            ep = roboger_manager.Endpoint(id=ep_id, addr_id=addr_id)
            if a.func in ['list', 'ls', 'l']:
                result = [
                    format_dict(s, [
                        'res', 'active', 'location', 'tag', 'sender', 'level',
                        'level_match'
                    ], {'res': f'{s.addr_id}.{s.endpoint_id}.{s.id}'})
                    for s in ep.list_subscriptions()
                ]
            elif a.func in ['create', 'cr']:
                result = format_subscription(ep.create_subscription())
        else:
            try:
                addr_id, ep_id, s_id = split_res(a.resource, 2)
            except ValueError:
                raise Exception('Please specify correct resource path: '
                                'addr_id.endpoint_id.subscription_id')
            s = roboger_manager.Subscription(id=s_id,
                                             addr_id=addr_id,
                                             endpoint_id=ep_id)
            result = process_obj_func(s, format_subscription)
except Exception as e:
    print(f'ERROR: {e}', file=sys.stderr)
    if a.debug:
        raise
    sys.exit(1)

if result:
    if a.output == 'yaml':
        print(yaml.dump(result, default_flow_style=False))
    elif a.output == 'json':
        import json
        print(json.dumps(result, indent=True, sort_keys=True))
    else:
        if isinstance(result, dict):
            import rapidtables
            tbl = [
                dict(attr=k, value=result[k])
                for k in sorted(result.keys())
                if k != 'res'
            ]
            if 'res' in result.keys():
                tbl.insert(0, dict(attr='res', value=result['res']))
            rapidtables.print_table(tbl)
        elif isinstance(result, list):
            import rapidtables
            rapidtables.print_table(result)
        else:
            print(result)
elif result != '' and result != []:
    print('OK')
