#!/usr/bin/env python

import subprocess as sp
import os
from contextlib import closing

import click
from yandex.maps.wiki.utils import ConnParams
import psycopg2
from lxml import etree


def rel_path(path):
    return os.path.join(os.getcwd(), path)

SERVICE_DIRS = {
    'renderer': rel_path('../../services/renderer/src'),
    'editor': rel_path('../../services/editor/src'),
    'acl': rel_path('../../services/acl/bin'),
    'tasks': rel_path('../../services/tasks'),
    'social': rel_path('../../services/social/src/bin'),
}

PG_HOST = os.environ.get('PGHOST', 'dataprod-db.vla.yp-c.yandex.net')
PG_PORT = os.environ.get('PGPORT', '5432')
PG_PASSWORD = os.environ.get('PGPASSWORD', 'mapspro')

SERVICES_CFG = rel_path('../../cfg/services/services.local.%s.xml' % os.environ.get('USER', 'maps'))
RENDERER_CFG = rel_path('../../cfg/layers/mpskl/config-local.%s.xml' % os.environ.get('USER', 'maps'))

DB_TEMPLATE = 'mapspro_template'


@click.group()
def click_default_group():
    pass


def stop_services(services=SERVICE_DIRS.keys()):
    cmds = [['make', '-C', SERVICE_DIRS[s], 'stop'] for s in services]
    processes = [sp.Popen(cmd) for cmd in cmds]
    for cmd, p in zip(cmds, processes):
        retcode = p.wait()
        if retcode:
            raise sp.CalledProcessError(retcode, cmd)


def start_services(services=SERVICE_DIRS.keys()):
    dirs = [SERVICE_DIRS[s] for s in services]
    for dir in dirs:
        sp.check_call(['make', '-C', dir, 'start'])


def available_dbs():
    conn_str = ('host=' + PG_HOST
                + ' port=5432 dbname=postgres user=mapspro password=mapspro')
    with closing(psycopg2.connect(conn_str)) as conn:
        cur = conn.cursor()
        cur.execute('select datname from pg_database')
        return [tup[0] for tup in cur.fetchall()]


def drop_db(db_name):
    sp.check_call(['ssh', PG_HOST, 'dropdb', '--username=postgres', db_name])


def create_db(db_name):
    sp.check_call(['./pgmigrate_helper/pgmigrate_helper',
                   '-c', '../../migrations/config.development.yaml',
                   '-e', 'development_template',
                   '-d', '../../migrations',
                   '-t', 'latest',
                   'migrate'])
    sp.check_call(['ssh', PG_HOST, 'createdb',
                   '--username=postgres',
                   '--template=' + DB_TEMPLATE,
                   '--owner=mapspro',
                   db_name])


def force_create_db(db_name):
    if db_name == DB_TEMPLATE:
        raise click.ClickException('Can not force-create template database "%s"' % DB_TEMPLATE)

    if db_name in available_dbs():
        stop_services()
        drop_db(db_name)
    create_db(db_name)


def change_db_name_in_configs(db_name, host, port):
    cfg_xpaths = [
        (SERVICES_CFG, '/config/common/databases/database'),
        (RENDERER_CFG, '/config/db')]

    def patch_instance_node(node):
        node.attrib['host'] = host
        node.attrib['port'] = port
        node.attrib['pass'] = PG_PASSWORD

    for fname, cfg_xpath in cfg_xpaths:
        cfg = etree.parse(fname)
        for node in cfg.xpath(cfg_xpath):
            node.attrib['name'] = db_name

            for read_node in node.findall('read'):
                patch_instance_node(read_node)
            for write_node in node.findall('write'):
                patch_instance_node(write_node)

        with open(fname, 'w') as out:
            cfg.write(out, pretty_print=True, encoding="utf-8", xml_declaration=True)


def parse_services_str(str):
    if str == 'all':
        services = SERVICE_DIRS.keys()
    else:
        services = str.split(',')
    for s in services:
        if s not in SERVICE_DIRS:
            raise click.ClickException('unknown service: ' + s)
    return services


@click_default_group.command()
@click.argument('services')
def stop(services):
    """stop services (comma-separated list or 'all')"""

    stop_services(parse_services_str(services))


@click_default_group.command()
@click.argument('services')
def restart(services):
    """restart services (comma-separated list or 'all')"""

    start_services(parse_services_str(services))


@click_default_group.command('list_dbs')
def list_dbs():
    """list available dbs"""

    for db in available_dbs():
        print db


@click_default_group.command('switch-db')
@click.argument('name')
@click.option('--create', '-c', is_flag=True, help='if set, create blank db')
@click.option('--force', '-f', is_flag=True, help='if set, drop old db before creating new db with the same name')
def switch_db(name, create, force):
    """switch services to the database"""

    if create and force:
        force_create_db(name)
    elif create:
        if name in available_dbs():
            raise click.ClickException('database "%s" already exists, set --force if you want to drop it' % name)
        create_db(name)
    elif name not in available_dbs():
        raise click.ClickException('database "%s" does not exist, set --create if you want to create blank db' % name)

    change_db_name_in_configs(db_name=name, host=PG_HOST, port=PG_PORT)
    start_services()


def call_revisionapi(cfg, *args):
    cmd = ['revisionapi', '--cfg', cfg] + list(args)
    process = sp.Popen(cmd, stdout=sp.PIPE)
    stdout, _ = process.communicate()
    if process.returncode:
        raise sp.CalledProcessError(process.returncode, ' '.join(cmd))
    return stdout


def is_big_database(cfg):
    head_commit_id = call_revisionapi(cfg, '--cmd=headCommitId')
    head_commit_id = int(head_commit_id)
    return head_commit_id > 100


@click_default_group.command('reset_tds')
def reset_tds():
    """drop all geo objects from database"""

    if is_big_database(SERVICES_CFG):
        raise click.ClickException(
            'current database is too big, refusing to reset')

    db_xml = etree.parse(SERVICES_CFG).find('common/databases/database[@id="core"]')
    conn_params = ConnParams.from_xml(
        db_xml.find('write'), dbname=db_xml.attrib["name"])

    print call_revisionapi(SERVICES_CFG, '--cmd=clearAll')

    sp.check_call(['/usr/lib/yandex/maps/wiki/bin/wiki-editor-tool',
                   '--log-level=warning',
                   '--all-objects=1',
                   '--config', SERVICES_CFG])

    with closing(psycopg2.connect(conn_params.connstring())) as conn:
        cur = conn.cursor()
        cur.execute('truncate social.commit_event')
        conn.commit()


@click_default_group.command('load_tds_json')
@click.argument('json_file')
def load_tds_json(json_file):
    """load object revisions from json to current db"""

    commit_ids = call_revisionapi(
        SERVICES_CFG,
        '--cmd=import',
        '--user-id=82282794',
        '--start-from-json-id',
        '--path', json_file)
    commit_ids = commit_ids.rstrip()

    print 'commit ids:', commit_ids

    sp.check_call(['/usr/lib/yandex/maps/wiki/bin/wiki-editor-tool',
                   '--config', SERVICES_CFG,
                   '--commits', commit_ids,
                   '--set-progress-state=0',
                   '--branch-exclusive-lock=0'])


@click_default_group.command('dump_tds_json')
def dump_tds_json():
    """dump object revisions from current db into json"""

    if is_big_database(SERVICES_CFG):
        raise click.ClickException(
            'current database is too big, please dump via other means')

    sp.check_call(
        'revisionapi --cfg=%s --cmd=export | python -m json.tool' % SERVICES_CFG,
        shell=True)


def main():
    click_default_group()

if __name__ == '__main__':
    main()
