#!/usr/bin/python3.6
import argparse
import grp
import os
import pwd
import sys
import time
from typing import List, Optional
from pathlib import Path
from subprocess import list2cmdline

from retrace.retrace import (ALLOWED_FILES,
                             STATUS_FAIL,
                             STATUS_SUCCESS,
                             TASK_COREDUMP_INTERACTIVE,
                             TASK_VMCORE_INTERACTIVE,
                             KernelVMcore,
                             KernelVer,
                             RetraceTask)
from retrace.config import Config

CONFIG = Config()

ACTIONS = ["shell", "gdb", "crash", "printdir", "delete", "set-success", "set-fail"]

def print_cmdline(cmd_line: List[str]):
    sys.stderr.write("If you want to execute the command manually, you can run\n")
    sys.stderr.write("$ %s\n\n" % list2cmdline(cmd_line))

if __name__ == "__main__":
    groups = []
    for g in os.getgroups():
        try:
            group = grp.getgrgid(g)
            groups.append(group.gr_name)
        except KeyError:
            pass
    if not CONFIG["AuthGroup"] in groups:
        sys.stderr.write("You must be a member '%s' group in order to use "
                         "interactive debugging.\n" % CONFIG["AuthGroup"])
        sys.exit(1)

    parser = argparse.ArgumentParser(description="Interact with retrace-server's chroot")
    parser.add_argument("task_id", help="Task ID")
    parser.add_argument("action", help="Desired action (%s)" % "|".join(ACTIONS))
    parser.add_argument("-f", "--force", action="store_true", default=False,
                        help="Run `set-success` or `set-fail` action even if the task is still running")
    args = parser.parse_args()

    if args.action not in ACTIONS:
        sys.stderr.write("Invalid action. Allowed actions are: '%s'.\n" % "', '".join(ACTIONS))
        sys.exit(1)

    try:
        taskid = int(args.task_id)
        task = RetraceTask(taskid)
    except Exception as ex:
        sys.stderr.write("%s\n" % ex)
        sys.exit(1)

    # touch the task directory
    task.reset_age()

    if args.action == "printdir":
        sys.stdout.write("%s\n" % task.get_savedir())
        sys.exit(0)

    if args.action == "delete":
        username = pwd.getpwuid(os.getuid()).pw_name

        if CONFIG["TaskManagerAuthDelete"] and \
           username not in CONFIG["TaskManagerDeleteUsers"]:
            sys.stderr.write("You are not allowed to delete tasks\n")
            sys.exit(1)

        task.remove()
        sys.exit(0)

    if args.action == "set-success":
        if task.is_running() and not args.force:
            sys.stderr.write("The task is still running and the effect of this "
                             "action will most probably be overwritten. If you "
                             "want to execute it anyway, use --force.\n")
            sys.exit(0)

        task.set_status(STATUS_SUCCESS)
        if not task.has_finished_time():
            task.set_finished_time(int(time.time()))
        sys.exit(0)

    if args.action == "set-fail":
        if task.is_running() and not args.force:
            sys.stderr.write("The task is still running and the effect of this "
                             "action will most probably be overwritten. If you "
                             "want to execute it anyway, use --force.\n")
            sys.exit(0)

        task.set_status(STATUS_FAIL)
        if not task.has_finished_time():
            task.set_finished_time(int(time.time()))
        sys.exit(0)

    if task.get_type() == TASK_COREDUMP_INTERACTIVE:
        if args.action == "shell":
            cmdline = ["/usr/bin/mock", "--configdir", str(task.get_savedir()), "shell"]
            print_cmdline(cmdline)
            os.execvp(cmdline[0], cmdline)
        if args.action == "gdb":
            with Path(task.get_savedir(), "crash", "executable").open() as exec_file:
                executable = exec_file.read(ALLOWED_FILES["executable"])
            if "'" in executable or '"' in executable:
                sys.stderr.write("executable contains forbidden characters.\n")
                sys.exit(1)

            cmdline = ["/usr/bin/mock", "--configdir", str(task.get_savedir()), "shell",
                       "gdb '%s' /var/spool/abrt/crash/coredump" % executable]

            print_cmdline(cmdline)
            os.execvp(cmdline[0], cmdline)

        sys.stderr.write("Action '%s' is not allowed for coredumps.\n" % args.action)
        sys.exit(1)

    elif task.get_type() == TASK_VMCORE_INTERACTIVE:
        task.find_vmcore_file()
        vmcore_path = task.get_vmcore_path()
        vmcore = KernelVMcore(vmcore_path)

        if task.has_kernelver():
            kv = task.get_kernelver()
            # Assured by has_kernelver() returning True.
            assert kv is not None
            kernelver: Optional[KernelVer] = KernelVer(kv)
        else:
            crash_cmd = task.get_crash_cmd()
            assert crash_cmd is not None
            kernelver = vmcore.get_kernel_release(crash_cmd.split())
            if kernelver is None:
                raise Exception("Unable to determine kernel version")
            task.set_kernelver(kernelver)

        assert kernelver is not None

        hostarch = os.uname()[4]
        if hostarch in ["i486", "i586", "i686"]:
            hostarch = "i386"

        if args.action == "crash":
            if task.has_vmlinux():
                vmlinux = task.get_vmlinux()
            else:
                if task.has_status() and \
                   task.get_status() not in [STATUS_SUCCESS, STATUS_FAIL]:
                    sys.stderr.write("Task '%s' still in progress or hung (status = %d), "
                                     "please wait for task to complete.\n" %
                                     (task.get_taskid(), task.get_status()))
                    sys.stderr.write("If you suspect task is hung and will never "
                                     "complete, try forcing failure with "
                                     "retrace-server-interact, and restart the task.")
                    sys.exit(1)
                else:
                    raise Exception("Task '%s' complete but no vmlinux.\n"
                                    "Try restarting or resubmitting the task.\n" %
                                    task.get_taskid())

            if task.has_mock():
                cfgdir = os.path.join(CONFIG["SaveDir"], "%d-kernel" % task.get_taskid())
                if task.has_crashrc():
                    cmdline = ["/usr/bin/mock", "--configdir", cfgdir,
                               "shell", "crash -i %s %s %s" % (task.get_crashrc_path(), vmcore_path, vmlinux)]
                else:
                    cmdline = ["/usr/bin/mock", "--configdir", cfgdir,
                               "shell", "crash %s %s" % (vmcore_path, vmlinux)]
            else:
                crash_cmd = task.get_crash_cmd()
                if crash_cmd is None:
                    raise Exception("Unable to determine crash command")
                if task.has_crashrc():
                    cmdline = crash_cmd.split() + ["-i", str(task.get_crashrc_path()),
                                                   str(vmcore_path), vmlinux]
                else:
                    cmdline = crash_cmd.split() + [str(vmcore_path), vmlinux]

            print_cmdline(cmdline)
            os.execvp(cmdline[0], cmdline)

        if args.action == "shell":
            if task.has_mock():
                cmdline = ["/usr/bin/mock",
                           "--configdir",
                           os.path.join(CONFIG["SaveDir"], "%d-kernel" % task.get_taskid()),
                           "shell"]

                print_cmdline(cmdline)
                os.execvp(cmdline[0], cmdline)

            sys.stderr.write("The task does not require a chroot. You can use the current shell.\n")
            sys.exit(1)

        sys.stderr.write("Action '%s' is not allowed for vmcores.\n" % args.action)
        sys.exit(1)
    else:
        sys.stderr.write("The specified task was not intended for interactive debugging.\n")
        sys.exit(1)
