"""Check a completed local ZIP backup without extraction. Python 3.9+."""
import argparse
import json
from pathlib import Path
import sys
import zipfile
import zlib


def check_zip(path, required):
    with zipfile.ZipFile(path, "r") as archive:
        seen = set()
        files = set()
        total = 0
        for info in archive.infolist():
            name = info.filename
            if name in seen:
                raise ValueError(f"duplicate member: {name!r}")
            seen.add(name)
            if info.flag_bits & 1:
                raise ValueError(f"encrypted member is unsupported: {name!r}")
            if info.compress_type not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
                raise ValueError(f"unsupported compression: {name!r}")
            if not info.is_dir():
                files.add(name)
                total += info.file_size
        if not files:
            raise ValueError("archive contains no files")
        missing = sorted(set(required) - files)
        if missing:
            raise ValueError(f"missing required members: {missing!r}")
        bad = archive.testzip()
        if bad is not None:
            raise ValueError(f"CRC/header check failed: {bad!r}")
    return {
        "file": str(path), "files": len(files),
        "uncompressed_bytes": total, "valid": True,
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument("file", type=Path, help="completed, unchanging ZIP backup")
    parser.add_argument(
        "--require", action="append", default=[], metavar="MEMBER",
        help="exact non-directory member name; repeat for multiple files",
    )
    args = parser.parse_args()
    if any(not name or name.endswith("/") for name in args.require):
        parser.error("--require needs a nonempty file member name, without trailing /")
    try:
        result = check_zip(args.file, args.require)
    except (OSError, ValueError, EOFError, RuntimeError,
            NotImplementedError, zipfile.BadZipFile, zlib.error) as exc:
        print(f"ERROR: {args.file}: {exc}", file=sys.stderr)
        return 1
    print(json.dumps(result, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
