#!/usr/bin/python3
"""
Query bugzilla for component bugs and search through comments for
trigger words.  Output results to a log and for tasks found in the
comments set bugzillano field with bugzilla ids.
"""

import sys
import re
import time
from datetime import datetime, timedelta

from typing import Dict, List, Set
from pathlib import Path

import bugzilla

from retrace.retrace import BUGZILLA_STATUS, RetraceTask
from retrace.config import Config

CONFIG = Config()
BUGZILLA_QUERY_LIMIT = 500

if __name__ == "__main__":

    logfile = Path(CONFIG["LogDir"], "bugzilla-query.log")

    with logfile.open("a") as log:
        log.write(time.strftime("[%Y-%m-%d %H:%M:%S] Running bugzilla query\n"))

        # connect to bugzilla
        bz_url = CONFIG["BugzillaURL"]
        bz_creds = CONFIG["BugzillaCredentials"]

        try:
            bzapi = bugzilla.Bugzilla(url=bz_url, cookiefile=None,
                                      tokenfile=None)
        except (bugzilla.BugzillaError, ValueError) as e:
            log.write("Exception: {0}".format(e))
            sys.exit(1)

        if not bzapi.logged_in and bz_creds:
            bzapi.readconfig(bz_creds)
            try:
                bzapi.connect()
            except (bugzilla.BugzillaError, ValueError) as e:
                log.write("Exception: {0}".format(e))

        if bzapi.logged_in:
            log.write("Successfuly logged in as {0}.\n".format(bzapi.user))
        else:
            log.write("Not logged in. Continue as anonymous user.\n")

        found_tasks: Dict[str, List[str]] = {}
        product_list = CONFIG.get_list("BugzillaProduct")
        component_list = CONFIG.get_list("BugzillaComponent")
        config_status = CONFIG.get_list("BugzillaStatus")
        trigger_words = CONFIG.get_list("BugzillaTriggerWords")
        regexes = CONFIG.get_list("BugzillaRegExes")
        delta = CONFIG["BugzillaQueryLastChangeDelta"]

        query = bzapi.build_query(product=product_list,
                                  component=component_list,
                                  include_fields=["id", "comments"])
        query["status"] = list(set(BUGZILLA_STATUS).difference(config_status))
        query["last_change_time"] = datetime.today() - timedelta(hours = delta)
        bugzilla_offset = 0
        bugzilla_bug_count = 0
        while True:
            bugzilla_offset += bugzilla_bug_count
            query["offset"] = bugzilla_offset
            query["limit"] = BUGZILLA_QUERY_LIMIT
            bugs = bzapi.query(query)

            bugzilla_bug_count = len(bugs)
            log.write("Query returned {0} bugs at offset {1}"
                      "\n".format(bugzilla_bug_count, bugzilla_offset))
            # Query until we get no bugs back, which indicates the end
            if bugzilla_bug_count == 0:
                break

            for bug in bugs:
                found_ids: Set[str] = set()
                for comment in bug.comments:
                    for i, trigger_word in enumerate(trigger_words):
                        # Use slower regex only if trigger word is in comment
                        if trigger_word in comment["text"]:
                            m = re.findall(regexes[i], comment["text"])
                            found_ids.update(m)
                for f in found_ids:
                    if f in found_tasks:
                        found_tasks[f].append(str(bug.bug_id))
                    else:
                        found_tasks[f] = [str(bug.bug_id)]

        try:
            files = sorted(Path(CONFIG["SaveDir"]).iterdir())
        except OSError as ex:
            files = []
            log.write("Error listing task directory: %s\n" % ex)

        # Find all tasks
        existing_tasks = []
        for taskdir in files:
            if not taskdir.is_dir():
                continue

            existing_tasks.append(taskdir.name)

        log.write("------------"
                  "Existing tasks with found bugzillas"
                  "------------\n")
        for taskid in found_tasks:
            if taskid in existing_tasks:
                log.write("Task {0} has following bugzilla(s): {1}."
                          "\n".format(taskid, ", ".join(found_tasks[taskid])))
                try:
                    task = RetraceTask(taskid)
                except Exception:
                    continue

                if task.has_bugzillano():
                    current = task.get_bugzillano()
                    assert isinstance(current, list)
                    found = found_tasks[taskid]
                    # Only set bugzillano if some aren't in current list
                    if not set(found).issubset(current):
                        task.set_bugzillano(current + found)
                else:
                    task.set_bugzillano(found_tasks[taskid])

        log.write("\n\n"
                  "------------"
                  "Found tasks in bugzillas that do not exist on local system"
                  "------------\n")
        for taskid in found_tasks:
            if taskid not in existing_tasks:
                log.write("Unknown task {0} has following bugzilla(s): {1}.\n"
                          .format(taskid, ", ".join(found_tasks[taskid])))

        log.write("\n\n"
                  "------------"
                  "Existing tasks that no bugzilla was found "
                  "------------\n")
        no_bz_tasks = [taskid for taskid in existing_tasks
                       if taskid not in found_tasks.keys()]
        log.write("{0}\n\n".format(", ".join(no_bz_tasks)))
