import hashlib
import json
from pathlib import Path
import remote

ROOT=Path(__file__).resolve().parent
before=json.loads((ROOT/'before-switch.json').read_text())
expected={str(p.relative_to(ROOT/'candidate')):hashlib.sha256(p.read_bytes()).hexdigest()
          for p in (ROOT/'candidate'/'app').rglob('*.py')}
appcode=r'''
import os,psycopg2,hashlib,json
from pathlib import Path
c=psycopg2.connect(os.environ['DATABASE_URL'],connect_timeout=5,options='-c statement_timeout=8000');c.set_session(readonly=True)
with c.cursor() as q:
 q.execute("SELECT row_to_json(t)::text FROM contextra_event_artifacts t WHERE delivery_state='BLOCKED_TIME' AND created_at<=%s AND updated_at<=%s ORDER BY event_id",(CUTOFF,CUTOFF))
 h=hashlib.sha256();n=0
 for row in q:h.update(row[0].encode()+b'\n');n+=1
c.rollback();c.close()
source={str(p.relative_to('/opt/contextra')):hashlib.sha256(p.read_bytes()).hexdigest() for p in Path('/opt/contextra/app').rglob('*.py')}
children=[]
for p in Path('/proc').glob('[0-9]*/cmdline'):
 try:args=p.read_bytes()
 except OSError:continue
 if b'multiprocessing.spawn' in args and b'python' in args:children.append(int(p.parent.name))
print(json.dumps({'old_blocked_rows':n,'old_blocked_sha256':h.hexdigest(),
 'source_matches_candidate':source==EXPECTED,'python_files':len(source),'provider_children':children}))
'''
sidecode=r'''
import os,json,urllib.request
from pathlib import Path
t=Path(os.environ['CONTEXTRA_CONTROL_TOKEN_FILE']).read_text().strip()
out={}
for path in ('/ready','/internal/bridge-status'):
 r=urllib.request.Request('http://127.0.0.1:8080'+path,headers={'Authorization':'Bearer '+t})
 with urllib.request.urlopen(r,timeout=5) as f:out[path]=json.load(f)
print(json.dumps(out))
'''
code='''
import json,subprocess,sys,shutil
sys.path.insert(0,'/usr/local/lib/contextra-maintenance/portable-20261006T192646')
import compose_backend as cb,server_context as s
rows=cb.containers()
def role(name):return next(r for r in rows if r['Config']['Labels']['com.docker.compose.service']==s.service(name))
app=role('analytics')
oldrow=json.loads(subprocess.check_output(['docker','image','inspect','sha256:4648079cdd0330f1da5e492b172718e29ad722fb403444bb7ac0241d330fbd9a']))[0]
newrow=json.loads(subprocess.check_output(['docker','image','inspect',app['Image']]))[0]
old,new=oldrow['Config'],newrow['Config']
image_diff={k:{'old':old.get(k),'new':new.get(k)} for k in set(old)|set(new) if old.get(k)!=new.get(k) and k not in ('Env','Labels')}
expected=dict(x.split('=',1) for x in old['Env']);expected.update(cb.inspect()[3]['services']['analytics']['environment'])
actual=dict(x.split('=',1) for x in app['Config']['Env'])
out={'image_config_changed_keys':sorted(image_diff),'effective_env_matches_baseline':actual==expected,
 'base_layers_preserved':newrow['RootFS']['Layers'][:len(oldrow['RootFS']['Layers'])]==oldrow['RootFS']['Layers'],
 'new_layers':len(newrow['RootFS']['Layers'])-len(oldrow['RootFS']['Layers'])}
p=subprocess.run(['docker','exec','-i',app['Id'],'python3','-'],input=APPCODE.encode(),capture_output=True,timeout=30)
assert p.returncode==0,p.stderr.decode()[-1000:]
out['app']=json.loads(p.stdout)
p=subprocess.run(['docker','exec','-i',role('ai-control-sidecar')['Id'],'python3','-'],input=SIDECODE.encode(),capture_output=True,timeout=20)
out['sidecar_probe_code']=p.returncode
if p.returncode==0:out['sidecar']=json.loads(p.stdout)
else:out['sidecar_probe_error']=p.stderr.decode()[-700:]
g=role('ai-gateway')
if g['State']['Running']:
 p=subprocess.run(['docker','exec',g['Id'],'nginx','-t','-c','/tmp/nginx.conf'],capture_output=True,text=True,timeout=10)
 out['effective_nginx_config_ok']=p.returncode==0
out['root_free_bytes']=shutil.disk_usage('/').free
print(json.dumps(out))
'''
appcode='CUTOFF='+repr(before['app']['blocked_time']['cutoff'])+'\nEXPECTED='+repr(expected)+'\n'+appcode
out=remote.run('APPCODE='+repr(appcode)+'\nSIDECODE='+repr(sidecode)+'\n'+code,root=True,timeout=80)
remote.save('preservation-final.json',out)
value=json.loads(out)
value['old_blocked_unchanged']=(value['app']['old_blocked_rows']==before['app']['blocked_time']['rows'] and
 value['app']['old_blocked_sha256']==before['app']['blocked_time']['sha256'])
print(json.dumps(value,indent=2))
