#!/usr/bin/env python3
# Copyright 2018, Canonical, Ltd.
# Author: Alex Murray <alex.murray@canonical.com>
# License: GPLv3
#
# Report MIRs for the security team
#

from __future__ import print_function

import cve_lib
import optparse
import re
import sys

parser = optparse.OptionParser()
parser.add_option("--debug", help="Verbose processing output", action='store_true')
parser.add_option("--team", help="Find bugs for team (default ubuntu-security)", metavar="TEAM", action='store', default='ubuntu-security')
parser.add_option("--status", help="Specify status for bugs", metavar="STATUS", action='store', default=None)
parser.add_option("--priority", help="Specify status for bugs", metavar="STATUS", action='store', default=None)
parser.add_option("--last-message-date", help="Sort bugs by their most recent message", action='store_true')

(opt, args) = parser.parse_args()

def debug(s):
    '''Print debug message'''
    if opt.debug:
        print("DEBUG: %s" % (s), file=sys.stderr)

def sort_by_date_last_message(task):
    return task[1].bug.date_last_message

def sort_by_id(task):
    return task[0]

def print_bugs(bugs, status=None, priority=None, last_message_date=False):
    '''Output a collection of bugs'''
    if last_message_date:
        bugs = sorted(bugs.items(), key=sort_by_date_last_message, reverse=True)
    else:
        bugs = sorted(bugs.items(), key=sort_by_id)

    count = 0
    for (id, task) in bugs:
        debug(task.title)
        tstatus = task.status
        t_importance = task.importance
        bugid = task.bug.id
        if status:
           if tstatus != status:
               debug("Skipping LP: #%d (%s != %s)" % (bugid, tstatus, status))
               continue
        if priority:
           if t_importance != priority:
               debug("Skipping LP: #%d (%s != %s)" % (bugid, t_importance, priority))
               continue

        ttags = task.bug.tags

        cves = []
        tcves = task.bug.cves
        if tcves != None:
            for c in tcves:
                cves.append("CVE-" + c.sequence)

        bug_target_name = task.bug_target_name
        if not ' (' in bug_target_name:
            print("Skipping target name=%s (LP: #%d)" % (bug_target_name, bugid), file=sys.stderr)
            continue

        pkg, target = bug_target_name.split(' (', 1)
        target = target.split(')')[0]
        if ' ' in target:
            target, targeted_to = target.split(' ', 1)

        if target and target.lower() != 'ubuntu':
            debug('skipping target "%s" (%s) (LP: #%d)' % (target, pkg, bugid))
            continue

        if tstatus in ['Fix Released', 'Invalid', "Won't Fix"]:
            debug('skipping (pkg:%s status:%s LP: #%d)' % (pkg, tstatus, bugid))
            continue

        if not re.match(r'^[a-z0-9][a-z0-9+\.\-]+$', pkg):
            print("Bad package name '%s' (LP: #%d)" % (pkg, bugid), file=sys.stderr)
            continue

        release = "ubuntu"
        if ':' in id:
            release = id.split(':')[1]
        print("=== Source: %s (%s) ===" % (pkg, release))
        print(" * Status: %s" % tstatus, end=" ")
        if tstatus == "Fix Committed":
            print("(committed on %s)" % str(task.date_fix_committed).split(' ')[0])
        elif tstatus == "In Progress":
            print("(marked 'In Progress' on %s)" % str(task.date_in_progress).split(' ')[0])
        else:
            print("")

        print(" * Priority: %s" % t_importance)

        if ttags:
            print(" * Tags: %s" % ' '.join(ttags))
        print(" * URL: https://launchpad.net/bugs/%d" % bugid)

        if len(cves) > 0:
            print(" * CVES:", end=" ")
            for c in cves:
                print("%s" % (c), end=" ")
            print("")

        if last_message_date:
            print(" * Last message: %s" % task.bug.date_last_message);

        print("")

        count += 1

    # How many unique bugs did we report on?
    return count

#
# Connect to Launchpad
#
try:
    import lpl_common
except:
    print("lpl_common.py seems to be missing.  Please create a symlink from $UQT/common/lpl_common.py to $UCT/scripts/", file=sys.stderr)
    sys.exit(1)

# Load configuration
cve_lib.read_config()

# API interface
debug("Connecting to LP ...")
lp = lpl_common.connect()

# Get authenticated URL fetcher
opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
if not opener:
    raise ValueError("Could not open cookies")

ubuntu = lp.distributions['ubuntu']
debug("Distribution: %s" % ubuntu)
team = lp.people[opt.team]
mir_team = lp.people['ubuntu-mir']
debug("Team: %s" % team)

# by default we want all not in Fix Released, Fix Committed, Opinion, Invalid,
# Won't Fix & Expired which leaves the following
search_status = ['New',
                 'Incomplete',
                 'Confirmed',
                 'Triaged',
                 'In Progress']
if opt.status:
    search_status = [opt.status]

debug("Loading bugs ...")
bugs = {}
# get bugs for a specific series, then for 'Ubuntu' if we haven't already added
# the bug
for rel in cve_lib.releases + ['ubuntu']:
    if rel == 'ubuntu':
        obj = ubuntu
    else:
        series = ubuntu.getSeries(name_or_version=rel)
        if not series.active:
            continue
        obj = series

    debug("")
    # get team members
    assignees = [member.member for member in team.members_details]
    debug("Gathering MIR bugs for %s assigned to %s and their members %s..." %
          (rel, team.name, [assignee.name for assignee in assignees]))
    for assignee in assignees + [team]:
        task_collection = obj.searchTasks(bug_subscriber=mir_team,
                                           assignee=assignee,
                                           omit_targeted=False,
                                           status=search_status)

        for task in task_collection:
            # handle bugs which list multiple packages in the single MIR as
            # separate bugs
            pkg = task.bug_target_name.split(' (')[0]
            bugid = str(task.bug.id) + pkg + ":" + rel
            if rel != 'ubuntu' or bugid not in bugs:
                bugs[bugid] = task

    debug("Found %d bugs across all supported releases" % len(bugs))

    n = print_bugs(bugs, opt.status, opt.priority, last_message_date=opt.last_message_date)

    if opt.status:
        print("%d bugs found for '%s' in %s with status '%s'" % (n, opt.team, rel, "|".join(search_status)))
    else:
        print("%d open bugs found for '%s' in %s" % (n, opt.team, rel))

sys.exit(0)

