import collections
import json
import pprint
import sys

from .. import util
from ..provisioner.testingfarm import api as tf


def _get_api(args):
    api_args = {}
    if args.url:
        api_args["url"] = args.url
    if args.token:
        api_args["token"] = args.token
    return tf.TestingFarmAPI(**api_args)


def about(args):
    api = _get_api(args)
    pprint.pprint(api.about())


def whoami(args):
    api = _get_api(args)
    pprint.pprint(api.whoami())


def composes(args):
    api = _get_api(args)
    comps = api.composes(ranch=args.ranch)
    comps_list = comps["composes"]
    for comp in comps_list:
        if comp["type"] == "compose":
            print(comp["name"])


def get_request(args):
    api = _get_api(args)
    request = tf.Request(args.request_id, api=api)
    print(str(request))


def cancel(args):
    api = _get_api(args)
    api.cancel_request(args.request_id)


def search_requests(args):
    api = _get_api(args)

    func_kwargs = {
        "mine": not args.all,
        "user_id": args.user_id,
        "token_id": args.token_id,
        "ranch": args.ranch,
        "created_before": args.before,
        "created_after": args.after,
    }

    if args.page is not None:
        reply = api.search_requests_paged(
            state=args.state,
            page=args.page,
            **func_kwargs,
        )
        if not reply:
            return
    else:
        reply = api.search_requests(
            state=args.state,
            **func_kwargs,
        )
        if not reply:
            return
        reply = sorted(reply, key=lambda x: x["created"])

    if args.json:
        for req in reply:
            print(json.dumps(req))
        return

    for req in reply:
        req_id = req["id"]
        created = req["created"].partition(".")[0]

        if "fmf" in req["test"] and req["test"]["fmf"]:
            test = req["test"]["fmf"]["url"]
        elif "tmt" in req["test"] and req["test"]["tmt"]:
            test = req["test"]["tmf"]["url"]
        else:
            test = ""

        envs = []
        for env in req["environments_requested"]:
            if "os" in env and env["os"] and "compose" in env["os"]:
                compose = env["os"]["compose"]
                arch = env["arch"]
                if compose and arch:
                    envs.append(f"{compose}@{arch}")

        print(f"{created} {req_id}", end="")
        if test:
            print(f" | test:{test}", end="")
        if envs:
            print(f" | envs:[{', '.join(envs)}]", end="")
        print()


def stats(args):
    api = _get_api(args)

    def top_users_repos(requests):
        tokens = collections.defaultdict(int)
        repos = collections.defaultdict(int)
        for req in requests:
            tokens[req["token_id"]] += 1
            if "fmf" in req["test"] and req["test"]["fmf"]:
                repos[req["test"]["fmf"]["url"]] += 1
            elif "tmt" in req["test"] and req["test"]["tmt"]:
                repos[req["test"]["tmt"]["url"]] += 1

        top_tokens = sorted(tokens, key=lambda x: tokens[x], reverse=True)[:10]
        top_repos = sorted(repos, key=lambda x: repos[x], reverse=True)[:10]
        if not top_tokens or not top_repos:
            return
        digits = max(len(str(tokens[top_tokens[0]])), len(str(repos[top_repos[0]])))

        print("Top 10 token IDs:")
        for token_id in top_tokens:
            count = tokens[token_id]
            print(f"{count:>{digits}}  {token_id}")

        print("Top 10 repo URLs:")
        for repo_url in top_repos:
            count = repos[repo_url]
            print(f"{count:>{digits}}  {repo_url}")

    def request_search_results():
        func_kwargs = {}
        if args.before is not None:
            func_kwargs["created_before"] = args.before
        if args.after is not None:
            func_kwargs["created_after"] = args.after

        if args.page is not None:
            for state in args.states.split(","):
                reply = api.search_requests_paged(
                    state=state,
                    page=args.page,
                    mine=False,
                    ranch=args.ranch,
                    **func_kwargs,
                )
                if reply:
                    yield from reply
        else:
            for state in args.states.split(","):
                reply = api.search_requests(
                    state=state,
                    mine=False,
                    ranch=args.ranch,
                    **func_kwargs,
                )
                if reply:
                    yield from reply

    top_users_repos(request_search_results())


def reserve(args):
    print(f"Reserving {args.compose} on {args.arch} for {args.timeout} minutes")

    if args.hvm:
        hardware = {"virtualization": {"is-supported": True}}
    else:
        hardware = None

    if args.native_test:
        test = tf.DEFAULT_RESERVE_TEST.copy()
        test["name"] = "/plans/testing-farm-native"
    else:
        test = None

    api = _get_api(args)
    res = tf.Reserve(
        compose=args.compose,
        arch=args.arch,
        timeout=args.timeout,
        hardware=hardware,
        reserve_test=test,
        api=api,
    )
    with res as m:
        print(f"Got machine: {m}")
        while True:
            try:
                res.request.assert_alive()
            except tf.GoneAwayError as e:
                print(e)
                raise SystemExit(1) from None

            proc = util.subprocess_run([
                "ssh", "-q", "-i", m.ssh_key,
                "-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null",
                f"{m.user}@{m.host}",
            ])
            if proc.returncode != 0:
                print(
                    f"\nssh -i {str(m.ssh_key)} {m.user}@{m.host}\n"
                    f"terminated with exit code {proc.returncode}\n",
                )
                try:
                    input("Press RETURN to try to reconnect, Ctrl-C to quit ...")
                except KeyboardInterrupt:
                    print()
                    break
            else:
                break


def watch_pipeline(args):
    api = _get_api(args)
    request = tf.Request(id=args.request_id, api=api)

    print(f"Waiting for {args.request_id} to be 'running'")
    try:
        request.wait_for_state("running")
    except tf.GoneAwayError:
        print(f"Request {args.request_id} already finished")
        return

    print("Querying pipeline.log")
    try:
        for line in tf.PipelineLogStreamer(request):
            sys.stdout.write(line)
            sys.stdout.write("\n")
    except tf.GoneAwayError:
        print(f"Request {args.request_id} finished, exiting")


def parse_args(parser):
    parser.add_argument("--url", help="Testing Farm API URL")
    parser.add_argument("--token", help="Testing Farm API auth token")
    cmds = parser.add_subparsers(
        dest="_cmd", help="TF helper to run", metavar="<cmd>", required=True,
    )

    cmd = cmds.add_parser(
        "whoami",
        help="print out details about active TF token",
    )
    cmd = cmds.add_parser(
        "about",
        help="print out details about TF instance (url)",
    )

    cmd = cmds.add_parser(
        "composes",
        help="list all composes available on a given ranch",
    )
    cmd.add_argument("ranch", nargs="?", help="Testing Farm ranch (autodetected if token)")

    cmd = cmds.add_parser(
        "get-request", aliases=("gr",),
        help="retrieve and print JSON of a Testing Farm request",
    )
    cmd.add_argument("request_id", help="Testing Farm request UUID")

    cmd = cmds.add_parser(
        "cancel",
        help="cancel a Testing Farm request",
    )
    cmd.add_argument("request_id", help="Testing Farm request UUID")

    cmd = cmds.add_parser(
        "search-requests", aliases=("sr",),
        help="return a list of requests matching the criteria",
    )
    cmd.add_argument("--state", help="request state (running, etc.)", required=True)
    cmd.add_argument("--all", help="all requests, not just owned by token", action="store_true")
    cmd.add_argument("--ranch", help="Testing Farm ranch (detected from token)")
    cmd.add_argument("--user-id", help="'user_id' request field (detected from token)")
    cmd.add_argument("--token-id", help="'token_id' request field (detected from token)")
    cmd.add_argument("--before", help="only requests created before ISO8601")
    cmd.add_argument("--after", help="only requests created after ISO8601")
    cmd.add_argument("--json", help="full details, one request per line", action="store_true")
    cmd.add_argument("--page", help="do paged search, page interval in secs", type=int)

    cmd = cmds.add_parser(
        "stats",
        help="print out TF usage statistics",
    )
    cmd.add_argument("--before", help="only requests created before ISO8601")
    cmd.add_argument("--after", help="only requests created after ISO8601")
    cmd.add_argument("--page", help="do paged search, page interval in secs", type=int)
    cmd.add_argument("ranch", help="Testing Farm ranch name")
    cmd.add_argument("states", help="comma-separated TF request states")

    cmd = cmds.add_parser(
        "reserve",
        help="reserve a system and ssh into it",
    )
    cmd.add_argument("--compose", "-c", help="OS compose to install", required=True)
    cmd.add_argument("--arch", "-a", help="system HW architecture", default="x86_64")
    cmd.add_argument("--timeout", "-t", help="pipeline timeout (in minutes)", type=int, default=60)
    cmd.add_argument("--ssh-key", help="path to a ssh private key file like 'id_rsa'")
    cmd.add_argument("--hvm", help="request a HVM virtualization capable HW", action="store_true")
    cmd.add_argument(
        "--native-test",
        help="use the default testing farm reserve test",
        action="store_true",
    )

    cmd = cmds.add_parser(
        "watch-pipeline", aliases=("wp",),
        help="continuously output pipeline.log like 'tail -f'",
    )
    cmd.add_argument("request_id", help="Testing Farm request UUID")


def main(args):
    if args._cmd == "whoami":
        whoami(args)
    elif args._cmd == "about":
        about(args)
    elif args._cmd == "composes":
        composes(args)
    elif args._cmd in ("get-request", "gr"):
        get_request(args)
    elif args._cmd == "cancel":
        cancel(args)
    elif args._cmd in ("search-requests", "sr"):
        search_requests(args)
    elif args._cmd == "stats":
        stats(args)
    elif args._cmd == "reserve":
        reserve(args)
    elif args._cmd in ("watch-pipeline", "wp"):
        watch_pipeline(args)
    else:
        raise RuntimeError(f"unknown args: {args}")


CLI_SPEC = {
    "aliases": ("tf",),
    "help": "various utils for Testing Farm",
    "args": parse_args,
    "main": main,
}
