#!/usr/bin/env python3
"""
Find new available releases possibly providing CVE/security fixes.
Usage:
    {0} user/projname "command returning the current version installed"
"""
# pylint: disable=line-too-long
import os
import subprocess
import sys
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser

import requests

try:
    from packaging.version import Version
except ImportError:
    print(
        "UNKNOWN - Missing dependency, please install package 'python3-packaging'.",
        file=sys.stderr,
    )
    sys.exit(3)

if os.environ["USER"] == "root":
    print("Must not be run as root", file=sys.stderr)
    sys.exit(3)

parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument(
    "-p",
    "--prefix",
    dest="version_prefix",
    help="A prefix to ignore in front of release tags.",
)
parser.add_argument(
    "-F",
    "--name-filter",
    help="Filter release names: only consider those containing this string.",
)
parser.add_argument(
    "-w",
    "--word",
    nargs="*",
    default=[],
    help="Extra keyword to search for inside the description of each release. Can be repeated. By default, some hard-coded keywords are already searched for (CVE, GHSA, vuln)",
)
parser.add_argument(
    dest="gh_project",
    help="Name of the github project, formatted as username/projectname.",
)
parser.add_argument(
    dest="cmd_getversion",
    help="A command to be executed to obtain the currently installed version.",
)

args = parser.parse_args()

gh_project = args.gh_project
cmd_getversion = args.cmd_getversion
version_prefix = args.version_prefix


def normalize_version(version_str):
    """Convert string to a valid Version object."""
    if version_prefix:
        version_str = version_str.removeprefix(version_prefix)
    return Version(version_str)


try:
    cmd = subprocess.run(cmd_getversion.split(), check=True, capture_output=True)
except Exception as e:  # pylint: disable=broad-exception-caught
    print(
        f"UNKNOWN - Failed to guess current running version of {gh_project} with command `{cmd_getversion}`.\n",
        file=sys.stderr,
    )
    print(e)
    sys.exit(3)
current_version = normalize_version(cmd.stdout.decode().split("\n")[0].split(" ")[-1])

req = requests.get(f"https://api.github.com/repos/{gh_project}/releases", timeout=10)
if not req.ok:
    print(f"Couldn't get releases page for project {gh_project}", file=sys.stderr)
    sys.exit(3)

releases = [
    release for release in req.json() if not release["prerelease"]
]  # filter out nightly release

security_releases = set()
normal_releases = set()
major_releases = set()
minor_releases = set()
for release in releases:
    version = normalize_version(release["tag_name"])
    release_name = release["name"]
    if args.name_filter and args.name_filter not in release_name:
        continue
    if version > current_version:
        if version.major > current_version.major:
            major_releases.add(release["tag_name"])
            continue
        if version.major == current_version.major and version.minor > current_version.minor:
            minor_releases.add(release["tag_name"])
            continue

        kw_matched = False
        for word in args.word:
            if word in release["body"]:
                security_releases.add(release["tag_name"])
                kw_matched = True
                break
        if kw_matched or (
            "CVE" in release["body"]
            or "vuln" in release["body"].lower()
            or "security" in release["body"].lower()
            or "GHSA" in release["body"]
        ):
            security_releases.add(release["tag_name"])
        else:
            normal_releases.add(release["tag_name"])

if not security_releases:
    print(
        f"OK - No security releases found for {gh_project} (current running version {current_version})"
    )
    if len(normal_releases) > 0:
        print(
            "\nAvailable normal releases:\n  - "
            + "\n  - ".join(sorted(normal_releases, key=normalize_version))
        )
    if len(minor_releases) > 0:
        print(
            "\nAvailable minor releases:\n  - "
            + "\n  - ".join(sorted(minor_releases, key=normalize_version))
        )
    if len(major_releases) > 0:
        print(
            "\nAvailable major releases:\n  - "
            + "\n  - ".join(sorted(major_releases, key=normalize_version))
        )
    sys.exit(0)

print(
    f"WARNING - Security fix available for {gh_project}.\n\nThe following releases provide security fixes, please consider upgrading ASAP:"
)
print("  - " + "\n  - ".join(sorted(security_releases, key=normalize_version)))
sys.exit(1)
