#!/usr/bin/env python3
# pylint: disable=invalid-name,missing-docstring
#
# Copyright 2022 Richard Hughes <richard@hughsie.com>
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import argparse
import datetime
import sys
import json

from typing import List

import xml.etree.ElementTree as ET

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--version", type=str, help="fwupd version")
    parser.add_argument("-s", "--schema-version", type=str, help="schema version")
    parser.add_argument(
        "filename_dst", action="store", type=str, help="XML destination"
    )
    parser.add_argument("json_attrs", nargs="+", help="JSON attributes")
    args = parser.parse_args()

    # parse JSON
    items: List[str] = []
    for fn in sorted(args.json_attrs):
        try:
            with open(fn, "rb") as f:
                item = json.loads(f.read())
        except json.decoder.JSONDecodeError as e:
            print(f"failed to parse {fn}: {str(e)}")
            sys.exit(1)
        for tag in ["id", "name", "failure-results"]:
            if tag not in item:
                print("skipping {} as no {}".format(fn), tag)
                continue
        items.append(item)

    oval_definitions = ET.Element("oval_definitions")
    oval_definitions.set("xmlns", "http://oval.mitre.org/XMLSchema/oval-definitions-5")
    oval_definitions.set("xmlns:oval", "http://oval.mitre.org/XMLSchema/oval-common-5")
    oval_definitions.set(
        "xmlns:unix-def", "http://oval.mitre.org/XMLSchema/oval-definitions-5#unix"
    )
    oval_definitions.set(
        "xmlns:red-def", "http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"
    )
    oval_definitions.set(
        "xmlns:ind-def",
        "http://oval.mitre.org/XMLSchema/oval-definitions-5#independent",
    )
    oval_definitions.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
    oval_definitions.set(
        "xsi:schemaLocation",
        "http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd "
        "http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd "
        "http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd "
        "http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd",
    )

    generator = ET.SubElement(oval_definitions, "generator")
    for key, value in {
        "oval:product_name": "fwupd",
        "oval:product_version": args.version,
        "oval:schema_version": args.schema_version,
        "oval:timestamp": datetime.datetime.now().isoformat(),
    }.items():
        oval = ET.SubElement(generator, key)
        oval.text = value

    definitions = ET.SubElement(oval_definitions, "definitions")
    for item in items:
        definition = ET.SubElement(definitions, "definition")
        definition.set("class", "patch")
        definition.set("id", f"oval:{item['id']}:def:1")
        definition.set("version", "1")

        metadata = ET.SubElement(definition, "metadata")

        ET.SubElement(metadata, "title").text = item["name"]

        affected = ET.SubElement(metadata, "affected")
        affected.set("family", "unix")
        ET.SubElement(affected, "platform").text = "All"  # is this valid?

        if "issues" in item:
            for issue in item["issues"]:
                reference = ET.SubElement(metadata, "reference")
                reference.set("ref_id", issue)
                if issue.startswith("CVE-"):
                    reference.set(
                        "ref_url", f"https://nvd.nist.gov/vuln/detail/{issue}"
                    )
                    reference.set("source", "CVE")

        if "description" in item:
            ET.SubElement(metadata, "description").text = "\n".join(item["description"])

        criteria = ET.SubElement(definition, "criteria")
        criteria.set("operator", "OR")
        criterion = ET.SubElement(criteria, "criterion")
        criterion.set("comment", item["name"])
        criterion.set("test_ref", f"oval:{item['id']}:tst:1")

    tests = ET.SubElement(oval_definitions, "tests")
    for item in items:
        red_def = ET.SubElement(tests, "red-def:fwupdsecattr_test")
        red_def.set("check", "at least one")
        red_def.set("comment", item["name"])
        red_def.set("id", f"oval:{item['id']}:tst:1")
        red_def.set("version", "1")

        red_def_object = ET.SubElement(red_def, "red-def:object")
        red_def_object.set("object_ref", f"oval:{item['id']}:obj:1")
        red_def_state = ET.SubElement(red_def, "red-def:state")
        red_def_state.set("state_ref", f"oval:{item['id']}:ste:1")

    objects = ET.SubElement(oval_definitions, "objects")
    for item in items:
        red_def = ET.SubElement(objects, "red-def:fwupdsecattr_object")
        red_def.set("id", f"oval:{item['id']}:obj:1")
        red_def.set("version", "1")

        red_def_stream_id = ET.SubElement(red_def, "red-def:stream-id")
        red_def_stream_id.set("datatype", "string")
        red_def_stream_id.text = format(item["id"])

    states = ET.SubElement(oval_definitions, "states")
    for item in items:
        red_def = ET.SubElement(states, "red-def:fwupdsecattr_state")
        red_def.set("id", f"oval:{item['id']}:ste:1")
        red_def.set("version", "1")

        red_def_security_attr = ET.SubElement(red_def, "red-def:security-attr")
        red_def_security_attr.set("datatype", "string")
        red_def_security_attr.set("operation", "pattern match")
        red_def_security_attr.text = "|".join(
            [value for value, _ in item["failure-results"].items()]
        )

    ET.indent(oval_definitions, space=" ", level=0)
    with open(args.filename_dst, "wb") as f:
        f.write(ET.tostring(oval_definitions, encoding="utf-8", xml_declaration=True))
