"""Disposable SQLite + attachment restore rehearsal; no real data, services or measured RTO."""
import hashlib,json,sqlite3,tempfile,shutil
from pathlib import Path

def digest(p):return hashlib.sha256(p.read_bytes()).hexdigest()
with tempfile.TemporaryDirectory() as d:
 root=Path(d);source=root/'source';backup=root/'backup';restored=root/'restored'
 for p in [source,backup,restored]:p.mkdir()
 db=sqlite3.connect(source/'app.db');db.executescript('CREATE TABLE booking(id TEXT PRIMARY KEY,document TEXT NOT NULL);')
 for i in range(1,4):
  db.execute('INSERT INTO booking VALUES(?,?)',(f'B{i}',f'D{i}.txt'))
  (source/f'D{i}.txt').write_text(f'Illustrative attachment D{i}\n')
 db.commit();copy=sqlite3.connect(backup/'app.db');db.backup(copy);copy.close()
 manifest={f'D{i}.txt':digest(source/f'D{i}.txt') for i in range(1,4)}
 (backup/'manifest.json').write_text(json.dumps(manifest));(backup/'config.json').write_text(json.dumps({'outboundEnabled':False}))
 for name in ['D1.txt','D3.txt']:shutil.copyfile(source/name,backup/name) # negative fixture omits D2
 db.execute("INSERT INTO booking VALUES('B4','D4.txt')");db.commit();db.close() # after snapshot
 for p in backup.iterdir():shutil.copyfile(p,restored/p.name)
 def validate():
  c=sqlite3.connect(restored/'app.db');bookings=c.execute('SELECT * FROM booking ORDER BY id').fetchall();c.close()
  expected=json.loads((restored/'manifest.json').read_text());missing=[];mismatch=[]
  for bid,name in bookings:
   if not (restored/name).exists():missing.append(name)
   elif digest(restored/name)!=expected.get(name):mismatch.append(name)
  return {'bookings':bookings,'missing':missing,'hashMismatch':mismatch,'outboundEnabled':json.loads((restored/'config.json').read_text())['outboundEnabled']}
 first=validate();assert len(first['bookings'])==3 and first['missing']==['D2.txt']
 shutil.copyfile(source/'D2.txt',restored/'D2.txt');(restored/'D3.txt').write_text('Wrong attachment bytes\n')
 second=validate();assert second['missing']==[] and second['hashMismatch']==['D3.txt']
 shutil.copyfile(source/'D3.txt',restored/'D3.txt');final=validate()
 assert final['missing']==final['hashMismatch']==[] and final['outboundEnabled'] is False
 assert [r[0] for r in final['bookings']]==['B1','B2','B3']
 report={'firstRestore':first,'presentButWrongFile':second,'repairedSet':final,'postSnapshotBooking':'B4 intentionally absent','scope':'Fixture repairs use unchanged source files; no backup recovery guarantee or measured timing'}
if __name__=='__main__':print(json.dumps(report,indent=2))
