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

PROJECT=Path('/data/video-pipeline/Youtube-Video-Maker/Project/05062026-002-Dinh-Bo-Linh')
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 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()
    validation={
        'final_exists':FINAL.exists() and FINAL.stat().st_size>1000,
        'has_audio':has(pr,'audio'),'has_video':has(pr,'video'),
        '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['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','#dinhbolinh','#dinhtienhoang','#hoalu','#daicoviet','#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':'Đinh Bộ Lĩnh: Từ Cờ Lau Đến Người Dẹp Loạn Mười Hai Sứ Quân | Việt Sử Kể Lại',
            'title_youtube_2':'Đinh Bộ Lĩnh - Từ Cậu Bé Cờ Lau Đến Hoàng Đế Đại Cồ Việt',
            'title_youtube_3':'Đinh Bộ Lĩnh Và Hành Trình Dẹp Loạn Mười Hai Sứ Quân',
            'description_youtube':'Cùng Vết Mực Thời Gian kể lại cuộc đời Đinh Bộ Lĩnh: từ cậu bé cờ lau ở Hoa Lư đến người dẹp loạn mười hai sứ quân, thống nhất đất nước và dựng nên nhà Đinh, mở đầu quốc hiệu Đại Cồ Việt.',
            'hashtag_youtube':' '.join(yt_tags),
            'title_facebook':'Đinh Bộ Lĩnh - từ cờ lau Hoa Lư đến giấc mộng thống nhất Đại Việt',
            'description_facebook':'Câu chuyện về Đinh Bộ Lĩnh, người bước ra từ tuổi thơ cờ lau để dẹp loạn mười hai sứ quân và dựng nền độc lập cho Đại Cồ Việt.',
            'hashtag_facebook':' '.join(yt_tags),
            'title_tiktok':'Đinh Bộ Lĩnh - cậu bé cờ lau dẹp loạn sứ quân',
            'description_tiktok':'Từ Hoa Lư đến ngôi hoàng đế, đây là câu chuyện Đinh Bộ Lĩnh dẹp loạn mười hai sứ quân.',
            '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()
