#!/usr/bin/env python3

import argparse
import json
import logging
import os
import platform
import sys

import termcolor

try:
    import paperwork.frontend.shell
    FRONTEND_COMMANDS = paperwork.frontend.shell.COMMANDS
except:
    FRONTEND_COMMANDS = {}
try:
    import paperwork_backend.shell
    BACKEND_COMMANDS = paperwork_backend.shell.COMMANDS
except:
    BACKEND_COMMANDS = {}


PACKAGE_TOOLS = {
    'debian': 'apt install',
    'fedora': 'yum install',
    'gentoo': 'emerge',
    'linuxmint': 'apt install',
    'ubuntu': 'apt install',
    'suse': 'zypper in',
}


isatty = os.isatty(sys.stdout.fileno())
verbose_enabled = False

interactive = True


def colored(txt, color):
    if isatty:
        return termcolor.colored(txt, color)
    return txt


def verbose(msg):
    if verbose_enabled:
        print(msg)


def warning(msg):
    print("[%s] %s" % (colored("WARN", "yellow"), msg))


def error(msg):
    print("[%s] %s" % (colored("ERROR", "red"), msg))


def get_distribution():
    distribution = platform.dist()
    verbose("Detected system: {}".format(" ".join(distribution)))
    distribution = distribution[0].lower()
    if distribution not in PACKAGE_TOOLS:
        warning("Unknown distribution. Can't suggest packages to install")
    return distribution


def _chkdeps(module_name, distribution):
    try:
        module = __import__(
            module_name + ".deps", globals(), locals(),
            [module_name.split('.')[-1]]
        )
    except ImportError as exc:
        error("Unable to import {}: {}".format(module_name, exc))
        sys.exit(1)
    if not hasattr(module, "find_missing_dependencies"):
        error("{} is not a Paperwork module".format(module_name))
        sys.exit(1)
    missing = module.find_missing_dependencies()

    verbose("")
    if len(missing) <= 0:
        print("All dependencies have been " + colored("found", "green") + ".")
        sys.exit(0)

    print("")
    print(colored("WARNING", "yellow") + ": Missing dependencies:")
    pkgs = []
    for dep in missing:
        if distribution in dep[2]:
            print("  - %s (python module: %s ; %s package: %s)"
                  % (dep[0], dep[1], distribution, dep[2][distribution]))
            pkgs.append(dep[2][distribution])
        else:
            print("  - %s (python module: %s ; Debian package: %s)"
                  % (dep[0], dep[1], dep[2]['debian']))

    if len(pkgs) > 0 and distribution in PACKAGE_TOOLS:
        command = "sudo %s %s" % (
            PACKAGE_TOOLS[distribution], " ".join(pkgs)
        )
        print("")
        print(colored("Suggested command", "yellow") + ":")
        print("  %s" % command)

        if ">" in command:
            # means there are package for which we don't know the exact name
            # so we can't run the command
            sys.exit(1)

        answer = "n"
        if interactive:
            print("Do you want to run this command now ? [Y/n]")
            answer = sys.stdin.readline().strip().lower()
        if answer == "" or answer == "y":
            r = os.system(command)
            sys.exit(r)


def chkdeps(*args):
    """
    Arguments: <component1> [<component2> [...]]]

    Look for missing dependencies and tries to suggest a command to install
    them all using the local distribution package manager (APT, DNF, etc).

    Examples:
        paperwork-shell chkdeps paperwork_backend
        paperwork-shell chkdeps paperwork
        paperwork-shell chkdeps paperwork_backend paperwork
    """
    module_names = args
    if len(module_names) <= 0:
        error("No module specified")
        return
    distribution = get_distribution()
    for module_name in module_names:
        _chkdeps(module_name, distribution)


def cmd_help(*args):
    """
    Arguments: [<command>]
    List available commands
    """
    if len(args) == 0:
        print("Possible commands are:")
        print("")
        for cmd_name in sorted(COMMANDS.keys()):
            cmd_func = COMMANDS[cmd_name]
            print("{}:".format(cmd_name))
            print("=" * (len(cmd_name) + 1))
            print("    {}".format(cmd_func.__doc__.strip()))
            print("")
    else:
        cmd_name = args[0]
        cmd_func = COMMANDS[cmd_name]
        print("{}:".format(cmd_name))
        print("=" * (len(cmd_name) + 1))
        print("    {}".format(cmd_func.__doc__.strip()))


COMMANDS = {
    "chkdeps": chkdeps,
    "help": cmd_help,
}
COMMANDS.update(FRONTEND_COMMANDS)
COMMANDS.update(BACKEND_COMMANDS)


def main():
    parser = argparse.ArgumentParser(
        description='Paperwork shell'
    )
    parser.add_argument(
        'cmd', metavar="command", type=str,
        help=(
            "Command. Can be: {} (use 'help <command>' for details)"
            .format(", ".join(COMMANDS.keys()))
        )
    )
    parser.add_argument(
        'cmd_args', metavar="arg", type=str, nargs="*",
        help="Command arguments (use 'help <command>' for details)"
    )
    parser.add_argument('-q', dest="quiet",
                        action='store_true',
                        help="quiet mode (JSON reply only)")
    parser.add_argument('-b', dest="batch",
                        action='store_true',
                        help="batch mode (never ask any question)")
    args = parser.parse_args()
    verbose_enabled = not args.quiet
    interactive = not args.batch

    os.environ['PAPERWORK_SHELL_VERBOSE'] = "True" if verbose_enabled else ""
    os.environ['PAPERWORK_INTERACTIVE'] = "True" if interactive else ""

    if not verbose_enabled:
        # hide warnings. They could mess output parsing
        logging.getLogger().setLevel(logging.ERROR)

    if args.cmd not in COMMANDS:
        print("Unknown command {}".format(args.cmd))
        cmd_help()
        sys.exit(1)

    try:
        sys.exit(COMMANDS[args.cmd](*args.cmd_args))
    except Exception as exc:
        print (json.dumps(
            {
                "status": "error",
                "exception": str(type(exc)),
                "args": str(exc.args),
                "reason": str(exc),
            },
            indent=4,
            separators=(',', ': '),
            sort_keys=True
        ))
        if verbose_enabled:
            raise
        sys.exit(5)

if __name__ == "__main__":
    main()
