# This file is part of Indico.
# Copyright (C) 2002 - 2020 CERN
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see the
# LICENSE file for more details.

import os
import shlex
import subprocess
import sys

import click
from click._compat import should_strip_ansi
from migra import Migration
from sqlalchemy import create_engine


click.disable_unicode_literals_warning = True


def _indent(msg, level=4):
    indentation = level * ' '
    return indentation + msg.replace('\n', '\n' + indentation)


def _subprocess_check_output(*popenargs, **kwargs):
    # copied from subprocess.check_output; added the ability to pass data ta stdin
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    stdin_data = kwargs.pop('stdin_data', None)
    if stdin_data:
        kwargs['stdin'] = subprocess.PIPE
    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate(stdin_data)
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get('args')
        if cmd is None:
            cmd = popenargs[0]
        raise subprocess.CalledProcessError(retcode, cmd, output=output)
    return output


def _checked_call(verbose, args, return_output=False, env=None, stdin_data=None):
    cmd = ' '.join([os.path.basename(args[0]), shlex.join(args[1:])])
    if verbose:
        click.echo(click.style(f'** {cmd}', fg='blue', bold=True), err=True)
    kwargs = {}
    if env:
        kwargs['env'] = dict(os.environ, **env)
    try:
        rv = _subprocess_check_output(args, stderr=subprocess.STDOUT, stdin_data=stdin_data, **kwargs).strip()
    except OSError as exc:
        click.echo(click.style(f'!! {cmd} failed: {exc}', fg='red', bold=True), err=True)
        sys.exit(1)
    except subprocess.CalledProcessError as exc:
        click.echo(click.style(f'!! {cmd} failed:', fg='red', bold=True), err=True)
        click.echo(click.style(_indent(exc.output.strip()), fg='yellow', bold=True), err=True)
        sys.exit(1)
    else:
        if return_output:
            return rv
        if verbose > 1 and rv:
            click.echo(click.style(rv, fg='green'), err=True)


def _build_conn_string(dbname):
    pghost = os.environ.get('PGHOST')
    pgport = os.environ.get('PGPORT')
    pguser = os.environ.get('PGUSER')
    parts = ['postgresql://']
    if pguser:
        parts += [pguser, ':@']
    if pghost:
        parts += [pghost]
        if pgport:
            parts += [':', pgport]
    parts += ['/', dbname]
    return ''.join(parts)


def _which(program):
    # taken from http://stackoverflow.com/a/377028/298479
    def _is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if _is_exe(program):
            return program
    else:
        for path in os.environ['PATH'].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if _is_exe(exe_file):
                return exe_file
    return None


@click.command()
@click.argument('dbname', required=False, default='indico')
@click.option('-v', '--verbose', help='Verbose - show called commands; use -vv to also show all output', count=True)
@click.option('-r', '--reverse', help='Reverse - return instead the SQL to go from target to base', is_flag=True)
def main(dbname, verbose, reverse):
    """
    Compare the structure of the database against what's created from the
    models during `indico db prepare`.

    By default the current database is assumed to be named `indico`, but it
    can be overridden.  The database uses for comparison will be named
    `indico_dbdiff` and may not exist. It will be created and dropped
    automatically.

    Since your user may or may not be a database superuser, the database is
    created from a template database named `indico_template`.  You can create
    it using the following SQL commands:

    \b
        createdb indico_template
        psql indico_template -c 'CREATE EXTENSION unaccent;'
        psql indico_template -c 'CREATE EXTENSION pg_trgm;'

    Since this script uses the command-line PostgreSQL tools any other
    configuration should be done using the various environment variables like
    PGHOST, PGPORT and PGUSER) and your `.pgpass` file.
    """
    temp_dbname = 'indico_dbdiff'
    base_conn = None
    target_conn = None

    # create database and dump current/new structures
    _checked_call(verbose, ['createdb', '-T', 'indico_template', temp_dbname])
    try:
        env_override = {'INDICO_CONF_OVERRIDE': repr({'SQLALCHEMY_DATABASE_URI': _build_conn_string(temp_dbname)})}
        _checked_call(verbose, ['indico', 'db', 'prepare'], env=env_override)

        # create SQLAlchemy engines/connections for base and target db
        base_eng = create_engine(_build_conn_string(temp_dbname))
        target_eng = create_engine(_build_conn_string(dbname))
        base_conn = base_eng.connect()
        target_conn = target_eng.connect()

        version = base_conn.execute("SELECT current_setting('server_version_num')::int").scalar()
        if version < 100000:
            click.echo(click.style('!! This utility requires at least Postgres 10', fg='red', bold=True), err=True)
            sys.exit(1)

        if verbose:
            click.echo(click.style('** Calculating differences', fg='cyan'), err=True)
        # use migra to figure out the SQL diff
        m = Migration(base_conn, target_conn) if reverse else Migration(target_conn, base_conn)
        m.set_safety(False)
        m.add_all_changes()
        diff = m.sql
    finally:
        # clean up connections and engines, so that no open connection remains
        # (otherwise the DROPDB operation won't work)
        if base_conn:
            base_conn.close()
            base_eng.dispose()
        if target_conn:
            target_conn.close()
            target_eng.dispose()
        _checked_call(verbose, ['dropdb', temp_dbname])

    if not diff:
        click.echo(click.style('No changes found :)', fg='green', bold=True), err=True)
        return
    elif should_strip_ansi(sys.stdout):
        click.echo(diff)
    else:
        pretty_diff = _checked_call(verbose, ['pygmentize', '-l', 'sql', '-f', 'terminal256',
                                              '-O', 'style=native,bg=dark'], return_output=True, stdin_data=diff)
        click.echo(pretty_diff)


if __name__ == '__main__':
    main()

@@ -558,719 +558,8 @@
 )%0A%0A%0A
-def _subprocess_check_output(*popenargs, **kwargs):%0A    # copied from subprocess.check_output; added the ability to pass data ta stdin%0A    if 'stdout' in kwargs:%0A        raise ValueError('stdout argument not allowed, it will be overridden.')%0A    stdin_data = kwargs.pop('stdin_data', None)%0A    if stdin_data:%0A        kwargs%5B'stdin'%5D = subprocess.PIPE%0A    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)%0A    output, unused_err = process.communicate(stdin_data)%0A    retcode = process.poll()%0A    if retcode:%0A        cmd = kwargs.get('args')%0A        if cmd is None:%0A            cmd = popenargs%5B0%5D%0A        raise subprocess.CalledProcessError(retcode, cmd, output=output)%0A    return output%0A%0A%0A
 def 
@@ -622,23 +622,16 @@
 ne, 
-stdin_data=None
+input=''
 ):%0A 
@@ -890,17 +890,16 @@
    rv = 
-_
 subproce
@@ -900,17 +900,17 @@
 bprocess
-_
+.
 check_ou
@@ -950,29 +950,30 @@
 UT, 
-stdin_data=stdin_data
+input=input, text=True
 , **
@@ -5753,18 +5753,13 @@
 ue, 
-stdin_data
+input
 =dif

Fix db_diff tool and remove obsolete util