#!/usr/bin/env python3
"""Real systemd boot-service comparison. No test harness or mocked server.
Usage: python3 driver.py SCRATCH BASE_CHECKOUT HEAD_CHECKOUT
Requires Node 26, installed checkout dependencies, systemd user manager, ImageMagick, ffmpeg.
Artifacts contain synthetic measurements only; raw app output stays private.
"""
import datetime, hashlib, json, os, pathlib, shlex, shutil, subprocess, sys, time, urllib.request
root=pathlib.Path(sys.argv[1])/datetime.datetime.now(datetime.timezone.utc).strftime('run-%Y%m%dT%H%M%SZ')
root.mkdir(parents=True)
node=shutil.which('node')
SEED=17825792

def cmd(*args, **kw):
    return subprocess.check_output(args,text=True,stderr=subprocess.STDOUT,**kw).strip()

def run(side, checkout, port):
    checkout=pathlib.Path(checkout).resolve(); d=root/side; d.mkdir()
    home=d/'home'; runtime=home/'runtime'/'versions'/'0.1.9'; runtime.mkdir(parents=True)
    logs=home/'userdata'/'logs'; logs.mkdir(parents=True); log=logs/'boot-service.log'
    unitname='t3-log-evidence-'+side+'.service'
    config=d/'config'; units=config/'systemd'/'user'; units.mkdir(parents=True)
    unit=units/unitname
    entry=runtime/'t3'
    entry.write_text('#!/bin/sh\nexec '+shlex.quote(node)+' '+shlex.quote(str(checkout/'apps/server/src/bin.ts'))+' "$@"\n'); entry.chmod(0o755)
    (runtime/'.install-complete').write_text('0.1.9\n')
    (home/'runtime/service-state.json').write_text(json.dumps({'protocol':4,'activeVersion':'0.1.9'}))
    render='import {renderBootServiceUnit} from '+json.dumps(str(checkout/'apps/server/src/cloud/bootService.ts'))+'; console.log(renderBootServiceUnit(JSON.parse(process.argv[1])));'
    text=cmd(node,'--input-type=module','-e',render,json.dumps({'program':[str(entry),'__service-launcher'],'baseDir':str(home),'logPath':str(log),'unitPath':str(unit)}),cwd=checkout)
    text=text.replace('Environment=T3_BOOT_SERVICE_UNIT=t3code.service','Environment=T3_BOOT_SERVICE_UNIT='+unitname)
    text=text.replace('[Service]','[Service]\nEnvironment=XDG_CONFIG_HOME='+str(config)+'\nEnvironment=T3CODE_PORT='+str(port)+'\nEnvironment=T3CODE_DEV_SEED=0\nEnvironment=T3CODE_NO_BROWSER=1')
    unit.write_text(text+'\n')
    with log.open('wb') as f: f.write(b'Synthetic boot log seed\n'); f.truncate(SEED)
    original_inode=log.stat().st_ino
    events=[]; frames=d/'frames'; frames.mkdir(); start=time.monotonic()
    transcript=d/'transcript.txt'
    sha=cmd('git','-C',str(checkout),'rev-parse','HEAD')
    def observe(stage):
        stat=log.stat(); archive=pathlib.Path(str(log)+'.1')
        status=cmd('systemctl','--user','show',unitname,'-p','ActiveState','-p','SubState').replace('\n',', ')
        try:
            with urllib.request.urlopen('http://127.0.0.1:'+str(port)+'/api/health',timeout=2) as response: http=response.status
        except Exception as e: http=str(getattr(e,'code','not listening'))
        event={'seconds':round(time.monotonic()-start,1),'stage':stage,'service':status,'http':http,'logBytes':stat.st_size,'sameInode':stat.st_ino==original_inode,'archiveBytes':archive.stat().st_size if archive.exists() else None}
        events.append(event)
        lines=['BOOT SERVICE LOG / REAL SYSTEMD RUN',side.upper()+'  '+sha[:10], 'Linux | Node 26 | systemd user service | fresh isolated data', '', 'Same steps: seed 17 MiB, start, wait 15 s, restart.', 'Then append 17 MiB while running; observe for 11 minutes.', '', 'Elapsed: '+str(event['seconds'])+' s    '+stage,'Service: '+status,'HTTP /api/health: '+str(http),'boot-service.log: '+format(stat.st_size,',')+' bytes','Original inode retained: '+str(event['sameInode']),'boot-service.log.1: '+(format(event['archiveBytes'],',')+' bytes' if archive.exists() else 'absent'),'', 'Recent observations:']
        for e in events[-7:]: lines.append(f"{e['seconds']:>6}s  {e['stage']:<23} log={e['logBytes']:>10} archive={str(e['archiveBytes']):>10}")
        image=d/'measurement.png'
        subprocess.run(['convert','-size','1440x900','xc:#101820','-font','DejaVu-Sans-Mono','-pointsize','22','-fill','#f2f5f7','-gravity','NorthWest','-annotate','+32+32','\n'.join(lines),str(image)],check=True)
        shutil.copyfile(image,frames/f'{len(events):04}.png')
        transcript.write_text('Environment: Linux; Node 26; real systemd user service; fresh synthetic T3 home.\nCommit: '+sha+'\nSeed: '+str(SEED)+' bytes.\n'+''.join(json.dumps(e)+'\n' for e in events))
        print(side,json.dumps(event),flush=True)
    try:
        cmd('systemctl','--user','link','--runtime',str(unit)); cmd('systemctl','--user','daemon-reload')
        observe('seeded / stopped')
        cmd('systemctl','--user','start',unitname)
        for i in range(15): time.sleep(1); observe('start + '+str(i+1)+' s')
        cmd('systemctl','--user','restart',unitname)
        for i in range(15): time.sleep(1); observe('restart + '+str(i+1)+' s')
        # Append through an O_APPEND descriptor, just as the service manager does.
        with log.open('ab') as f:
            block=b'Synthetic repeated service output\n'*1024
            remaining=SEED
            while remaining: data=block[:remaining]; f.write(data); remaining-=len(data)
        observe('append 17 MiB live')
        for i in range(132): time.sleep(5); observe('running; no restart')
        cmd('systemctl','--user','stop',unitname); observe('stopped / final')
    finally:
        subprocess.run(['systemctl','--user','stop',unitname],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
        subprocess.run(['systemctl','--user','disable','--runtime',unitname],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
        subprocess.run(['systemctl','--user','daemon-reload'],stdout=subprocess.DEVNULL)
    (d/'observations.json').write_text(json.dumps(events,indent=2))
    # One observed sample per video frame. Labels retain actual elapsed time.
    subprocess.run(['ffmpeg','-y','-loglevel','error','-framerate','2','-i',str(frames/'%04d.png'),'-c:v','libx264','-pix_fmt','yuv420p','-crf','20',str(d/'recording.mp4')],check=True)
    return d

print('Artifacts:',root,flush=True)
for side,checkout,port in [('before',sys.argv[2],18471),('after',sys.argv[3],18472)]: run(side,checkout,port)
