#!/usr/bin/env python3
"""Re-derive which capture is authentic, without trusting the filenames.

Task 1 of the /screenshot-evidence/ build. Run from this directory:

    python verify-panels.py

The point of committing this is that anyone can repeat it. The mapping the page
publishes is not an assertion by us: it falls out of comparing the captures
against the message data the live site actually served, which sits outside all
three image files and is therefore a valid discriminator.

thread.fetched.json is the copy retrieved from
https://shorenet.co.uk/demo-messenger/thread.json at the time the captures were
identified. It is committed so the check still works if the demo site is ever
edited. It should remain byte-identical to the served file; that file is frozen.

Requires: pillow, numpy (dev tooling only, NOT dependencies of the site).
"""
import hashlib, json, struct, sys
from pathlib import Path

HERE = Path(__file__).parent
CAPTURES = sorted(HERE.glob('capture-*.png'))

def sha256(p):
    return hashlib.sha256(Path(p).read_bytes()).hexdigest()

def png_chunks(p):
    out = []
    with open(p, 'rb') as fh:
        fh.read(8)
        while True:
            hdr = fh.read(8)
            if len(hdr) < 8:
                break
            ln, typ = struct.unpack('>I4s', hdr)
            out.append((typ.decode('latin1'), ln, fh.tell()))
            fh.seek(ln + 4, 1)
            if typ == b'IEND':
                break
    return out

def main():
    print('=== source captures ===')
    for c in CAPTURES:
        from PIL import Image
        w, h = Image.open(c).size
        chunks = png_chunks(c)
        prov = [t for t, _, _ in chunks if t.lower() in ('cabx', 'jumb', 'exif', 'itxt', 'ztxt', 'text')]
        print(f'  {c.name:<16} {w}x{h}  aspect {w/h:.5f}  sha256 {sha256(c)[:16]}…'
              f'  provenance chunks: {prov or "none"}')

    tj = HERE / 'thread.fetched.json'
    print(f'\n=== thread.fetched.json ===\n  sha256 {sha256(tj)}')
    data = json.loads(tj.read_text(encoding='utf-8'))
    msgs = data['thread']['messages']
    print(f'  {len(msgs)} messages; the last is the one the demonstration turns on:')
    last = msgs[-1]
    print(f'    {last["date"]}, {last["time"]}  {last["sender"]}')
    print(f'    "{last["body"]}"')
    print('\n  every message, for comparison against the captures:')
    for m in msgs:
        print(f'    {m["time"]}  {m["sender"][:2]}  {m["body"]}')

    print("""
=== how the mapping is derived ===
  A  the capture whose every message matches the JSON above.
     A DOM edit and an image edit both change the render without changing the
     served data, so only an authentic capture agrees everywhere.
  B  of the remaining two, the one that diverges at the FINAL message.
  C  the remaining one. It may diverge in more than one place; an image model
     can alter regions nobody asked it to.

  Corroborated by pixel diff on these lossless sources (never on the AVIF or
  WebP derivatives, whose lossy encoding swamps the signal):
    A vs B  difference confined to a tight box inside the thread, and exactly
            zero outside it, because both are browser captures of one page at
            one size.
    A vs C  cannot be compared without a resample, because the frames are
            different sizes, and the resample sets a noise floor of its own.
            Reported, but step 1 is what carries the identification.
""")

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