#!/usr/bin/env python3
from pathlib import Path
import datetime,hashlib,json,os
ROOT=Path('/data/video-pipeline/HaTramAudio/project/022-Tiem-Giat-Mo-Cua-Luc-Bon-Gio-Muoi-Bay');PID=ROOT.name;RUN='run-20260721T172022Z-9462493a';OWNER='Levy'
def now():return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def guard():
 d=json.loads((ROOT/'.ownership-lock.json').read_text())
 for k,v in {'project_id':PID,'run_id':RUN,'owner':OWNER}.items():
  if d.get(k)!=v:raise RuntimeError('ownership mismatch '+k)
def atomic_text(p,s):
 guard();p.parent.mkdir(parents=True,exist_ok=True);t=p.with_name('.'+p.name+'.tmp');t.write_text(s);os.replace(t,p)
def atomic_json(p,d):atomic_text(p,json.dumps(d,ensure_ascii=False,indent=2)+'\n')
def main():
 guard();m=json.loads((ROOT/'script/project-manifest.json').read_text());canon=m.get('canon_sha256')
 story=m.get('gate_events',{}).get('story',{})
 if m.get('steps',{}).get('story')!='completed' or story.get('verified') is not True or story.get('canon_sha256')!=canon:raise RuntimeError('Story Gate is not committed/current')
 promo=json.loads((ROOT/'script/promotion-report.json').read_text());brief=ROOT/'script/story-brief.json';brief_data=json.loads(brief.read_text())
 if not promo.get('verified') or promo.get('canon_sha256')!=canon or sha(ROOT/'story/story-canon.txt')!=canon:raise RuntimeError('canon promotion invalid')
 if brief_data.get('project_id')!=PID or brief_data.get('run_id')!=RUN:raise RuntimeError('story brief identity mismatch')
 title=brief_data['canonical_title'];genres=brief_data['genre']
 summary='Giang Vãn Đường phát hiện một tiệm giặt chỉ mở mười ba phút lúc bốn giờ mười bảy sáng, nơi mỗi lần sửa quá khứ đều lấy đi một ký ức chung. Khi biết mình đã ba lần xóa tình yêu với Tạ Hoài Chu để trốn hậu quả lựa chọn, cô phải quyết định có đánh đổi ký ức cuối cùng hay tự xây lại quan hệ từ hiện tại.'
 yt=f'Truyện Audio | {title} (Full) | Hạ Trâm Audio'
 if not 2<=len(yt)<=100:raise RuntimeError('youtube title length invalid')
 info=f'{yt}\n\n{summary}\n\nThể loại: '+', '.join(genres)+'\n\nTruyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức\n'
 ip=ROOT/'output/info.txt';atomic_text(ip,info)
 metadata={'schema_version':1,'source_project_id':PID,'project_id':PID,'run_id':RUN,'status':'completed','verified':True,'source_canon_sha256':canon,'source_story_brief_sha256':sha(brief),'canonical_title':title,'youtube_title':yt,'title_length':len(yt),'description':summary,'summary':summary,'genres':genres,'tags':[],'thumbnail_text':title,'copyright_line':'Truyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức','authority_sha256':{'story_canon':canon,'story_brief':sha(brief)},'contains_spoiler_reveal':False}
 mp=ROOT/'script/metadata.json';atomic_json(mp,metadata);receipt={**metadata,'artifact_path':str(ip),'artifact_sha256':sha(ip),'metadata_json_path':str(mp),'metadata_json_sha256':sha(mp),'completed_at':now()};atomic_json(ROOT/'log/metadata.json',receipt);print(json.dumps({'status':'completed','verified':True,'youtube_title':yt,'artifact_sha256':receipt['artifact_sha256']},ensure_ascii=False))
if __name__=='__main__':main()
