#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import json
import os
import re
import sqlite3
import subprocess as sp
import sys

from datetime import datetime

from globaleaks.db import get_db_file
from globaleaks.orm import make_db_uri, get_engine
from globaleaks.rest.requests import AdminNotificationDesc, AdminNodeDesc
from globaleaks.settings import Settings
from globaleaks.utils.crypto import GCE, generateRandomPassword


def check_file(f):
    if not os.path.isfile(f) or not os.access(f, os.R_OK):
        raise RuntimeError("Missing or inaccessible file: {}".format(f))


def check_dir(d):
    if not os.path.isdir(d):
        raise RuntimeError("Missing or inaccessible dir: {}".format(d))


def check_db(d):
    check_dir(args.workdir)

    db_version, db_path = get_db_file(args.workdir)

    if db_version <= 0:
        return

    check_file(db_path)

    return db_path


def default_backup_path():
    t = datetime.now().strftime("%y_%m_%d")
    name = "globaleaks_backup_{}.tar.gz".format(t)
    return os.path.join("/tmp", name)


def is_gl_running():
    try:
        with open(Settings.pidfile_path, 'r') as fd:
            gl_pid = int(fd.read())
            try:
                os.kill(gl_pid, 0)
            except OSError:
                return False
            return True
    except IOError:
        return False


def backup(args):
    workdir = args.workdir
    check_dir(workdir)

    must_stop = is_gl_running()

    if must_stop: sp.check_call("service globaleaks stop", shell=True)
    print("Creating an archive backup of the globaleaks setup. . .")
    p_head, p_tail = os.path.split(args.workdir)

    sp.check_call(["tar", "-zcf", args.backuppath, "--exclude='backups'", "-C", args.workdir, '.'])

    if must_stop: sp.check_call("service globaleaks start", shell=True)
    print("Success: The archived backup was created at:", args.backuppath)


def restore(args):
    check_dir(args.workdir)

    check_file(args.backuppath)

    print("\n", "-"*72)
    print("WARNING this command will DELETE everything currently in {}".format(args.workdir))
    print("-"*72)
    ans = input("Are you sure that you want to continue? [y/n] ")
    if not ans == "y":
        sys.exit(0)
    print("-"*72)

    must_stop = is_gl_running()
    if must_stop: sp.check_call("service globaleaks stop", shell=True)
    print("Deleting {} . . .".format(args.workdir))

    p_head, p_tail = os.path.split(args.workdir)

    # TODO remove workdir str fmt
    sp.check_call("find %s -type f -exec shred -vzn 3 {} \;" % args.workdir, shell=True)

    print("Extracting the archive {}".format(args.backuppath))
    sp.check_call(["tar", "-xf", args.backuppath, "-C", args.workdir])

    if must_stop: sp.check_call("service globaleaks start", shell=True)

    print("Success! globaleaks has been restored from a backup")


def reset_pass(args):
    db_path = check_db(args.workdir)

    salt = GCE.generate_salt()

    hashed_password = GCE.hash_password(args.password, salt)

    QUERY = "UPDATE user SET salt=?, password=?, hash_alg=? WHERE username=? AND tid=? AND crypto_pub_key=?;"

    conn = sqlite3.connect(db_path)
    c = conn.cursor()
    c.execute(QUERY, (salt, hashed_password, "ARGON2", args.username, args.tid, ''))

    if c.rowcount != 1:
        print("Failed! The user '{}' does not exist or encryption key is set".format(args.username))
        sys.exit(1)

    conn.commit()
    conn.close()

    print(("=========================================\n"
           "||      Password reset completed       ||\n"
           "=========================================\n"
           "     Username: {}\n"
           "     Password: {}\n"
           "=========================================\n"
         ).format(args.username, args.password))


def get_var(args):
    db_path = check_db(args.workdir)

    QUERY = "SELECT value FROM config WHERE var_name=? AND tid=?;"

    conn = sqlite3.connect(db_path)
    c = conn.cursor()
    c.execute(QUERY, (args.varname, args.tid))
    ret = c.fetchone()
    if ret is None:
        print("Failed to read value of var '{}'.".format(args.varname))
        sys.exit(1)

    conn.close()

    print(json.loads(str(ret[0])))


def set_var(args, silent=False):
    db_path = check_db(args.workdir)

    if args.value == 'True':
        args.value = True
    elif args.value == 'False':
        args.value = False

    value = json.dumps(args.value)

    QUERY = "UPDATE config SET value=? WHERE var_name=? AND tid=?;"

    conn = sqlite3.connect(db_path)
    c = conn.cursor()
    c.execute(QUERY, (value, args.varname, args.tid))
    conn.commit()
    conn.close()

    if not silent:
        print("Success! {} set to '{}'".format(args.varname, args.value))


def add_workingdir_path_arg(parser):
    parser.add_argument("-w",
                        "--workdir",
                        help="the directory that hosts the globaleaks data storage",
                        default=Settings.working_path)

Settings.eval_paths()

parser = argparse.ArgumentParser(prog="gl-admin",
                 description="GlobaLeaks backend administator interface")

subp = parser.add_subparsers(title="commands")

bck_p = subp.add_parser("backup", help="create a backup of the setup")
add_workingdir_path_arg(bck_p)
bck_p.add_argument("backuppath", nargs="?", help="the path and name of the backup",
                   default=default_backup_path())
bck_p.set_defaults(func=backup)

res_p = subp.add_parser("restore", help="restore a backup of the setup")
add_workingdir_path_arg(res_p)
res_p.add_argument("backuppath", nargs="?", help="the path and name of the backup",
                   default=default_backup_path())
res_p.set_defaults(func=restore)

pw_p = subp.add_parser("resetpass", help="reset a user's password")
add_workingdir_path_arg(pw_p)
pw_p.add_argument("--tid", help="the tenant id", default='1', type=int)
pw_p.add_argument("username")
pw_p.add_argument("password", nargs="?", help="if not set a random password is generated",
                  default=generateRandomPassword(16))
pw_p.set_defaults(func=reset_pass)

rv_p = subp.add_parser("getvar", help="get database config variable")
add_workingdir_path_arg(rv_p)
rv_p.add_argument("--tid", help="the tenant id", default='1', type=int)
rv_p.add_argument("varname", help="the name of the config var", default='version', type=str)
rv_p.set_defaults(func=get_var)

sv_p = subp.add_parser("setvar", help="set database config variable")
add_workingdir_path_arg(sv_p)
sv_p.add_argument("--tid", help="the tenant id", default='1', type=int)
sv_p.add_argument("varname", help="the name of the config var", type=str)
sv_p.add_argument("value", help="value which must be of the correct type Bool(0|1), Int(0-9^9), String(everything else)")
sv_p.set_defaults(func=set_var)

if __name__ == '__main__':
    try:
        args = parser.parse_args()
        if hasattr(args, 'func'):
            args.func(args)
        else:
            parser.print_help()

    except Exception as exc:
        print("ERROR: {}".format(exc))
