#!/usr/bin/env python3
import json, time, urllib.request, subprocess
from pathlib import Path

PROJECT=Path('/data/video-pipeline/Youtube-Video-Maker/Project/25012026-001-Thanh-Cat-Tu-Han')
PROBE='http://192.168.1.104:8018/probe'
BLACK='http://192.168.1.104:8018/blackdetect'
FINAL=PROJECT/'output/final/final_video.mp4'
FINAL_PROBE=PROJECT/'output/final/final_video_probe.json'
PKG_REPORT=PROJECT/'output/final/final_package_report.json'
QA_REPORT=PROJECT/'output/final/final_qa_report.json'
INFO=PROJECT/'output/info.json'
THUMB16=PROJECT/'output/thumbnails/thumb_16x9_final.png'
THUMB9=PROJECT/'output/thumbnails/thumb_9x16_final.png'
MANIFEST=PROJECT/'logs/step19_final_qa_info_manifest.json'

def post(url,payload,timeout=21600):
    req=urllib.request.Request(url,data=json.dumps(payload,ensure_ascii=False).encode(),headers={'Content-Type':'application/json'},method='POST')
    with urllib.request.urlopen(req,timeout=timeout) as r: return json.loads(r.read().decode())
def load(p, default=None):
    return json.loads(p.read_text(encoding='utf-8')) if p.exists() else default
def dur(pr): return float(pr.get('format',{}).get('duration') or 0)
def has(pr,t): return any(s.get('codec_type')==t for s in pr.get('streams',[]))
def video_stream(pr): return next((s for s in pr.get('streams',[]) if s.get('codec_type')=='video'), {})
def ffprobe_dimensions(path):
    out=subprocess.check_output(['ffprobe','-v','error','-select_streams','v:0','-show_entries','stream=width,height,display_aspect_ratio','-of','json',str(path)], text=True, timeout=60)
    s=(json.loads(out).get('streams') or [{}])[0]
    return int(s.get('width') or 0), int(s.get('height') or 0), s.get('display_aspect_ratio')
def count_files(path, glob): return len(list(Path(path).glob(glob))) if Path(path).exists() else 0

def extract_frames(times):
    outdir=PROJECT/'output/final/final_qa_frames'; outdir.mkdir(parents=True,exist_ok=True)
    frames=[]
    for label,sec in times:
        out=outdir/f'{label}_{sec:.1f}.jpg'
        cmd=['ffmpeg','-y','-hide_banner','-loglevel','error','-ss',f'{sec:.3f}','-i',str(FINAL),'-frames:v','1','-q:v','2',str(out)]
        subprocess.run(cmd,check=True,timeout=120)
        frames.append({'label':label,'time_sec':sec,'path':str(out),'exists':out.exists()})
    return frames

def parse_meta():
    txt=(PROJECT/'script/source_story.txt').read_text(encoding='utf-8')
    series='Việt Sử Kể Lại'; title='NGƯỜI CON GÁI HÁI DÂU BƯỚC VÀO LONG MẠCH ĐẠI VIỆT'
    for line in txt.splitlines():
        low=line.lower()
        if low.startswith('series:'): series=line.split(':',1)[1].strip()
        if low.startswith('tiêu đề truyện:') or low.startswith('tieu de truyen:'): title=line.split(':',1)[1].strip().title()
    return series,title

def main():
    if not FINAL.exists(): raise SystemExit('missing final')
    pr=post(PROBE,{'input_path':str(FINAL)},120)
    FINAL_PROBE.write_text(json.dumps(pr,ensure_ascii=False,indent=2),encoding='utf-8')
    pkg=load(PKG_REPORT,{})
    black=post(BLACK,{'input_path':str(FINAL),'log_path':str(PROJECT/'output/final/final_qa_blackdetect.log')},21600)
    total=dur(pr)
    intro_dur=dur(pkg.get('input_probes',{}).get('intro',{})) or 16.2
    main_dur=dur(pkg.get('input_probes',{}).get('main',{})) or 697.682
    outro_start=max(0,total-25)
    times=[('intro_transition',max(0,intro_dur-1)),('early_main',intro_dur+30),('mid_main',intro_dur+main_dur/2),('late_main',intro_dur+max(0,main_dur-45)),('outro',outro_start+5)]
    frames=extract_frames(times)
    master_probe=load(PROJECT/'audio/master_audio_probe.json',{})
    main_probe=load(PROJECT/'output/main_story_probe.json',{})
    scenes=load(PROJECT/'script/scenes.json',{}).get('scenes',[])
    parts=load(PROJECT/'parts/part_ranges.json',{}).get('parts',[])
    series,title=parse_meta()
    vs=video_stream(pr)
    final_width=int(vs.get('width') or 0); final_height=int(vs.get('height') or 0); final_dar=vs.get('display_aspect_ratio')
    if not final_width or not final_height:
        final_width, final_height, final_dar = ffprobe_dimensions(FINAL)
    validation={
        'final_exists':FINAL.exists() and FINAL.stat().st_size>1000,
        'has_audio':has(pr,'audio'),'has_video':has(pr,'video'),
        'final_width':final_width,'final_height':final_height,'final_display_aspect_ratio':final_dar,
        'resolution_16x9_1080p': final_width==1920 and final_height==1080,
        'thumbnail_16x9_exists':THUMB16.exists(),'thumbnail_9x16_exists':THUMB9.exists(),
        'black_event_count':black.get('event_count',0),'blackdetect_clear':black.get('event_count',0)==0,
        'qa_frames_extracted':all(f['exists'] for f in frames),
        'package_completed':pkg.get('status') in ('completed','needs_review'),
        'bgm_volume':pkg.get('validation',{}).get('bgm_volume'),
        'decision':'approved_output_candidate_after_automated_qa; human spot-check recommended for subjective BGM/sync'
    }
    passed=all([validation['final_exists'],validation['has_audio'],validation['has_video'],validation['resolution_16x9_1080p'],validation['thumbnail_16x9_exists'],validation['thumbnail_9x16_exists'],validation['blackdetect_clear'],validation['qa_frames_extracted'],validation['package_completed']])
    qa={'project_id':PROJECT.name,'step':19,'passed':passed,'status':'passed' if passed else 'failed','final_video_path':str(FINAL),'final_duration_sec':total,'qa_points':times,'qa_frames':frames,'blackdetect':black,'validation':validation,'notes':['Automated QA checks streams, duration, black frames, frames at intro/early/mid/late/outro. User should still listen/spot-check BGM and sync before publishing.']}
    QA_REPORT.write_text(json.dumps(qa,ensure_ascii=False,indent=2),encoding='utf-8')
    yt_tags=['#vetmucthoigian','#thanhcattuhan','#genghiskhan','#mongol','#lichsuthegioi','#lichsu','#worldhistory']
    info={
        'project_id':PROJECT.name,'series':series,'title':title,'approved_output':passed,'qa_final_status':qa['status'],
        'paths':{
            'source_script':str(PROJECT/'script/source_story.txt'),'cleaned_story':str(PROJECT/'script/cleaned_story.txt'),'dense_plan':str(PROJECT/'script/scenes.json'),'character_bible':str(PROJECT/'script/character_bible.json'),
            'scene_images_dir':str(PROJECT/'images/scenes'),'thumbnails':{'16x9':str(THUMB16),'9x16':str(THUMB9)},'voice_segments_dir':str(PROJECT/'audio/voice_segments'),'scene_tts_dir':str(PROJECT/'audio/scenes'),'master_narration':str(PROJECT/'audio/master_narration.wav'),
            'subtitles':str(PROJECT/'subtitles/master_subtitles_clamped.srt'),'part_ranges':str(PROJECT/'parts/part_ranges.json'),'rendered_parts_dir':str(PROJECT/'render_parts'),'main_story':str(PROJECT/'output/main_story.mp4'),
            'intro':str(PROJECT/'output/intro/intro.mp4'),'final_output':str(FINAL),'final_qa_report':str(QA_REPORT),'final_package_report':str(PKG_REPORT)
        },
        'durations':{'master_audio_sec':master_probe.get('master_duration_sec') or dur(load(PROJECT/'audio/master_audio_probe.json',{})),'main_story_sec':main_probe.get('main_duration_sec') or dur(main_probe),'final_sec':total},
        'counts':{'scenes':len(scenes),'scene_images':count_files(PROJECT/'images/scenes','scene_*.png'),'parts':len(parts)},
        'social_metadata':{
            'title_youtube_1':'Thành Cát Tư Hãn: Từ Đứa Trẻ Bị Bỏ Rơi Đến Cơn Bão Làm Rung Chuyển Thế Giới',
            'title_youtube_2':'Thành Cát Tư Hãn - Người Biến Thảo Nguyên Mông Cổ Thành Đế Chế Khổng Lồ',
            'title_youtube_3':'Genghis Khan: Luật Sắt, Vó Ngựa Và Đế Chế Mông Cổ',
            'description_youtube':'Cùng Vết Mực Thời Gian kể lại cuộc đời Thành Cát Tư Hãn: từ tuổi thơ bị bỏ rơi bên thảo nguyên Mông Cổ đến quá trình thống nhất các bộ lạc, xây dựng cỗ máy chiến tranh và đặt nền móng cho một đế chế làm rung chuyển Á-Âu.',
            'hashtag_youtube':' '.join(yt_tags),
            'title_facebook':'Thành Cát Tư Hãn - từ đứa trẻ bị bỏ rơi đến cơn bão thảo nguyên',
            'description_facebook':'Câu chuyện về Temüjin, người vượt qua đói khát, phản bội và chiến tranh để trở thành Thành Cát Tư Hãn, biểu tượng dữ dội của lịch sử Mông Cổ và thế giới.',
            'hashtag_facebook':' '.join(yt_tags),
            'title_tiktok':'Thành Cát Tư Hãn - cơn bão Mông Cổ',
            'description_tiktok':'Từ thảo nguyên lạnh giá, Temüjin đã trở thành Thành Cát Tư Hãn và làm cả lục địa phải nhớ tên mình.',
            'hashtag_tiktok':' '.join(yt_tags)
        }
    }
    INFO.write_text(json.dumps(info,ensure_ascii=False,indent=2),encoding='utf-8')
    MANIFEST.write_text(json.dumps({'project_id':PROJECT.name,'step':19,'status':qa['status'],'info_path':str(INFO),'qa_report_path':str(QA_REPORT),'final_output':str(FINAL)},ensure_ascii=False,indent=2),encoding='utf-8')
    print(time.strftime('%Y-%m-%d %H:%M:%S ')+'DONE '+json.dumps({'status':qa['status'],'final_duration':total,'info':str(INFO)},ensure_ascii=False),flush=True)
    if not passed: raise SystemExit('failed_validation')
if __name__=='__main__': main()
