# coding=utf-8
"""Authors: Łukasz Opioła, Konrad Zemek
Copyright (C) 2015 ACK CYFRONET AGH
This software is released under the MIT license cited in 'LICENSE.txt'

Brings up a set of appmock instances.
"""

import copy
import json
import os
import random
import string
from .timeouts import *

from . import common, docker, dns, cluster_manager, worker, test_ca


def domain(appmock_instance, uid):
    """Formats domain for an appmock instance.
    It is intended to fake OP or OZ domain.
    """
    return common.format_hostname(appmock_instance, uid)


def appmock_hostname(node_name, uid):
    """Formats hostname for a docker hosting appmock.
    NOTE: Hostnames are also used as docker names!
    """
    return common.format_hostname(node_name, uid)


def appmock_erl_node_name(node_name, uid):
    """Formats erlang node name for a vm on appmock docker.
    """
    hostname = appmock_hostname(node_name, uid)
    return common.format_erl_node_name('appmock', hostname)


def _tweak_config(config, appmock_node, appmock_instance, uid):
    cfg = copy.deepcopy(config)
    cfg['nodes'] = {'node': cfg['nodes'][appmock_node]}
    mocked_app = 'none'
    if 'mocked_app' in cfg['nodes']['node']:
        mocked_app = cfg['nodes']['node']['mocked_app']

    # Node name depends on mocked app, if none is specified,
    # default appmock_erl_node_name will be used.
    node_name = {
        'cluster_manager': cluster_manager.cm_erl_node_name(appmock_node,
                                                            appmock_instance,
                                                            uid),
        'op_worker': worker.worker_erl_node_name(appmock_node,
                                                 appmock_instance,
                                                 uid),
        'oz_worker': worker.worker_erl_node_name(appmock_node, appmock_instance, uid)
    }.get(mocked_app, appmock_erl_node_name(appmock_node, uid))

    if 'vm.args' not in cfg['nodes']['node']:
        cfg['nodes']['node']['vm.args'] = {}
    vm_args = cfg['nodes']['node']['vm.args']
    vm_args['name'] = node_name
    # If cookie is not specified, set random cookie
    # so the node does not try to connect to others
    if 'setcookie' not in vm_args:
        vm_args['setcookie'] = ''.join(
            random.sample(string.ascii_letters + string.digits, 16))

    return cfg


def _node_up(image, bindir, config, config_path, dns_servers, logdir):
    node_name = config['nodes']['node']['vm.args']['name']
    (name, sep, hostname) = node_name.partition('@')

    sys_config = config['nodes']['node']['sys.config']['appmock']
    # can be an absolute path or relative to gen_dev_args.json
    app_desc_file_path = sys_config['app_description_file']
    app_desc_file_name = os.path.basename(app_desc_file_path)
    app_desc_file_path = os.path.join(common.get_file_dir(config_path),
                                      app_desc_file_path)

    # file_name must be preserved as it must match the Erlang module name
    sys_config['app_description_file'] = '/tmp/' + app_desc_file_name

    key, cert, cacert = test_ca.generate_webcert(hostname)

    command = '''mkdir -p /root/bin/node/log/
mkdir -p /root/bin/node/etc/certs
bindfs --create-for-user={uid} --create-for-group={gid} /root/bin/node/log /root/bin/node/log
set -e
cat <<"EOF" > /tmp/{app_desc_file_name}
{app_desc_file}
EOF
cat <<"EOF" > /tmp/gen_dev_args.json
{gen_dev_args}
EOF
cat <<"EOF" > /root/bin/node/etc/certs/web_key.pem
{key}
EOF
cat <<"EOF" > /root/bin/node/etc/certs/web_cert.pem
{cert}
EOF
cat <<"EOF" > /root/bin/node/etc/certs/web_chain.pem
{cacert}
EOF
escript bamboos/gen_dev/gen_dev.escript /tmp/gen_dev_args.json

/root/bin/node/bin/appmock console
sleep 5'''  # Add sleep so logs can be chowned
    command = command.format(
        uid=os.geteuid(),
        gid=os.getegid(),
        app_desc_file_name=app_desc_file_name,
        app_desc_file=open(app_desc_file_path, 'r').read(),
        gen_dev_args=json.dumps({'appmock': config}),
        key=key,
        cert=cert,
        cacert=cacert
    )

    bindir = os.path.abspath(bindir)
    volumes = ['/root/bin', (bindir, bindir, 'ro')]

    if logdir:
        logdir = os.path.join(os.path.abspath(logdir), hostname)
        os.makedirs(logdir)
        volumes.extend([(logdir, '/root/bin/node/log', 'rw')])

    container = docker.run(
        image=image,
        name=hostname,
        hostname=hostname,
        detach=True,
        interactive=True,
        tty=True,
        workdir=bindir,
        volumes=volumes,
        dns_list=dns_servers,
        privileged=True,
        command=command)

    return container, {
        'docker_ids': [container],
        'appmock_nodes': [node_name]
    }


def _ready(node):
    node_ip = docker.inspect(node)['NetworkSettings']['IPAddress']
    return common.nagios_up(node_ip, '9999')


def up(image, bindir, dns_server, uid, config_path, logdir=None):
    config = common.parse_json_config_file(config_path)
    input_dir = config['dirs_config']['appmock']['input_dir']
    dns_servers, output = dns.maybe_start(dns_server, uid)

    for appmock_instance in config['appmock_domains']:
        gen_dev_cfg = {
            'config': {
                'input_dir': input_dir,
                'target_dir': '/root/bin'
            },
            'nodes': config['appmock_domains'][appmock_instance]['appmock']
        }

        tweaked_configs = [_tweak_config(gen_dev_cfg, appmock_node,
                                         appmock_instance, uid)
                           for appmock_node in gen_dev_cfg['nodes']]

        include_domain = False
        appmock_ips = []
        appmocks = []
        for cfg in tweaked_configs:
            appmock_id, node_out = _node_up(image, bindir, cfg,
                                            config_path, dns_servers, logdir)
            appmocks.append(appmock_id)
            if 'mocked_app' in cfg['nodes']['node']:
                mocked_app = cfg['nodes']['node']['mocked_app']
                if mocked_app == 'op_worker' or mocked_app == 'oz_worker':
                    include_domain = True
                    appmock_ips.append(common.get_docker_ip(appmock_id))
            common.merge(output, node_out)

        common.wait_until(_ready, appmocks, APPMOCK_WAIT_FOR_NAGIOS_SECONDS)

        if include_domain:
            domains = {
                'domains': {
                    domain(appmock_instance, uid): {
                        'ns': [],
                        'a': appmock_ips
                    }
                }
            }
            common.merge(output, domains)

    # Make sure domain are added to the dns server
    dns.maybe_restart_with_configuration(dns_server, uid, output)
    return output
