#!/usr/bin/env python3
"""Correct presentation from existing recordings; never write to source evidence."""
from pathlib import Path
import copy
import difflib
import hashlib
import json
import re
import sys

OUT = Path(__file__).resolve().parent
SOURCE = Path('/tmp/lead-27677-r1/evidence-capture/publish-20261103T0403')
URLS = json.loads((SOURCE.parent / 'uploaded-urls.json').read_text())
SHAS = {'before': 'f012fa210dbf464a5ea68fe9071ee377682f8d66',
        'after': '134aea71f0c861f362534d7ffeab390bfdb83d42'}
DECLARATION = '''## Normalizations (exhaustive)

1. Each recorded event's own top-level `utc` observation timestamp becomes `<OBSERVATION_UTC>`.
2. Each recorded event's own top-level `offsetMs` relative observation offset, when present, becomes `<OBSERVATION_OFFSET_MS>`.

No other field or value is normalized, removed, summarized or coalesced. In particular, RPC receipt and stream sequence numbers, every native SSE event (including duplicate updates and heartbeats), all snapshot fields, native/application timestamps, durations (`elapsedMs` and tool timeouts), IDs and references, ports, absolute paths, revision SHAs, model/usage/token/cache/cost metadata, full failures and null-versus-absent distinctions remain exact. Keeping these incidental differences visible is deliberate: this comparison uses the smallest possible normalization rather than hiding potentially material fields. Raw sources remain authoritative for elapsed time.

The two complete sequences below contain every record from each side's driver JSONL, HTTP JSONL, native-status-at-rejection observation and both independent persisted readbacks. Records are merged by original observation UTC with stable ties; `source` and one-based `sourceRecord` identify the original file and position. Each `record` preserves the entire original JSON object except the two substitutions above. Source order is verified for every file. The first RPC snapshot is already present verbatim as `t3.persisted.final.data` in the driver recording, so it is not inserted a second time. The separate independent final readback remains in full and resolves that first snapshot's projection race. The unified diff compares exactly these same complete sequences.
'''


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


def load(side):
    stem = 'M1-B-before' if side == 'before' else 'M1-A-after'
    names = [stem + suffix for suffix in ['-raw.jsonl', '-http.jsonl',
             '-native-busy.json', '-at-rejection.json', '-final-readback.json']]
    records = []
    provenance = []
    for name in names:
        path = SOURCE / name
        content = path.read_text()
        values = [json.loads(line) for line in content.splitlines()] if name.endswith('.jsonl') else [json.loads(content)]
        provenance.append({'name': name, 'records': len(values), 'sha256': sha(path)})
        for position, value in enumerate(values, 1):
            records.append({'source': name, 'sourceRecord': position, 'record': value})
    records.sort(key=lambda item: item['record']['utc'])
    for name in names:
        positions = [item['sourceRecord'] for item in records if item['source'] == name]
        assert positions == list(range(1, len(positions) + 1)), name
    first = json.loads((SOURCE / (stem + '-first-snapshot.json')).read_text())
    captured = [item['record']['data'] for item in records if item['record']['event'] == 't3.persisted.final']
    assert captured == [first], 'First snapshot must already be preserved in driver'
    return records, provenance


def normalize(records):
    result = copy.deepcopy(records)
    for item in result:
        value = item['record']
        assert isinstance(value['utc'], str)
        value['utc'] = '<OBSERVATION_UTC>'
        if 'offsetMs' in value:
            value['offsetMs'] = '<OBSERVATION_OFFSET_MS>'
    return result


def generate():
    lines = ['# M1 DiffT — complete normalized recordings', '', DECLARATION,
             '## Provenance', '']
    originals = {}
    normalized = {}
    report = {}
    for side, revision in SHAS.items():
        originals[side], provenance = load(side)
        normalized[side] = normalize(originals[side])
        assert originals[side][0]['record']['sha'] == revision
        lines += [f"### {side.capitalize()} — [{revision[:10]}](https://git.cafe/pingdotgg/t3code/commit/{revision})", '']
        for item in provenance:
            name = item['name']
            lines += [f"- [{name}]({URLS[name]}): {item['records']} records, SHA-256 `{item['sha256']}`."]
        report[side] = {'recordCount': len(originals[side]), 'sources': provenance}
    lines += ['', '## Outcome and reading guide', '',
              'Both RPC receipts retain `sequence: 13`. The head has distinct `run.updated` states `running`, `waiting`, then `completed`; the baseline has `running`, then `failed`. All other run, attempt, provider, checkpoint and turn-item updates remain in observation order below.', '',
              'The baseline HTTP request rejects with `HeadersTimeoutError` / `UND_ERR_HEADERS_TIMEOUT`; its full projected failure is `{"class":"provider_error","message":"Provider turn failed.","code":null,"retryable":null}`. Its later native result does not revive the failed T3 run, and its persisted usage is unavailable. The head keeps the admitted turn alive, projects the late bash result and `DONE`, records complete usage, and completes. A final checkpoint record exists with status `missing`; no usable checkpoint is claimed. Source interpretations follow the recorded milestones rather than adding fields to the recordings.', '',
              'The final independent readback, including all checkpoint fields and status, is included below. Diff0 and original raw transcripts are unchanged.', '']
    for side in SHAS:
        lines += [f"## {side.capitalize()} — full normalized sequence ({len(normalized[side])} records)", '',
                  '```json', json.dumps(normalized[side], ensure_ascii=False, indent=2), '```', '']
    before, after = [json.dumps(normalized[side], ensure_ascii=False, indent=2).splitlines() for side in SHAS]
    lines += ['## Unified diff — same full normalized sequences', '', '```diff',
              '\n'.join(difflib.unified_diff(before, after, fromfile='before.normalized.json', tofile='after.normalized.json', lineterm='')), '```', '']
    (OUT / 'M1-DiffT.md').write_text('\n'.join(lines))

    for side, step in [('before', 11), ('after', 12)]:
        name = f'C4-{side}.md'
        original = (SOURCE / name).read_text()
        old = 'snapshot after request failure before idle'
        new = 'snapshot after idle completion and subsequent request failure'
        assert original.count(old) == 1
        corrected = original.replace(old, new)
        introduction = ('\nCaption correction: native idle is emitted at step 7, the completed terminal is recorded at step 8, '
                        f'and the snapshot at step {step} follows that completion and the later request rejection. '
                        'Only the snapshot heading is corrected; every JSON payload and timestamp below is unchanged. '
                        'The original raw log retains its original event label.\n')
        corrected = corrected.replace('\n## 1.', introduction + '\n## 1.', 1)
        assert re.findall(r'```json\n(.*?)\n```', corrected, re.S) == re.findall(r'```json\n(.*?)\n```', original, re.S)
        (OUT / name).write_text(corrected)

    sheet = (SOURCE / 'C4-sheet.md').read_text()
    sheet += ('\n\nRecorded order on both revisions: native idle at step 7, completed terminal at step 8, '
              'request rejection at step 9, then the post-rejection snapshot (before step 11 / after step 12). '
              'That snapshot follows idle completion. The corrected paired sheets change only narration; '
              'raw logs and all JSON payloads remain unchanged.\n')
    (OUT / 'C4-sheet.md').write_text(sheet)

    sheet = (SOURCE / 'M1-sequence-sheet.md').read_text()
    old = 'and a final checkpoint. These are consequences'
    assert sheet.count(old) == 1
    sheet = sheet.replace(old, 'and a final checkpoint record with status `missing` (no usable checkpoint is claimed). These are consequences')
    sheet += ('\nCheckpoint qualification: the head final readback contains a final checkpoint record with '
              '`status: "missing"` and `files: []`. The baseline final readback contains no checkpoint records. '
              'A populated `checkpointId` alone does not establish a usable checkpoint. The unchanged Diff0 '
              'contains the earlier unqualified narrative; this qualification supersedes that claim.\n')
    (OUT / 'M1-sequence-sheet.md').write_text(sheet)
    (OUT / 'generation-report.json').write_text(json.dumps(report, indent=2) + '\n')
    for name in ['M1-DiffT.md', 'C4-before.md', 'C4-after.md', 'C4-sheet.md', 'M1-sequence-sheet.md']:
        path = OUT / name
        print(f'{name}: {path.stat().st_size} bytes; SHA-256 {sha(path)}')


def index():
    urls = json.loads((OUT / 'uploaded-urls.json').read_text())
    text = (OUT / 'original-index.md').read_text()
    old = ('Diff0, raw recordings, original row sheets, M1 evidence and all other existing artifact links are unchanged.')
    text = text.replace(old, 'The prior correction left Diff0 and raw recordings unchanged. The additional M1/C4 corrections below supersede the affected presentation files; all other artifact URLs remain unchanged.')
    text = text.replace('and a final checkpoint. Base has', 'and a final checkpoint record with status `missing` (no usable checkpoint is claimed). Base has')
    text = text.replace('M1 DiffT and its original threshold are unchanged.', 'M1 DiffT now contains every driver/HTTP record, native-busy observation and independent readback, with only each record’s top-level observation `utc` and `offsetMs` normalized. IDs, ports, absolute paths, native/application timestamps, durations, model/usage metadata and all other fields remain exact; its exhaustive declaration is at the top.')
    correction = '''## Additional evidence corrections — existing recordings only

M1 DiffT is rebuilt from full recordings, preserving RPC receipt `sequence: 13`, the distinct head `waiting` run update, the baseline complete failure payload, every native SSE and T3 stream record, HTTP records, native-busy status and complete independent readbacks. Only each record's top-level observation `utc` and `offsetMs` are normalized; no events or fields are dropped or collapsed. First RPC snapshots are verified identical to their already-included driver payloads. The full before/after sequences and their unified diff use the same normalized data.

C4 captions now reflect native idle at step 7, completed terminal at step 8, later request rejection at step 9, and the snapshot afterward (before step 11 / after step 12). All JSON payloads and raw links remain unchanged. The raw event label still says "before idle" and is preserved as recorded, including in the accepted harness DiffT.

M1 head has a final checkpoint record with status `missing` and no files. No usable checkpoint is claimed. This qualification supersedes the earlier unqualified checkpoint wording in the immutable M1 Diff0; Diff0 is intentionally unchanged. The corrected sequence sheet and this index use the qualified wording. Raw transcripts, all accepted harness DiffTs, and all unrelated artifact URLs remain unchanged. No runtime runs, source edits, PR writes, commits or pushes were performed.

Correction generator: [regenerate-evidence.py](GENERATOR_URL). Its `generate` mode builds M1 DiffT and corrected sheets from the read-only source; `index` mode replaces only affected artifact URLs and this narrative using the saved upload map.

'''.replace('GENERATOR_URL', urls['regenerate-evidence.py'])
    text = text.replace('## Findings\n', correction + '## Findings\n', 1)
    for name, url in urls.items():
        text = re.sub(r'(?<=\]\()https://[^)\s]+/' + re.escape(name) + r'(?=\))', lambda _: url, text)
    for name in ['M1-DiffT.md', 'C4-before.md', 'C4-after.md', 'C4-sheet.md', 'M1-sequence-sheet.md']:
        assert urls[name] in text
    assert 'and a final checkpoint. Base has' not in text
    (OUT / 'index.md').write_text(text)
    print(f'index.md: {len(text.encode())} bytes; SHA-256 {sha(OUT / "index.md")}')


if __name__ == '__main__':
    if len(sys.argv) == 2 and sys.argv[1] == 'index':
        index()
    else:
        generate()
