#!/usr/bin/env python
"""
Dump data from genisys mongo in an acceptance test fixture format.
"""
import logging
import codecs
from collections import defaultdict

import msgpack
import pymongo

from genisys.web import model
from genisys.toiler.section import _get_config_hash


log = logging.getLogger('acctest')


def _get_volatiles(storage, vtype, keys, usage=None):
    volatiles = storage.get_volatiles(vtype, keys=list(keys), full=True)
    success = True
    result = {}
    for key, value in volatiles.items():
        if value['value'] is None:
            log.error('volatile of type %r with key %r has not '
                      'been calculated ever', vtype, key)
            success = False
        elif value['last_status'] == 'new':
            log.error('volatile of type %r with key %r has not '
                      'been calculated since edit (status=\'new\')',
                      vtype, key)
            success = False
        elif value['last_status'] not in ('same', 'modified'):
            log.error('volatile of type %r with key %r was not '
                      'calculated successfully the last time '
                      '(actual status was %r)',
                      vtype, key, value['last_status'])
            success = False
        else:
            result[key] = value['value']
            continue
        if usage and key in usage:
            log.info('volatile vtype=%r key=%r is being used in %r',
                     vtype, key, ', '.join(str(u) for u in sorted(usage[key])))
    return success, result


def export_data(client, db_name):
    storage = model.MongoStorage(client, db_name)
    log.info('collecting section structure')
    structure = storage.get_section_subtree('')
    stack = [structure]
    all_paths = []
    all_selectors = set()
    selectors_usage = defaultdict(set)
    all_users = set()
    all_resource_types = set()
    resource_types_usage = defaultdict(set)
    all_resource_ids = set()
    resource_ids_usage = defaultdict(set)
    section_keys = tuple('name desc owners stype rules path '
                         'stype_options subsections'.split())
    rule_keys = tuple('name desc editors selector config_source'.split())
    while stack:
        node = stack.pop()
        all_users.update(node['owners'])
        all_paths.append(node['path'])
        if node['stype'] == 'sandbox_resource':
            rtype = node['stype_options']['resource_type']
            all_resource_types.add(rtype)
            resource_types_usage[rtype].add(node['path'])
        all_rules = []
        for rule in node['rules']:
            all_rules.append(rule)
            for subrule in rule['subrules']:
                all_rules.append(subrule)
        for rule in all_rules:
            all_users.update(rule['editors'])
            if rule['selector']:
                all_selectors.add(rule['selector'])
                selectors_usage[rule['selector']].add((node['path'],
                                                       rule['name']))
            if node['stype'] == 'sandbox_resource':
                rid = rule['config']['resource_id']
                all_resource_ids.add(rid)
                resource_ids_usage[rid].add((node['path'], rule['name']))
            cleanrule = {key: rule[key] for key in rule_keys}
            if 'subrules' in rule:
                cleanrule['subrules'] = rule['subrules']
            rule.clear()
            rule.update(cleanrule)
        stack.extend(node['subsections'].values())
        cleannode = {key: node[key] for key in section_keys}
        node.clear()
        node.update(cleannode)

    log.info('fetching section volatiles')
    sec_ok, sec_volatiles = _get_volatiles(storage, 'section', all_paths)
    all_hosts = {'alldefault': []}
    if sec_ok:
        for path in all_paths:
            value = sec_volatiles[path]
            for host in list(value['hosts']):
                all_hosts.setdefault(host, [])
        for path in all_paths:
            value = sec_volatiles[path]
            for host, host_config in all_hosts.items():
                config_id = value['hosts'].get(host, -1)
                config = value['configs'].get(config_id)
                if config is None:
                    host_config.append((None, None))
                else:
                    ehash = _get_config_hash(config['config'])
                    if ehash != config['config_hash']:
                        log.error('host %r section %r: config hash differs '
                                  'from expected (%r != %r)',
                                  host, path, config['config_hash'], ehash)
                    host_config.append((config['config_hash'],
                                        config['matched_rules']))

    log.info('fetching resources volatiles')
    res_ok, all_resources = _get_volatiles(
        storage, 'sandbox_resource', all_resource_ids, resource_ids_usage
    )
    log.info('fetching releases volatiles')
    rel_ok, all_releases = _get_volatiles(
        storage, 'sandbox_releases', all_resource_types, resource_types_usage
    )
    log.info('fetching selectors volatiles')
    sel_ok, all_selectors = _get_volatiles(
        storage, 'selector', all_selectors, selectors_usage
    )

    if not all([sec_ok, res_ok, rel_ok, sel_ok]):
        import sys
        sys.exit(1)

    log.info('finishing up')

    for key in list(all_selectors):
        stripped = key.strip()
        if stripped != key:
            log.warning('stripping selector %r', key)
            all_selectors[stripped] = all_selectors.pop(key)

    all_groups = [username[len('group:'):]
                  for username in all_users
                  if username.startswith('group:')]
    all_users = [username
                 for username in all_users
                 if not username.startswith('group:')]

    return {'paths': all_paths,
            'hosts': all_hosts,
            'selectors': all_selectors,
            'users': all_users,
            'groups': all_groups,
            'resources': all_resources,
            'releases': all_releases,
            'structure': structure}


def main(mongo_uri, db_name, output_file):
    client = pymongo.MongoClient(mongo_uri)
    data = export_data(client, db_name)
    dump = codecs.encode(msgpack.dumps(data, encoding='utf8'), 'zip')
    output_file.write(dump)
    log.info('dumped %d bytes of data to %r', len(dump), output_file.name)


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('output_path', type=argparse.FileType('wb'))
    parser.add_argument('--mongo-uri', '-m', default='mongodb://127.0.0.1',
                        help='mongodb connection uri')
    parser.add_argument('--db-name', '-n', default='genisys',
                        help='database name (defaults to "genisys")')
    args = parser.parse_args()
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    main(args.mongo_uri, args.db_name, args.output_path)
