#!/usr/bin/env python

""" freshmaker-cli - Read-only client for Freshmaker REST API


Example usage:
    - List all the events handled by Freshmaker on redhat staging instance:
        ./freshmaker-cli --redhat --stage
    - List particular events based on the event id:
        ./freshmaker-cli --redhat --stage 1 2 3
    - Search for events matching particular event key:
        ./freshmaker-cli --redhat --stage --search-key 29110
    - Search for events which lead built particular NVR in Koji:
        ./freshmaker-cli --redhat --stage --rebuilt-nvr NVR
    - Search for events which upated particular NVR:
        ./freshmaker-cli --redhat --stage --original-nvr NVR
    - Search for events building particular artifact name
        ./freshmaker-cli --redhat --stage --name httpd24-docker
    - Search for events in particular state
        ./freshmaker-cli --redhat --stage --state skipped
"""

from __future__ import print_function

import textwrap
import argparse
import sys
import requests
from tabulate import tabulate
from kobo.rpmlib import parse_nvr

# Disable InsecureRequestWarning
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


EVENT_TYPES = [
    "Module build state changed",
    "Module git definition changed",
    "RPM git definition changed",
    "Internal Freshmaker event type",
    "Container image git definition changed",
    "Bodhi update added to stable",
    "Koji task state changed",
    "Koji RPM signed",
    "Errata advisory RPMS signed",
    "Brew container task state changed",
    "Errata advisory state changed",
    "Manual rebuild triggerred",
    "ODCS compose state changed",
]


EVENT_STATES = [
    "init",
    "building",
    "complete",
    "failed",
    "skipped",
    ]


def get_api_url(deployment, env):
    API_URLS = {
        "redhat": {
            "prod": "https://freshmaker.host.prod.eng.bos.redhat.com",
            "stage": "https://freshmaker.host.stage.eng.bos.redhat.com",
            "qe": "https://freshmaker.host.qe.eng.pek2.redhat.com",
            "dev": "https://freshmaker.host.dev.eng.pek2.redhat.com",
        },
        "fedora": {}
    }

    if deployment not in API_URLS:
        return None

    envs = API_URLS[deployment]
    if env not in envs:
        return None

    return envs[env]


def get_events_from_builds(api_url, rebuilt_nvr=None, original_nvr=None,
                           name=None):
    page = 1
    ids = []
    while True:
        print("Getting messages from Freshmaker, page", page)
        url = '%s/api/1/builds?page=%d&per_page=100' % (api_url, page)
        if rebuilt_nvr:
            url += "&rebuilt_nvr=" + rebuilt_nvr
        if original_nvr:
            url += "&original_nvr=" + original_nvr
        if name:
            url += "&name=" + name

        r = requests.get(url, verify=False)
        page += 1
        ret = r.json()
        items = ret["items"]
        if not items:
            break
        for item in items:
            if item["event_id"] not in ids:
                ids.append(item["event_id"])

    return get_freshmaker_events_by_ids(api_url, ids)


def get_freshmaker_events(api_url, search_key=None, rebuilt_nvr=None,
                          original_nvr=None, name=None, states=None):
    if rebuilt_nvr or original_nvr or name:
        return get_events_from_builds(api_url, rebuilt_nvr, original_nvr,
                                      name)

    page = 1
    events = []
    while True:
        print("Getting messages from Freshmaker, page", page)
        url = '%s/api/1/events?page=%d&per_page=100' % (api_url, page)
        if search_key:
            url += "&search_key=" + search_key

        r = requests.get(url, verify=False)
        page += 1
        ret = r.json()
        items = ret["items"]
        if not items:
            break
        events += items

    if states:
        events = filter(
            lambda x: EVENT_STATES[x["state"]] in states, events)

    events = sorted(events, key=lambda k: k['id'])
    return events


def get_freshmaker_events_by_ids(api_url, ids):
    events = []
    for i in ids:
        print("Getting Freshmaker event id", i)
        url = '%s/api/1/events/%s' % (api_url, str(i))

        r = requests.get(url, verify=False)
        ret = r.json()
        if not "error" in ret:
            events += [ret]
        else:
            print(ret["error"])

    events = sorted(events, key=lambda k: k['id'])
    return events


def print_events(events):
    table = [["ID", "Event type", "State", "Event key", "Number of builds", "State reason"]]
    for event in events:
        row = [event["id"], EVENT_TYPES[event["event_type_id"]],
               EVENT_STATES[event["state"]], event["search_key"][:20],
               len(event["builds"]), event["state_reason"]]
        table.append(row)
    print(tabulate(table, headers="firstrow"))


def make_tree_dict(child_parent_mapping):
    """
    Takes the list of tuples with child-parent mapping and constructs a tree
    represented by nested dict.
    """
    has_parent = set()
    all_items = {}
    for child, parent in child_parent_mapping:
        if parent not in all_items:
            all_items[parent] = {}
        if child not in all_items:
            all_items[child] = {}
        all_items[parent][child] = all_items[child]
        has_parent.add(child)

    result = {}
    for key, value in all_items.items():
        if key not in has_parent:
            result[key] = value
    return result


def print_detailed_events(events, states=None):
    """
    Prints detailed information about events - this mainly means the list of
    artifacts build as part of each event.
    """
    for event in events:
        print("Event id:", event["id"])

        # Temporary list with child-parent mapping between the builds.
        builds_mapping = []
        # Temporary dict to find build based on rebuilt_nvr.
        builds_by_nvr = {}
        for build in event["builds"]:
            builds_mapping.append(
                (build["rebuilt_nvr"], build["build_args"]["parent"]))
            builds_by_nvr[build["rebuilt_nvr"]] = build

        tree = make_tree_dict(builds_mapping)

        def _prepare_table(subtree, level = 0):
            """
            Function called recursively for each level of tree generating the
            rows with builds which can be later consumed by `tabulate()`
            method.
            """
            rows = []
            for nvr, children in subtree.items():
                if nvr in builds_by_nvr:
                    build = builds_by_nvr[nvr]

                    if states:
                        if build['state_name'].lower() not in states:
                            continue

                    nvr = parse_nvr(build["original_nvr"])
                    new_nvr = parse_nvr(build["rebuilt_nvr"])

                    padding = "-" * (level * 2) + ">" + " " * (8 - level * 2)
                    row = [padding + str(build["id"]), build["build_id"],
                           build["state_name"], nvr["name"],
                           "%s-%s" % (nvr["version"], nvr["release"]),
                           "%s-%s" % (new_nvr["version"], new_nvr["release"]),
                           build["state_reason"]]
                    rows.append(row)
                rows += _prepare_table(children, level + 1)

            return rows

        table = [["Build ID", "Task ID", "State", "Name", "NVR",
                  "Rebuilt NVR", "State Reason"]]
        table += _prepare_table(tree)

        print(tabulate(table, headers="firstrow"))


parser = argparse.ArgumentParser(
    description=textwrap.dedent(__doc__),
    formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
    '--stage', default=False, action='store_true',
    help="Use the staging environment.")
parser.add_argument(
    '--qe', default=False, action='store_true',
    help="Use the qe environment.")
parser.add_argument(
    '--dev', default=False, action='store_true',
    help="Use the dev environment.")
parser.add_argument(
    '--redhat', default=False, action='store_true',
    help="Use the redhat instances.")
parser.add_argument(
    'events', metavar='event', nargs='*',
    help='Event ids to be queried')
parser.add_argument(
    '--search-key', default=None,
    help="Search for events matching the search_key")
parser.add_argument(
    '--rebuilt-nvr', default=None,
    help="NVR of rebuilt artifact to search for")
parser.add_argument(
    '--original-nvr', default=None,
    help="NVR of original artifact to search for")
parser.add_argument(
    '--name', default=None,
    help="Name of artifact to search for")
parser.add_argument(
    '--state', default=None, action="append",
    help="Limit the query only for events/builds in given state")
args = parser.parse_args()

if args.redhat:
    deployment = "redhat"
else:
    deployment = "fedora"

if args.stage:
    env = "stage"
elif args.qe:
    env = "qe"
elif args.dev:
    env = "dev"
else:
    env = "prod"

api_url = get_api_url(deployment, env)
if not api_url:
    print("There is no Freshmaker instance for deployment %s and env %s" %( deployment, env))
    sys.exit(1)

if not args.events:
    if not args.state:
        args.state = ["init", "building", "complete", "failed"]
    events = get_freshmaker_events(api_url, search_key=args.search_key,
                                   rebuilt_nvr=args.rebuilt_nvr,
                                   original_nvr=args.original_nvr,
                                   name=args.name, states=args.state)
    print_events(events)
else:
    events = get_freshmaker_events_by_ids(api_url, args.events)
    print_detailed_events(events, states=args.state)
