"""Append fresh capture rounds without replacing any published capture or PNG container.

Full-page references remain in their original archives. Exact pixel duplicates reuse
the prior native crop. Source hashes, PNG bytes and decoded pixels are checked before
the public mapping changes. This operation never certifies visual inspection.
"""
from pathlib import Path
from collections import defaultdict
import argparse, datetime, hashlib, json
from PIL import Image, ImageDraw, ImageFont

root = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser()
parser.add_argument('--manifest', type=Path, action='append', required=True)
args = parser.parse_args()
target = root / 'site/review/criterion-captures'
mapping_path = target / 'captures.json'
data = json.loads(mapping_path.read_text())
old_records = data['records']
old_sheets = data['sheets']
original_mapping_sha = hashlib.sha256(mapping_path.read_bytes()).hexdigest()
prior_record_count = len(old_records)
assert [r['capture_index'] for r in old_records] == list(range(prior_record_count))

def digest(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()

# Immutable original context indices retain their existing source pointers.
combined_path = root / 'evidence/complete-capture-records.json'
if combined_path.exists():
    combined = json.loads(combined_path.read_text())['records']
else:
    primary = json.loads((root / 'evidence/inspection-post/inspection-manifest.json').read_text())
    legacy = json.loads((root / 'evidence/luna-post-a-inputs/legacy-supplement.json').read_text())
    combined = primary['records'] + [r for r in legacy['records'] if r['source_kind'] == 'outer-marker-and-selected-customer']
    combined = [dict(r, capture_round='post') for r in combined]
assert len(combined) == prior_record_count
assert all(a['sha256'] == b['sha256'] for a, b in zip(combined, old_records))

bindings = {r['sha256']: r['native_proof'] for r in old_records}
original_sheet_sha = {}
for sheet in old_sheets:
    path = root / 'site' / sheet['path']
    assert digest(path) == sheet['sha256'], path
    original_sheet_sha[sheet['path']] = sheet['sha256']

seen_context = {(r.get('capture_round', 'post'), r['sha256'], r['asset_id'],
                 r['name'], r.get('state'), r.get('group'), r['captured_at']) for r in combined}
added = []
input_receipts = []
for manifest_path in args.manifest:
    source = json.loads(manifest_path.read_text())
    round_name = source['round']
    before_count = len(added)
    for item in source['records']:
        row = dict(item, capture_round=round_name)
        identity = (round_name, row['sha256'], row['asset_id'], row['name'],
                    row.get('state'), row.get('group'), row['captured_at'])
        if identity in seen_context:
            continue
        path = Path(row['file'])
        assert digest(path) == row['sha256'], path
        assert row.get('archive_path') and row.get('archive_entry'), path
        assert (root / 'site' / row['archive_path']).is_file(), row['archive_path']
        seen_context.add(identity)
        added.append(row)
    input_receipts.append({'path': str(manifest_path.resolve()), 'sha256': digest(manifest_path),
                           'round': round_name, 'added_contexts': len(added) - before_count})

new_unique = defaultdict(list)
for offset, row in enumerate(added, prior_record_count):
    if row['sha256'] not in bindings:
        new_unique[row['sha256']].append(offset)
combined.extend(added)
font = ImageFont.load_default(size=14)
new_sheets = []
entries = list(new_unique.items())
for offset in range(0, len(entries), 16):
    batch = entries[offset:offset + 16]
    originals = []
    for sha, indices in batch:
        with Image.open(combined[indices[0]]['file']) as image:
            image.load()
            originals.append(image.convert('RGB'))
    header = 72
    canvas = Image.new('RGB', (sum(im.width for im in originals),
                              max(im.height for im in originals) + header), 'white')
    draw = ImageDraw.Draw(canvas)
    x = 0
    tiles = []
    for (sha, indices), image in zip(batch, originals):
        row = combined[indices[0]]
        draw.multiline_text((x + 4, 4), f"{row['asset_id']} | {row['viewport']['width']}px\n"
                            f"{row.get('state', row['type'])[:38]}\n{sha[:16]}",
                            font=font, fill='#111', spacing=3)
        canvas.paste(image, (x, header))
        box = [x, header, x + image.width, header + image.height]
        pixel_sha = hashlib.sha256(image.tobytes()).hexdigest()
        assert hashlib.sha256(canvas.crop(box).tobytes()).hexdigest() == pixel_sha
        tiles.append({'source_sha256': sha, 'bbox': box, 'pixels': list(image.size),
                      'pixel_sha256': pixel_sha, 'context_indices': indices})
        x += image.width
    name = f'native-{len(old_sheets) + len(new_sheets) + 1:04d}.png'
    path = target / 'native-sheets' / name
    assert not path.exists(), f'Refusing to replace published proof: {path}'
    canvas.save(path)
    assert path.stat().st_size < 25 * 1024 ** 2
    with Image.open(path) as decoded:
        decoded.load()
        for tile in tiles:
            assert hashlib.sha256(decoded.crop(tile['bbox']).tobytes()).hexdigest() == tile['pixel_sha256']
    relative = str(path.relative_to(root / 'site'))
    for tile in tiles:
        bindings[tile['source_sha256']] = {'sheet_path': relative, 'sheet_pixels': list(canvas.size),
                                          'bbox': tile['bbox'], 'source_pixels': tile['pixels'],
                                          'pixel_sha256': tile['pixel_sha256']}
    new_sheets.append({'path': relative, 'sha256': digest(path), 'bytes': path.stat().st_size,
                       'pixels': list(canvas.size), 'tiles': tiles})
    for image in originals:
        image.close()
    canvas.close()

new_records = [dict(capture_index=index, **{k: v for k, v in row.items() if k != 'file'},
                    native_proof=bindings[row['sha256']])
               for index, row in enumerate(added, prior_record_count)]
data['records'] = old_records + new_records
data['sheets'] = old_sheets + new_sheets
created_at = datetime.datetime.now(datetime.timezone.utc).isoformat()
data.update(generated_at=created_at if added else data['generated_at'],
            record_count=len(data['records']), unique_source_hashes=len(bindings),
            sheet_count=len(data['sheets']))
assert data['records'][:prior_record_count] == old_records
for path, sha in original_sheet_sha.items():
    assert digest(root / 'site' / path) == sha
serialized = json.dumps(data, ensure_ascii=False, separators=(',', ':')).encode()
assert len(serialized) < 25 * 1024 ** 2
temp = mapping_path.with_suffix('.json.tmp')
temp.write_bytes(serialized)
temp.replace(mapping_path)
if added or not combined_path.exists():
    combined_path.write_text(json.dumps({'records': combined}, ensure_ascii=False, separators=(',', ':')))
receipt = {'created_at': created_at, 'operation': 'append-only native screenshot evidence',
           'prior_capture_mapping_sha256': original_mapping_sha,
           'capture_mapping_sha256': digest(mapping_path), 'inputs': input_receipts,
           'prior_contexts': prior_record_count, 'added_contexts': len(added),
           'new_unique_source_hashes': len(new_unique), 'new_containers': len(new_sheets),
           'total_contexts': data['record_count'], 'total_unique_hashes': len(bindings),
           'total_containers': data['sheet_count'], 'all_prior_context_indices_and_containers_preserved': True,
           'all_saved_new_crops_redecoded_and_verified': True,
           'visual_acceptance_changed': False}
receipt_path = root / 'evidence' / f"native-proof-append-{datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')}.json"
receipt_path.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n')
print(json.dumps(receipt, indent=2))
