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

PROJECT=Path('/data/video-pipeline/Youtube-Video-Maker/Project/01062026-005-Ly-Nam-De')
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','-of','json',str(path)], text=True)
    s=(json.loads(out).get('streams') or [{}])[0]
    return int(s.get('width') or 0), int(s.get('height') or 0)
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=['sshpass','-p','11210119','ssh','-o','StrictHostKeyChecking=no','-o','PreferredAuthentications=password','-o','PubkeyAuthentication=no','root@192.168.1.104',
             f"ffmpeg -y -hide_banner -loglevel error -ss {sec:.3f} -i '{FINAL}' -frames:v 1 -q:v 2 '{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)
    if not final_width or not final_height:
        final_width,final_height=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,
        '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, 1920x1080 16:9 resolution, 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','#phamngulao','#nhatran','#nguyenmong','#daiviet','#vietsukelai','#lichsuvietnam']
    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':'Lý Nam Đế: Người Đan Sọt Giữa Đường Và Ngọn Giáo Chống Nguyên Mông | Việt Sử Kể Lại',
            'title_youtube_2':'Lý Nam Đế - Từ Làng Phù Ủng Đến Danh Tướng Nhà Trần',
            'title_youtube_3':'Người Trai Đan Sọt Và Hào Khí Đông A Của Đại Việt',
            'description_youtube':'Cùng Vết Mực Thời Gian kể lại cuộc đời Lý Nam Đế: từ chàng trai làng Phù Ủng ngồi đan sọt bên đường đến danh tướng nhà Trần, người góp công trong kháng chiến chống Nguyên Mông và để lại hình tượng ngọn giáo giữa non sông Đại Việt.',
            'hashtag_youtube':' '.join(yt_tags),
            'title_facebook':'Lý Nam Đế - người trai làng Phù Ủng bước vào lịch sử nhà Trần',
            'description_facebook':'Câu chuyện về Lý Nam Đế, từ hình ảnh đan sọt bên đường đến danh tướng góp phần làm nên hào khí Đông A.',
            'hashtag_facebook':' '.join(yt_tags),
            'title_tiktok':'Lý Nam Đế - người đan sọt thành danh tướng',
            'description_tiktok':'Từ làng Phù Ủng đến chiến công chống Nguyên Mông, đây là câu chuyện Lý Nam Đế.',
            '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()
