#!/usr/bin/python3

"""This script does a version comparison between the versions of packages
from the update under test and the installed versions of the same-named
packages, for whichever packages from the update are installed. It expects
input data that's generated in advisory_check_nonmatching_packages and
advisory_get_installed_packages; you can run it manually against
updatepkgs.txt and installedupdatepkgs.txt logs from a completed test run
if you need to test it or something."""

# no, pylint, global scope variable names in a script like this aren't
# really "constants"
# pylint:disable=invalid-name

import json
import sys
from urllib.request import urlopen

import rpm  # pylint:disable=import-error

def printver(pname, pepoch, pversion, prelease):
    """Print a NEVR in the typical human-readable format."""
    return f"{pname}-{pepoch}:{pversion}-{prelease}"

def parse_lorax_log(logfile):
    """
    Parse a pylorax.log file into a format the rest of the script
    can work with.
    """
    with open(logfile, "r", encoding="utf-8") as logfh:
        log = logfh.read()
    log = log.splitlines()

    # Filter to the lines that indicate installed packages
    installed = [line for line in log if line.startswith("Install ")]
    # Drop the "Install " prefix
    installed = [line.split()[1] for line in installed]
    # Do a split to get (lname, lepoch+lversion, lrelease) tuples
    installed = [line.rsplit("-", 2) for line in installed]
    # create an output list
    out = []
    for (lname, ev, lrelease) in installed:
        # fiddle a 0 epoch into all the packages without an explicit one
        if ":" not in ev:
            ev = f"0:{ev}"
        # split the epoch and version out
        (lepoch, lversion) = ev.split(":")
        # strip the arch suffix from the release
        lrelease = lrelease.rsplit(".", 1)[0]
        # output in the format we expect later
        out.append(f"unknown {lname} {lepoch} {lversion} {lrelease}")
    return out

try:
    updfname = sys.argv[1]
    instfname = sys.argv[2]
except IndexError:
    sys.exit("Must specify two input filenames!")
try:
    updalias = sys.argv[3]
except IndexError:
    updalias = None


updpkgs = {}
with open(updfname, "r", encoding="utf-8") as ufh:
    for uline in ufh.readlines():
        (_, name, epoch, version, release) = uline.strip().split(" ")
        updpkgs[name] = (epoch, version, release)

problems = []
warnings = []
post = set()
ret = 0
updstableobs = None
if "lorax.log" in instfname:
    ilines = parse_lorax_log(instfname)
else:
    with open(instfname, "r", encoding="utf-8") as ifh:
        ilines = ifh.readlines()
for iline in ilines:
    (_, name, epoch, version, release) = iline.strip().split(" ")
    if name not in updpkgs:
        # this happens with lorax logs, as they contain every package
        continue
    res = rpm.labelCompare((epoch, version, release), (updpkgs[name]))  # pylint:disable=no-member
    if res == 0:
        continue
    instver = printver(name, epoch, version, release)
    updver = printver(name, *updpkgs[name])
    if res < 0:
        problems.append(f"Installed: {instver} is older than update: {updver}.")
        post.add("Installed older than update usually means there is a dependency problem preventing the update version being installed.")
        post.add("Check /var/log/dnf.log, and search for the string 'Problem'.")
    else:
        msg = f"Installed: {instver} is newer than update: {updver}"
        if not updalias:
            problems.append(f"{msg} and this is not an update test, please check if this is a problem")
            continue
        # check if the update is stable
        if updstableobs is None:
            try:
                url = f"https://bodhi.fedoraproject.org/updates/{updalias}"
                resp = json.loads(urlopen(url).read().decode("utf8"))   # pylint:disable=consider-using-with
                updstableobs = (resp.get("update", {}).get("status", "") in ("stable", "obsolete"))
            except: # pylint:disable=bare-except
                problems.append(f"{msg} and Bodhi is unreachable.")
                continue
        if updstableobs:
            warnings.append(f"{msg} and update is stable or obsolete, this is probably OK.")
        else:
            problems.append(f"{msg} and update is not stable or obsolete.")
            post.add("Installed newer than update and update not stable or obsolete means older version could be pushed over newer if update goes stable.")

if warnings:
    print("WARNINGS")
    for warning in warnings:
        print(warning)
    ret = 2

if problems:
    print("PROBLEMS")
    for problem in problems:
        print(problem)
    ret = 3

if post:
    print("INVESTIGATION TIPS")
    for postmsg in post:
        print(postmsg)

sys.exit(ret)