#!/usr/bin/env python3
from pathlib import Path
import json,hashlib,os,subprocess,datetime,urllib.request
ROOT=Path('/data/video-pipeline/HaTramAudio/project/015-Buc-Tranh-Khong-Co-Chu-Ky'); RUN='run-20260720T181013Z-3b1f33ef'; CANON='8c7004f1db7fa14f14535078b34bb442d160693f3a6c94ff6d242bbd02215f45'
VIDEO=ROOT/'output/final-upload.mp4'; THUMB=ROOT/'image/normalized/intro-poster-1920x1080.png'; LOG=ROOT/'log'; CFG=Path.home()/'.config/ha-tram-audio/postiz.env'
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 atomic(p,d):
 t=p.with_name('.'+p.name+'.tmp'); t.write_text(json.dumps(d,ensure_ascii=False,indent=2)+'\n'); os.replace(t,p)
def env():
 d=dict(os.environ)
 for raw in CFG.read_text().splitlines():
  s=raw.strip()
  if s and not s.startswith('#') and '=' in s:
   k,v=s.split('=',1); d.setdefault(k.strip(),v.strip().strip("\"'"))
 req=['POSTIZ_BASE_URL','POSTIZ_API_KEY','POSTIZ_FACEBOOK_INTEGRATION_ID','POSTIZ_YOUTUBE_INTEGRATION_ID']; miss=[x for x in req if not d.get(x)]
 if miss: raise RuntimeError('missing Postiz config')
 return d
def upload(base,key,p):
 cfg=f'url = "{base.rstrip("/")}/upload"\nheader = "Authorization: {key}"\nheader = "Accept: application/json"\nform = "file=@{p}"\nsilent\nshow-error\nfail-with-body\n'
 r=subprocess.run(['curl','--config','-'],input=cfg,text=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=7200)
 if r.returncode: raise RuntimeError('Postiz upload failed: '+r.stderr[-500:])
 v=json.loads(r.stdout); m=v.get('data',v) if isinstance(v,dict) else v
 if isinstance(m,list): m=m[0] if m else {}
 if not isinstance(m,dict) or not m.get('id') or not m.get('path'): raise RuntimeError('invalid upload response')
 return {'id':m['id'],'path':m['path']}
def one(label,p):
 rp=LOG/f'upload-{label}.json'; h=sha(p)
 if rp.exists():
  d=json.loads(rp.read_text())
  if d.get('local_sha256')!=h or d.get('local_bytes')!=p.stat().st_size or not d.get('media',{}).get('id'): raise RuntimeError('stale upload receipt')
  return d
 m=upload(E['POSTIZ_BASE_URL'],E['POSTIZ_API_KEY'],p)
 d={'schema_version':1,'project_id':ROOT.name,'run_id':RUN,'status':'completed','verified':True,'source_canon_sha256':CANON,'local_path':str(p),'local_sha256':h,'local_bytes':p.stat().st_size,'media':m,'completed_at':now()};atomic(rp,d);return d
ready=json.loads((LOG/'publish-ready.json').read_text()); poster=json.loads((LOG/'poster.json').read_text())
if ready.get('status')!='completed' or ready.get('readiness')!='ready' or ready.get('verified') is not True: raise RuntimeError('publish-ready failed')
if sha(VIDEO)!=ready['artifacts']['upload_copy']['sha256'] or VIDEO.stat().st_size>=1_000_000_000: raise RuntimeError('video mismatch')
if sha(THUMB)!=poster['artifact_sha256'] or poster.get('text_postprocessed') is not False: raise RuntimeError('thumbnail mismatch')
E=env(); base=E['POSTIZ_BASE_URL'].rstrip('/'); req=urllib.request.Request(base+'/is-connected',headers={'Authorization':E['POSTIZ_API_KEY']})
with urllib.request.urlopen(req,timeout=60) as r:
 if json.load(r).get('connected') is not True: raise RuntimeError('Postiz disconnected')
v=one('video',VIDEO); t=one('thumbnail',THUMB)
out={'schema_version':1,'project_id':ROOT.name,'gate':'upload','status':'completed','verified':True,'run_id':RUN,'source_canon_sha256':CANON,'source_poster_sha256':poster['artifact_sha256'],'source_layout_sha256':ready['artifacts']['layout']['sha256'],'artifact_path':str(VIDEO),'artifact_sha256':sha(VIDEO),'local_bytes':VIDEO.stat().st_size,'video_media':v['media'],'thumbnail_media':t['media'],'video_receipt':'log/upload-video.json','thumbnail_receipt':'log/upload-thumbnail.json','publish_ready_receipt':'log/publish-ready.json','completed_at':now()};atomic(LOG/'upload.json',out);print(json.dumps({'status':'completed','verified':True,'video_media_id':v['media']['id'],'thumbnail_media_id':t['media']['id'],'local_bytes':out['local_bytes']},ensure_ascii=False))
