#!/usr/bin/env python3
"""A requirements checker that has never heard of a BMC.

    proposal-review record <handle> <out>            copy the proposal snapshot
    proposal-review check <file>                     is it a snapshot at all
    proposal-review review --rules R --record W ...  judge the latest record
                    [--as-json]

Exit 0 clean, 1 findings, 2 could not complete. It reads the `qa-memory/1`
snapshot the memory tier writes, which is the only thing it and the harness
share. A requirement present with no answer is DECLINED, not judged: the
checker says what it did not check, and how many it did.
"""
import hashlib
import json
import sys


def load(path):
    with open(path) as handle:
        return json.load(handle)


def main(argv):
    if not argv:
        print("usage: proposal-review record|check|review ...")
        return 2

    if argv[0] == "record":
        snapshot = load(argv[1])
        text = json.dumps(snapshot, sort_keys=True)
        with open(argv[2], "w") as out:
            out.write(text)
        print("stored as proposal-" + hashlib.sha256(text.encode()).hexdigest()[:8])
        return 2 if snapshot.get("errors") else 0

    if argv[0] == "check":
        try:
            snapshot = load(argv[1])
            assert isinstance(snapshot.get("entities"), dict)
        except Exception as problem:                         # noqa: BLE001
            print("not a proposal snapshot: %s" % problem)
            return 2
        return 0

    if argv[0] == "review":
        rules, records, as_json, index = [], [], False, 1
        while index < len(argv):
            if argv[index] == "--rules":
                rules.append(argv[index + 1]); index += 2
            elif argv[index] == "--record":
                records.append(argv[index + 1]); index += 2
            elif argv[index] == "--as-json":
                as_json = True; index += 1
            else:
                print("unknown argument " + argv[index]); return 2
        if not rules or not records:
            print("review needs --rules and at least one --record"); return 2
        declared = load(rules[0])["declared"]
        latest = load(records[-1])
        if latest.get("errors"):
            print("the latest record is partial; nothing judged"); return 2
        entities = latest["entities"]

        issues, declined, checked = [], [], 0
        for name in declared:
            if name not in entities:
                issues.append({"requirement": name,
                               "issue": "declared and absent from the proposal"})
        for name, record in entities.items():
            value = record.get("value")
            if value is None:
                declined.append({"requirement": name, "reason": "no_answer"})
                continue
            checked += 1
            if isinstance(value, dict) and "contradicts" in value:
                issues.append({"requirement": name,
                               "issue": "contradicts " + str(value["contradicts"])})

        if as_json:
            print(json.dumps({"issues": issues, "not_checked": declined,
                              "checked": {"requirements": checked}}))
        else:
            for finding in issues:
                print(finding["requirement"])
                print("    " + finding["issue"])
            for decline in declined:
                print(decline["requirement"])
                print("    not checked: " + decline["reason"])
            print("checked %d requirement(s)" % checked)
        return 1 if issues else 0

    print("unknown subcommand " + argv[0])
    return 2


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
