"""Validate one small, specific JSON result contract; never execute its text."""
import json
import sys

MAX_BYTES = 64 * 1024


class ResultError(ValueError):
    pass


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ResultError("duplicate object key: " + repr(key)[:80])
        result[key] = value
    return result


def reject_constant(value):
    raise ResultError("nonstandard JSON constant is not allowed: " + value)


def strict_load_json(raw):
    if len(raw) > MAX_BYTES:
        raise ResultError("input exceeds 65536 bytes (64 KiB)")
    try:
        text = raw.decode("utf-8")
    except UnicodeDecodeError as exc:
        raise ResultError("input is not valid UTF-8 at byte " + str(exc.start)) from exc
    try:
        return json.loads(text, object_pairs_hook=unique_object,
                          parse_constant=reject_constant)
    except json.JSONDecodeError as exc:
        raise ResultError("invalid JSON at line %d column %d: %s" %
                          (exc.lineno, exc.colno, exc.msg)) from exc
    except RecursionError as exc:
        raise ResultError("JSON nesting is too deep for this Python runtime") from exc
    except ValueError as exc:
        raise ResultError(str(exc)) from exc


def validate_result(value):
    if not isinstance(value, dict):
        raise ResultError("top level must be an object")
    if set(value) != {"summary", "tags"}:
        raise ResultError("object must contain exactly summary and tags")
    summary = value["summary"]
    if not isinstance(summary, str) or not 1 <= len(summary.strip()) <= 120:
        raise ResultError("summary must be a string of 1-120 characters after stripping")
    tags = value["tags"]
    if not isinstance(tags, list) or len(tags) > 5:
        raise ResultError("tags must be an array containing 0-5 strings")
    seen = set()
    for index, tag in enumerate(tags):
        if not isinstance(tag, str) or not 1 <= len(tag.strip()) <= 20:
            raise ResultError("tags[%d] must be a string of 1-20 characters after stripping" % index)
        tag = tag.strip()
        if tag in seen:
            raise ResultError("tags must be unique after stripping (case-sensitive)")
        seen.add(tag)


def main(argv=None):
    argv = sys.argv[1:] if argv is None else argv
    if len(argv) != 1:
        print("usage: python3 strict_result.py FILE", file=sys.stderr)
        return 2
    try:
        with open(argv[0], "rb") as source:
            raw = source.read(MAX_BYTES + 1)
        validate_result(strict_load_json(raw))
    except (OSError, ResultError) as exc:
        print("INVALID: " + str(exc), file=sys.stderr)
        return 2
    print("VALID")
    return 0


if __name__ == "__main__":
    sys.exit(main())
