"""Isolated synthetic PostgreSQL recovery rehearsal; never connects to an existing DB."""
import subprocess,json,time,hashlib,uuid
from pathlib import Path
from datetime import datetime,timezone
root=Path(__file__).parent
name='vps-recovery-lab-'+uuid.uuid4().hex[:10]
report={'startedAt':datetime.now(timezone.utc).isoformat(),'container':name,'scope':'Local Docker Desktop; synthetic records; network none; no host ports; not VPS performance','checks':[]}
created=False

def run(args,inp=None,check=True):
 r=subprocess.run(args,input=inp,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
 if check and r.returncode:raise RuntimeError(f'{args[:4]}: {r.stderr.decode(errors="replace")}')
 return r

def pg(db,sql):return run(['docker','exec','-i',name,'psql','-U','postgres','-d',db,'-v','ON_ERROR_STOP=1','-At'],sql.encode()).stdout

def record(label,ok,detail=None):
 report['checks'].append({'check':label,'pass':bool(ok),'detail':detail})
 if not ok:raise AssertionError(label)

try:
 image=json.loads(run(['docker','image','inspect','postgres:18']).stdout)[0]
 report['imageId']=image['Id'];report['imageDigests']=image.get('RepoDigests',[])
 run(['docker','run','-d','--rm','--name',name,'--network','none','--memory','512m','--cpus','1','--env','POSTGRES_HOST_AUTH_METHOD=trust','postgres:18']);created=True
 for _ in range(40):
  if run(['docker','exec',name,'cat','/proc/1/comm'],check=False).stdout.strip()==b'postgres' and run(['docker','exec',name,'pg_isready','-U','postgres'],check=False).returncode==0:break
  time.sleep(.5)
 else:raise RuntimeError('New lab postgres did not become ready')
 report['version']=pg('postgres','SELECT version();').decode().strip()
 for db in ['lab_source','lab_restored','lab_bad']:pg('postgres',f'CREATE DATABASE {db};')
 schema="""CREATE TABLE samples (id integer PRIMARY KEY, label text NOT NULL, amount integer NOT NULL CHECK(amount>=0));
INSERT INTO samples VALUES (1,'sample-a',125),(2,'sample-b',250),(3,'sample-c',0),(4,'sample-d',875),(5,'sample-e',1000);
CREATE INDEX samples_label_idx ON samples(label);
CREATE VIEW sample_totals AS SELECT count(*) AS records,sum(amount) AS total FROM samples;
"""
 (root/'schema.sql').write_text(schema);pg('lab_source',schema)
 select="COPY (SELECT id,label,amount FROM samples ORDER BY id) TO STDOUT WITH CSV HEADER;"
 before=pg('lab_source',select);(root/'before.csv').write_bytes(before)
 t=time.monotonic();dump=run(['docker','exec',name,'pg_dump','-U','postgres','-d','lab_source','-Fc']).stdout;report['dumpSeconds']=round(time.monotonic()-t,3);(root/'database.dump').write_bytes(dump)
 # Restore to a different database, not to the source. No --clean or source deletion.
 t=time.monotonic();restored=run(['docker','exec','-i',name,'pg_restore','-U','postgres','--dbname','lab_restored','--exit-on-error','--single-transaction'],dump);report['restoreSeconds']=round(time.monotonic()-t,3)
 (root/'restore.stderr.txt').write_bytes(restored.stderr)
 after=pg('lab_restored',select);(root/'after.csv').write_bytes(after)
 record('sorted rows identical',before==after,hashlib.sha256(after).hexdigest())
 totals=pg('lab_restored','SELECT records,total FROM sample_totals;').decode().strip();record('count and total through restored view',totals=='5|2250',totals)
 indexes=pg('lab_restored',"SELECT indexname FROM pg_indexes WHERE tablename='samples' ORDER BY indexname;").decode().splitlines();record('primary and secondary indexes restored',indexes==['samples_label_idx','samples_pkey'],indexes)
 bad_constraint=run(['docker','exec','-i',name,'psql','-U','postgres','-d','lab_restored','-v','ON_ERROR_STOP=1'],b"INSERT INTO samples VALUES(6,'invalid',-1);",check=False);record('restored CHECK rejects invalid row',bad_constraint.returncode!=0)
 bad=run(['docker','exec','-i',name,'pg_restore','-U','postgres','--dbname','lab_bad','--exit-on-error','--single-transaction'],dump[:32],check=False);(root/'corrupt-archive.stderr.txt').write_bytes(bad.stderr);record('truncated archive fails',bad.returncode!=0,{'exitCode':bad.returncode,'message':bad.stderr.decode().strip()})
 record('failed restore created no public tables',pg('lab_bad',"SELECT count(*) FROM pg_tables WHERE schemaname='public';").decode().strip()=='0')
 record('source was not modified',pg('lab_source',select)==before)
 # File/config rehearsal contains only fabricated, nonsecret text and no live DB files.
 import tarfile
 source=root/'synthetic-files';source.mkdir(exist_ok=True)
 (source/'attachment.txt').write_text('Synthetic attachment for a recovery rehearsal.\n')
 (source/'settings.json').write_text(json.dumps({'mode':'lab','label':'synthetic-config','schemaVersion':1})+'\n')
 with tarfile.open(root/'files.tar','w') as tar:
  for p in source.iterdir():tar.add(p,arcname=p.name)
 target=root/'restored-files';target.mkdir(exist_ok=True)
 with tarfile.open(root/'files.tar') as tar:tar.extractall(target,filter='data')
 for p in source.iterdir():record('file '+p.name+' identical',p.read_bytes()==(target/p.name).read_bytes(),hashlib.sha256(p.read_bytes()).hexdigest())
 report['dumpBytes']=len(dump);report['dumpSha256']=hashlib.sha256(dump).hexdigest();report['completedAt']=datetime.now(timezone.utc).isoformat()
finally:
 if created:
  cleanup=run(['docker','rm','-f',name],check=False);report['cleanupExitCode']=cleanup.returncode
 (root/'report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n')
print(json.dumps(report,ensure_ascii=False,indent=2))
