import copy,json,os,subprocess,sys,time,urllib.request
from pathlib import Path
os.umask(0o077)
ROOT=Path('/backup/contextra/deployment-inbox/icerti-crowd-20260928-r01')
RUNTIME=Path('/workspace/contextra/runtime')
sys.path.insert(0,str(RUNTIME/'deploy-tools/provider-20260927-r01'))
from deployment_kit import compose_snapshot
from deployment_kit.core import atomic_write,write_json,sha256
from deployment_kit.site_guard import verify
def cmd(args,timeout=30):return subprocess.check_output(args,text=True,stderr=subprocess.STDOUT,timeout=timeout,env=compose_snapshot.command_environment())
def inspect(name):return json.loads(cmd(['docker','inspect','contextra-icerti-'+name+'-1']))[0]
def api(path):return json.load(urllib.request.urlopen('http://127.0.0.1:35000'+path,timeout=10))
p=json.loads((ROOT/'private/prepared.json').read_text());b=json.loads((ROOT/'inventory.json').read_text())
for n,h in b['baseline_hashes'].items():assert sha256(RUNTIME/'state'/n)==h,'DEPLOY_CAS_'+n
for path,h in p['old_inputs'].items():assert sha256(path)==h,'CONFIG_CAS'
compose_snapshot.verify_bindings(p['bindings'])
unit=Path('/etc/systemd/system/contextra-runtime.service');assert sha256(unit)==p['unit_sha256']
assert all(not inspect(s)['State']['Running'] for s in ('analytics','ai-control-sidecar','ai-gateway','postgresql','redis'))
assert not (ROOT/'activated.json').exists()
active=json.loads((RUNTIME/'state/active.json').read_text());tx=RUNTIME/'state/transactions'/p['transaction']
write_json(RUNTIME/'state/inflight.json',dict(status='APPLYING',stage='CROWD_PATCH',transaction=p['transaction']))
write_json(RUNTIME/'state/site.json',p['site'],expected=b['baseline_hashes']['site.json'])
atomic_write(unit,p['unit_after'],mode=0o644,expected=p['unit_sha256'])
cmd(['systemctl','daemon-reload'])
verify(RUNTIME/'state/site.json')
active.update(transaction=p['transaction'],images=p['images'],compose_sha256=sha256(tx/'compose.json'),
    normalized_sha256=sha256(tx/'compose.normalized.json'),site_sha256=sha256(RUNTIME/'state/site.json'),
    config_inputs=p['new_inputs'],bound_files=p['bindings'],time=time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),
    incremental_patch=dict(id='icerti-crowd-20260928-r01',source_head='0f172d948cacf1e4444c40ecc09d583f29644c74',
        manifest=str(ROOT/'kit/manifest.json'),manifest_sha256=sha256(ROOT/'kit/manifest.json'),
        tools=str(RUNTIME/'deploy-tools/provider-20260927-r01'),previous_transaction=b['active_transaction'],
        note='Crowd/cooldown and Gateway allowlist; field engines/native unchanged; A13 pending'),
    acceptance=dict(DEPLOYED='IN_PROGRESS',MODELS='PENDING',A13='PENDING',GATEWAY='PENDING',WEB_BROWSER='USER_PENDING'))
write_json(RUNTIME/'state/active.json',active,expected=b['baseline_hashes']['active.json'])
compose=['docker','compose','-p',p['site']['project'],'-f',p['compose']]
def ready(name,seconds):
    until=time.monotonic()+seconds
    while time.monotonic()<until:
        c=inspect(name);assert c['State']['Running'] and not c['State']['OOMKilled'],'SERVICE_FAILED_'+name
        if c['State'].get('Health',{}).get('Status')=='healthy':return
        time.sleep(2)
    raise RuntimeError('SERVICE_NOT_READY_'+name)
for name in ('postgresql','redis','analytics','ai-control-sidecar'):
    cmd(compose+['up','-d','--no-deps','--no-build','--pull','never',name],90)
    ready(name,180 if name=='analytics' else 60)
    print(name+' READY',flush=True)
    if name in ('postgresql','redis'):
        assert inspect(name)['Id']==b['containers']['contextra-icerti-'+name+'-1']['id'],'DATA_SERVICE_RECREATED'
cmd(['systemctl','restart','contextra-redis-access.service'],30)
rows=api('/api/v1/streams')['streams']
assert all(r['state']=='stopped' and not r.get('cleanup_pending') for r in rows),'UNEXPECTED_AUTOSTART'
code='''import json,urllib.request
out={}
for m in ('RFDETRMedium','RFDETRKeypointPreview','RFDETRFireSmoke','Awiros_person_and_head_detection'):
 c=json.load(urllib.request.urlopen('http://localhost:8000/v2/models/'+m+'/config',timeout=4))
 assert c['max_batch_size']==8 and c['version_policy']['specific']['versions']==[8]
 assert c['dynamic_batching']['preferred_batch_size']==[8] and all(g['count']==8 for g in c['instance_group'])
 out[m]={'ready':urllib.request.urlopen('http://localhost:8000/v2/models/'+m+'/ready',timeout=4).status,'config':c}
print(json.dumps(out))'''
models=json.loads(cmd(['docker','exec','contextra-icerti-analytics-1','python3','-B','-c',code],25))
artifact=api('/api/v1/metadata/status')['artifacts']
assert artifact['enabled'] and artifact['http_enabled'] and artifact['storage_cap_bytes']==4_000_000_000_000
runtime=Path(p['runtime_root'])
assert {str(f.relative_to(runtime)):sha256(f) for f in runtime.rglob('*') if f.is_file() and not f.is_symlink()}==p['runtime_hashes'],'SAVED_CAMERA_CONFIG_CHANGED'
active['acceptance'].update(DEPLOYED='PASS',MODELS='PASS')
write_json(RUNTIME/'state/active.json',active)
write_json(tx/'applied.json',active,new=True)
write_json(RUNTIME/'state/inflight.json',dict(status='DONE',stage='A13_PENDING',transaction=p['transaction']))
write_json(ROOT/'models.json',models,new=True)
write_json(ROOT/'activated.json',dict(transaction=p['transaction'],channels_stopped=len(rows),models_ready=4,
    saved_runtime_unchanged=True,data_container_ids_unchanged=True,gateway='stopped_for_trial'),new=True)
print(json.dumps(dict(status='ACTIVATED_A13_PENDING',channels_stopped=len(rows),models_ready=4)))
