#!/usr/bin/env python3
from pathlib import Path
import datetime,hashlib,json,os,subprocess,sys
ROOT=Path('/data/video-pipeline/HaTramAudio/project/025-Nguoi-Giu-Cho-Cuoi-Cung');PID=ROOT.name;RUN='run-20260721T232244Z-a0837ee4';OWNER='Levy';WORK=ROOT/'work/artwork'/RUN;GEN=Path.home()/'.hermes/skills/content-creation/tao-anh/scripts/generate_image.py'
OUTPUTS={'intro_poster':WORK/'intro-poster-provider.png','left_panel':WORK/'left-panel-provider.png','right_panel':WORK/'right-panel-provider.png'}
def now():return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
def sha(p):
 h=hashlib.sha256()
 with p.open('rb') as f:
  for b in iter(lambda:f.read(8*1024*1024),b''):h.update(b)
 return h.hexdigest()
def guard():
 d=json.loads((ROOT/'.ownership-lock.json').read_text())
 if any(d.get(k)!=v for k,v in {'project_id':PID,'run_id':RUN,'owner':OWNER}.items()):raise RuntimeError('ownership mismatch')
 m=json.loads((ROOT/'script/project-manifest.json').read_text());e=m.get('gate_events',{}).get('story',{});canon=m.get('canon_sha256')
 if m.get('steps',{}).get('story')!='completed' or e.get('verified') is not True or e.get('canon_sha256')!=canon:raise RuntimeError('Story Gate is not committed/current')
 if sha(ROOT/'story/story-canon.txt')!=canon:raise RuntimeError('canon drift')
 return m,canon
def atomic(p,d):
 guard();p.parent.mkdir(parents=True,exist_ok=True);t=p.with_name('.'+p.name+'.tmp');t.write_text(json.dumps(d,ensure_ascii=False,indent=2)+'\n');os.replace(t,p)
def main():
 m,canon=guard();bundle=json.loads((ROOT/'script/image-prompts.json').read_text())
 if bundle.get('source_canon_sha256')!=canon or bundle.get('provider_skill')!='tao-anh' or bundle.get('text_postprocessing_allowed') is not False:raise RuntimeError('prompt bundle contract mismatch')
 for rel,expected in bundle.get('authority_sha256',{}).items():
  if sha(ROOT/rel)!=expected:raise RuntimeError('authority drift '+rel)
 done=[]
 for role,out in OUTPUTS.items():
  guard();item=bundle.get('items',{}).get(role,{});prompt_path=Path(item.get('prompt_path','')).resolve()
  if WORK.resolve() not in prompt_path.parents or not prompt_path.is_file() or sha(prompt_path)!=item.get('prompt_sha256'):raise RuntimeError('prompt binding mismatch '+role)
  prompt=prompt_path.read_text(encoding='utf-8');receipt=WORK/f'{role}.generation.json'
  if receipt.exists() and out.exists():
   old=json.loads(receipt.read_text())
   if old.get('status')=='generated' and old.get('source_canon_sha256')==canon and old.get('prompt_sha256')==item['prompt_sha256'] and old.get('artifact_sha256')==sha(out):done.append(old);continue
   raise RuntimeError('stale artwork receipt/artifact '+role)
  if out.exists():raise RuntimeError('unreceipted artwork exists '+role)
  attempt={'schema_version':1,'project_id':PID,'run_id':RUN,'status':'requesting','verified':False,'provider_skill':'tao-anh','provider_model':'cx/gpt-5.5-image','source_canon_sha256':canon,'role':role,'prompt_path':str(prompt_path),'prompt_sha256':item['prompt_sha256'],'output':str(out),'text_postprocessed':False,'created_at':now()};atomic(receipt,attempt)
  result=subprocess.run([sys.executable,str(GEN),'--prompt',prompt,'--output',str(out),'--n','1','--size','auto','--quality','auto','--background','auto','--detail','high','--format','png','--timeout','600'],text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=720)
  if result.returncode!=0:
   attempt.update({'status':'failed','verified':False,'error_type':'provider_failed','error':(result.stderr or result.stdout)[-1200:],'updated_at':now()});atomic(receipt,attempt);raise RuntimeError('provider failed '+role)
  if not out.is_file() or out.stat().st_size<1024 or out.read_bytes()[:8]!=b'\x89PNG\r\n\x1a\n':raise RuntimeError('invalid provider PNG '+role)
  guard();attempt.update({'status':'generated','verified':False,'technical_verified':True,'artifact_path':str(out),'artifact_sha256':sha(out),'artifact_bytes':out.stat().st_size,'provider_requests':1,'completed_at':now()});atomic(receipt,attempt);done.append(attempt)
 print(json.dumps({'status':'generated_awaiting_artwork_qa','verified':False,'items':[{'role':x['role'],'path':x['artifact_path'],'sha256':x['artifact_sha256']} for x in done]},ensure_ascii=False))
if __name__=='__main__':main()
