#!/usr/bin/env python3
"""Read-only checks for a private copy-based Codex storage experiment.

Python 3. Compare also requires Pillow (python3 -m pip install Pillow).
  python3 verify-codex-case.py measure /path/to/isolated-test-root
  python3 verify-codex-case.py compare /path/to/original-copies /path/to/restored-copies

No network, mutation, app launch or telemetry. Output contains counts only.
Measure the same scope before/after, including recovery and profile files.
Use independent byte copies: allocated blocks can include shared APFS extents.
The comparison verifies bytes outside recognized image URLs, image dimensions,
RGBA pixels and ICC profile; it does not assert identical image encoding,
EXIF metadata, GUI behavior, model answers or future compatibility.
"""
import argparse
import base64
import hashlib
import io
import json
from pathlib import Path
import re


def files(root):
    if root.is_symlink() or not root.is_dir():
        raise ValueError('Expected a real directory, not a symbolic link.')
    return [p for p in root.rglob('*') if p.is_file() and not p.is_symlink()]


def measure(root):
    unique = {}
    for p in files(root):
        s = p.stat()
        unique[(s.st_dev, s.st_ino)] = s
    return {'regularFileInodes': len(unique),
            'logicalBytes': sum(s.st_size for s in unique.values()),
            'allocatedBytes': sum(s.st_blocks * 512 for s in unique.values()),
            'note': 'File allocation only; filesystem metadata, snapshots and shared extents require separate interpretation.'}


def content_digest(path):
    from PIL import Image
    pattern = re.compile(rb'data:image/[a-z]+;base64,([A-Za-z0-9+/=]+)')
    counts = {'decodedImages': 0, 'undecodableImageLikeValues': 0}
    def picture(m):
        try:
            im = Image.open(io.BytesIO(base64.b64decode(m[1], validate=True)))
            im.load()
            data = repr(im.size).encode() + im.convert('RGBA').tobytes() + (im.info.get('icc_profile') or b'')
        except (OSError, ValueError, SyntaxError, Image.DecompressionBombError):
            counts['undecodableImageLikeValues'] += 1
            return m[0]  # Leave the entire value byte-exact, including its MIME.
        counts['decodedImages'] += 1
        return b'<decoded-image:' + hashlib.sha256(data).hexdigest().encode() + b'>'
    # JSONL can contain literal/truncated data-URL examples, not only pictures.
    h = hashlib.sha256()
    with path.open('rb') as source:
        for line in source:
            h.update(pattern.sub(picture, line))
    return h.digest(), counts


def compare(a, b):
    left = {str(p.relative_to(a)): p for p in files(a) if p.suffix == '.jsonl'}
    right = {str(p.relative_to(b)): p for p in files(b) if p.suffix == '.jsonl'}
    same, decoded, undecodable = 0, 0, 0
    for k in left.keys() & right.keys():
        original, counts = content_digest(left[k])
        restored, restored_counts = content_digest(right[k])
        same += original == restored and counts == restored_counts
        decoded += counts['decodedImages']
        undecodable += counts['undecodableImageLikeValues']
    return {'originalFiles': len(left), 'restoredFiles': len(right),
            'sameFileSet': left.keys() == right.keys(), 'decodedContentMatches': same,
            'decodedImageOccurrences': decoded, 'undecodableImageLikeValuesComparedByteExactly': undecodable,
            'passed': bool(left) and left.keys() == right.keys() and same == len(left)}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('mode', choices=['measure', 'compare'])
    parser.add_argument('before', type=Path)
    parser.add_argument('after', type=Path, nargs='?')
    args = parser.parse_args()
    if args.mode == 'compare' and args.after is None:
        parser.error('compare needs both original and restored directories')
    try:
        result = measure(args.before) if args.mode == 'measure' else compare(args.before, args.after)
        print(json.dumps(result, indent=2))
        raise SystemExit(0 if result.get('passed', True) else 1)
    except (OSError, ValueError, ImportError):
        print(json.dumps({'passed': False, 'error': 'Read or decode failed; check local permissions and Pillow. No partial success is reported.'}))
        raise SystemExit(2)
