#!/usr/bin/env python
from __future__ import print_function

import sys, os, datetime, time, shutil, tarfile, tempfile, subprocess, os.path
from optparse import OptionParser
from ._backup import *

info = "'rethinkdb dump' creates an archive of data from a RethinkDB cluster"
usage = "rethinkdb dump [-c HOST:PORT] [-p] [--password-file FILENAME] [--tls-cert FILENAME] [-f FILE] [--clients NUM] [-e (DB | DB.TABLE)]..."

def print_dump_help():
    print(info)
    print(usage)
    print("")
    print("  -h [ --help ]                    print this help")
    print("  -c [ --connect ] HOST:PORT       host and client port of a rethinkdb node to connect")
    print("                                   to (defaults to localhost:28015)")
    print("  --tls-cert FILENAME              certificate file to use for TLS encryption.")
    print("  -p [ --password ]                interactively prompt for a password required to connect.")
    print("  --password-file FILENAME         read password required to connect from file.")
    print("  -f [ --file ] FILE               file to write archive to (defaults to")
    print("                                   rethinkdb_dump_DATE_TIME.tar.gz);")
    print("                                   if FILE is -, use standard output (note that")
    print("                                   intermediate files will still be written to")
    print("                                   the --temp-dir directory)")
    print("  -e [ --export ] (DB | DB.TABLE)  limit dump to the given database or table (may")
    print("                                   be specified multiple times)")
    print("  --clients NUM_CLIENTS            number of tables to export simultaneously (defaults")
    print("                                   to 3)")
    print("  --temp-dir DIRECTORY             the directory to use for intermediary results")
    print("  --overwrite-file                 don't abort when file given via --file already exists")
    print("  -q [ --quiet ]                   suppress non-error messages")
    print("")
    print("EXAMPLES:")
    print("rethinkdb dump -c mnemosyne:39500")
    print("  Archive all data from a cluster running on host 'mnemosyne' with a client port at 39500.")
    print("")
    print("rethinkdb dump -e test -f rdb_dump.tar.gz")
    print("  Archive only the 'test' database from a local cluster into a named file.")
    print("")
    print("rethinkdb dump -c hades -e test.subscribers -p")
    print("  Archive a specific table from a cluster running on host 'hades' which requires a password.")

def parse_options():
    parser = OptionParser(add_help_option=False, usage=usage)
    parser.add_option("-c", "--connect", dest="host", metavar="host:port", default="localhost:28015", type="string")
    parser.add_option("-f", "--file", dest="out_file", metavar="file", default=None, type="string")
    parser.add_option("-e", "--export", dest="tables", metavar="(db | db.table)", default=[], action="append", type="string")

    parser.add_option("--tls-cert", dest="tls_cert", metavar="TLS_CERT", default="", type="string")

    parser.add_option("--temp-dir", dest="temp_dir", metavar="directory", default=None, type="string")
    parser.add_option("--overwrite-file", dest="overwrite_file", default=False, action="store_true")
    parser.add_option("--clients", dest="clients", metavar="NUM", default=3, type="int")
    parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true")
    parser.add_option("--debug", dest="debug", default=False, action="store_true")
    parser.add_option("-h", "--help", dest="help", default=False, action="store_true")
    parser.add_option("-p", "--password", dest="password", default=False, action="store_true")
    parser.add_option("--password-file", dest="password_file", default=None, type="string")
    (options, args) = parser.parse_args()

    # Check validity of arguments
    if len(args) != 0:
        raise RuntimeError("Error: No positional arguments supported. Unrecognized option '%s'" % args[0])

    if options.help:
        print_dump_help()
        exit(0)

    res = {}

    # Verify valid host:port --connect option
    (res["host"], res["port"]) = parse_connect_option(options.host)

    res["tls_cert"] = options.tls_cert

    # Verify valid output file
    if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
        res["temp_filename"] = "rethinkdb_dump_%s" % datetime.datetime.today().strftime("%Y-%m-%dT%H-%M-%S")
    else:
        res["temp_filename"] = "rethinkdb_dump_%s" % datetime.datetime.today().strftime("%Y-%m-%dT%H:%M:%S")

    if options.out_file == "-":
        res["out_file"] = sys.stdout
    else:
        # The output file is a real file in the file system
        if options.out_file is None:
            res["out_file"] = os.path.abspath("./" + res["temp_filename"] + ".tar.gz")
        else:
            res["out_file"] = os.path.abspath(options.out_file)

        if os.path.exists(res["out_file"]) and not options.overwrite_file:
            raise RuntimeError("Error: Output file already exists: %s" % res["out_file"])

    # Verify valid client count
    if options.clients < 1:
        raise RuntimeError("Error: invalid number of clients (%d), must be greater than zero" % options.clients)
    res["clients"] = options.clients

    # Make sure the temporary directory exists and is accessible
    res["temp_dir"] = options.temp_dir

    if res["temp_dir"] is not None:
        if not os.path.isdir(res["temp_dir"]):
            raise RuntimeError("Error: Temporary directory doesn't exist or is not a directory: %s" % res["temp_dir"])
        if not os.access(res["temp_dir"], os.W_OK):
            raise RuntimeError("Error: Temporary directory inaccessible: %s" % res["temp_dir"])

    res["tables"] = options.tables
    res["quiet"] = True if res["out_file"] is sys.stdout else options.quiet
    res["debug"] = options.debug
    res["password"] = options.password
    res["password-file"] = options.password_file
    return res

def do_export(temp_dir, options):
    if not options["quiet"]:
        print("Exporting to directory...")
    export_args = ["rethinkdb-export"]
    export_args.extend(["--connect", "%s:%s" % (options["host"], options["port"])])
    export_args.extend(["--directory", os.path.join(temp_dir, options["temp_filename"])])
    if options["password"]:
        export_args.append("--password")
    if options["password-file"]:
        export_args.extend(["--password-file", options["password-file"]])
    export_args.extend(["--clients", str(options["clients"])])
    export_args.extend(["--tls-cert", options["tls_cert"]])
    for table in options["tables"]:
        export_args.extend(["--export", table])

    if options["debug"]:
        export_args.extend(["--debug"])

    if options["quiet"]:
        export_args.extend(["--quiet"])

    res = subprocess.call(export_args)

    if res != 0:
        raise RuntimeError("Error: rethinkdb-export failed")

    # 'Done' message will be printed by the export script (unless options["quiet"])

def do_zip(temp_dir, options):
    if not options["quiet"]:
        print("Zipping export directory...")
    start_time = time.time()
    original_dir = os.getcwd()

    # Below,` tarfile.open()` forces us to set either `name` or `fileobj`,
    # depending on whether the output is a real file or an open file object.
    is_fileobj = type(options["out_file"]) is file
    name = None if is_fileobj else options["out_file"]
    fileobj = options["out_file"] if is_fileobj else None

    try:
        os.chdir(temp_dir)
        with tarfile.open(name=name, fileobj=fileobj, mode="w:gz") as f:
            for curr, subdirs, files in os.walk(options["temp_filename"]):
                for data_file in files:
                    path = os.path.join(curr, data_file)
                    f.add(path)
                    os.unlink(path)
    finally:
        os.chdir(original_dir)

    if not options["quiet"]:
        print("  Done (%d seconds)" % (time.time() - start_time))

def run_rethinkdb_export(options):
    # Create a temporary directory to store the intermediary results
    temp_dir = tempfile.mkdtemp(dir=options["temp_dir"])
    res = -1

    if not options["quiet"]:
        # Print a warning about the capabilities of dump, so no one is confused (hopefully)
        print("NOTE: 'rethinkdb-dump' saves data and secondary indexes, but does *not* save")
        print(" cluster metadata.  You will need to recreate your cluster setup yourself after ")
        print(" you run 'rethinkdb-restore'.")

    try:
        do_export(temp_dir, options)
        do_zip(temp_dir, options)
    except KeyboardInterrupt:
        time.sleep(0.2)
        raise RuntimeError("Interrupted")
    finally:
        shutil.rmtree(temp_dir)

def main():
    try:
        options = parse_options()
    except RuntimeError as ex:
        print("Usage: %s" % usage, file=sys.stderr)
        print(ex, file=sys.stderr)
        return 1

    try:
        start_time = time.time()
        run_rethinkdb_export(options)
    except RuntimeError as ex:
        print(ex, file=sys.stderr)
        return 1
    return 0

if __name__ == "__main__":
    exit(main())
