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

PROJECT=Path('/data/video-pipeline/Youtube-Video-Maker/Project/01062026-003-Tran-Quoc-Toan')
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','#tranquoctoan','#hoaivanhau','#lacotheusauchu','#nhatran','#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':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':'Trần Quốc Toản: Lá Cờ Thêu Sáu Chữ Và Tuổi Trẻ Không Đứng Ngoài Sơn Hà | Việt Sử Kể Lại',
            'title_youtube_2':'Hoài Văn Hầu Trần Quốc Toản - Từ Quả Cam Bị Bóp Nát Đến Lời Thề Phá Cường Địch',
            'title_youtube_3':'Trần Quốc Toản Và Lá Cờ Sáu Chữ: Khí Phách Tuổi Trẻ Nhà Trần',
            'description_youtube':'Cùng Vết Mực Thời Gian kể lại câu chuyện Trần Quốc Toản: người thiếu niên nhà Trần uất nghẹn ở bến Bình Than, bóp nát quả cam, dựng lá cờ thêu sáu chữ "Phá cường địch, báo hoàng ân" và bước vào lịch sử chống Nguyên Mông của Đại Việt.',
            'hashtag_youtube':' '.join(yt_tags),
            'title_facebook':'Trần Quốc Toản - lá cờ sáu chữ và khí phách tuổi trẻ Đại Việt',
            'description_facebook':'Câu chuyện về Hoài Văn hầu Trần Quốc Toản, từ quả cam bị bóp nát ở Bình Than đến lời thề phá giặc mạnh, báo đền non sông.',
            'hashtag_facebook':' '.join(yt_tags),
            'title_tiktok':'Trần Quốc Toản và lá cờ thêu sáu chữ',
            'description_tiktok':'Từ quả cam bị bóp nát đến lá cờ “Phá cường địch, báo hoàng ân” của tuổi trẻ nhà Trầ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()
