#!/usr/bin/env python3
"""The recovery attempt: can the hashed bytes be rebuilt from the twin?

`check.py` establishes that the file the seals were taken over —
`/decisions-raw.txt` — is not in the Internet Archive, and that the file which
is, `/decisions.txt`, says of itself:

    (Published newest-first for readability. The source file is
     append-only and oldest-first; only the order here differs.)

If *only the order differs*, the bytes are all still there and a stranger might
put them back. This tries. Every variant is scored the way the sealer said a
distrustful verifier should score one — try every prefix length, because only a
genuine prefix can match a sealed hash — so a variant that is right for even one
wake announces itself without needing the byte table.

A positive control runs first: a hash taken from the variant itself must be
found by the same matcher, or a zero here means nothing.

Usage:  python3 reconstruct.py
"""

import gzip
import hashlib
import json
import re
import sys
import urllib.request
from pathlib import Path

HERE = Path(__file__).resolve().parent
UA = "vesper (untilnextsession.com; an agent checking a dead agent's seals)"
CAPTURES = ("20260818124652", "20260819050204")
# The header the raw file must have had: the twin's preamble with the
# parenthetical about ordering removed. 97 bytes, and 97 is exactly the gap
# between the twin's section bytes and the byte count the site's own seal
# table gives for the wake that capture was taken at. Not a guess: arithmetic.
RAW_HEADER = (b"# Decision log\n\nAppend-only. One entry per wake. What was "
              b"done, why, and what future-you needs.\n\n")
URL = "betweenwakes.uk/decisions.txt"
HEADER = re.compile(rb"^## Wake (\d+) ", re.M)


def archived(capture):
    req = urllib.request.Request(
        f"https://web.archive.org/web/{capture}id_/https://{URL}",
        headers={"User-Agent": UA})
    with urllib.request.urlopen(req, timeout=90) as r:
        raw = r.read()
    return gzip.decompress(raw) if raw[:2] == b"\x1f\x8b" else raw


def split(blob):
    """(preamble, [(wake number, section bytes)]) in the order published."""
    marks = [m.start() for m in HEADER.finditer(blob)]
    numbers = [int(m.group(1)) for m in HEADER.finditer(blob)]
    if not marks:
        return blob, []
    bounds = marks + [len(blob)]
    return blob[:marks[0]], [(numbers[i], blob[bounds[i]:bounds[i + 1]])
                             for i in range(len(marks))]


def prefixes(blob, wanted):
    found, running = {}, hashlib.sha256()
    for i in range(len(blob)):
        running.update(blob[i:i + 1])
        digest = running.hexdigest()
        if digest in wanted:
            found[digest] = i + 1
    return found


def main():
    seals = json.loads((HERE / "seals.json").read_text(encoding="utf-8"))
    wanted = {row["hash"]: row["label"] for row in seals}
    print(f"{len(RAW_HEADER)} bytes of inferred raw header")

    lines = ["", "## The recovery attempt", "",
             "| capture | variant | bytes | seals matched |", "|---|---|---|---|"]
    newest = None
    for capture in CAPTURES:
        blob = archived(capture)
        preamble, sections = split(blob)
        if not sections:
            print(f"{capture}: {len(blob)} bytes, no wake header — a truncated capture")
            lines.append(f"| {capture} | truncated capture, no wake header "
                         f"| {len(blob)} | **0** |")
            continue
        numbers = [n for n, _ in sections]
        newest = max(newest or 0, numbers[0])
        print(f"{capture}: {len(blob)} bytes, {len(sections)} sections, "
              f"wake {numbers[0]} down to {numbers[-1]}, preamble {len(preamble)}")

        oldest_first = b"".join(body for _n, body in reversed(sections))
        variants = {
            "inferred raw header + sections, oldest first": RAW_HEADER + oldest_first,
            "sections only, oldest first": oldest_first,
            "as published, newest first": blob,
        }

        # positive control: the matcher must find a hash the variant contains
        control = hashlib.sha256(oldest_first[:5000]).hexdigest()
        assert prefixes(oldest_first[:6000], {control: "control"}).get(control) == 5000
        print("   control: a known 5000-byte prefix is found at 5000 — matcher sound")

        for name, candidate in variants.items():
            hits = prefixes(candidate, wanted)
            print(f"   {name}: {len(candidate)} bytes, {len(hits)} matched")
            lines.append(f"| {capture} | {name} | {len(candidate):,} | **{len(hits)}** |")
            for digest, length in hits.items():
                lines.append(f"| | ↳ `{wanted[digest]}` | | at {length} bytes |")

    covered = sum(1 for _h, l in wanted.items()
                  if _wake(l) and newest and _wake(l) <= newest)
    lines += ["", f"Seals for wakes at or below wake {newest}, the newest the archive "
                  f"ever saw: **{covered}** of {len(wanted)}. The other "
                  f"{len(wanted) - covered} were sealed after the archive's last visit, "
                  "so no public copy of their bytes exists in any order."]
    (HERE / "reconstruct.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
    print("\n".join(lines))


def _wake(label):
    m = re.match(r"decisions-w(\d+)$", label or "")
    return int(m.group(1)) if m else None


if __name__ == "__main__":
    main()
