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

PROJECT = Path('/data/video-pipeline/Youtube-Video-Maker/Project/30052026-001-Mac-Dinh-Chi')
CONCAT_ENDPOINT = 'http://192.168.1.104:8016/concat-mp4'
PROBE_ENDPOINT = 'http://192.168.1.104:8002/probe'
PART_RANGES = PROJECT / 'parts/part_ranges.json'
RENDER_VALIDATION = PROJECT / 'render_parts/render_validation.json'
OUTPUT_DIR = PROJECT / 'output'
OUTPUT_MP4 = OUTPUT_DIR / 'main_story.mp4'
PROBE_JSON = OUTPUT_DIR / 'main_story_probe.json'
CONCAT_REPORT = OUTPUT_DIR / 'concat_report.json'
MANIFEST = PROJECT / 'logs/step15_concat_parts_manifest.json'
DURATION_TOLERANCE = 0.5


def log(msg):
    print(time.strftime('%Y-%m-%d %H:%M:%S ') + msg, flush=True)


def post_json(url, payload, timeout=3600):
    req = urllib.request.Request(
        url,
        data=json.dumps(payload, ensure_ascii=False).encode('utf-8'),
        headers={'Content-Type': 'application/json'},
        method='POST',
    )
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode('utf-8'))


def probe(path):
    doc = post_json(PROBE_ENDPOINT, {'input_path': str(path)}, timeout=120)
    fmt = doc.get('format', {})
    streams = doc.get('streams', [])
    video_streams = [s for s in streams if s.get('codec_type') == 'video']
    audio_streams = [s for s in streams if s.get('codec_type') == 'audio']
    return {
        'path': str(path),
        'format_name': fmt.get('format_name'),
        'duration_sec': float(fmt.get('duration') or 0),
        'size': int(fmt.get('size') or 0),
        'has_video': bool(video_streams),
        'has_audio': bool(audio_streams),
        'video_streams': video_streams,
        'audio_streams': audio_streams,
        'raw': doc,
    }


def main():
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    MANIFEST.parent.mkdir(parents=True, exist_ok=True)
    part_ranges = json.loads(PART_RANGES.read_text(encoding='utf-8'))['parts']
    render_validation = json.loads(RENDER_VALIDATION.read_text(encoding='utf-8'))
    if render_validation.get('status') != 'completed':
        raise RuntimeError(f'Step 14 validation is not completed: {render_validation.get("status")}')
    valid_by_id = {p['part_id']: p for p in render_validation.get('parts', []) if p.get('valid')}

    input_paths = []
    selected_parts = []
    for part in part_ranges:
        pid = part['part_id']
        if pid not in valid_by_id:
            raise RuntimeError(f'Part is missing or invalid in render validation: {pid}')
        path = Path(valid_by_id[pid]['output_path'])
        if not path.exists():
            raise FileNotFoundError(f'Missing rendered part: {path}')
        input_paths.append(str(path))
        selected_parts.append({
            'part_id': pid,
            'path': str(path),
            'target_duration_sec': part['duration_sec'],
            'validated_duration_sec': valid_by_id[pid]['video_duration_sec'],
        })

    target_total = sum(p['target_duration_sec'] for p in selected_parts)
    validated_total = sum(p['validated_duration_sec'] for p in selected_parts)
    payload = {
        'input_paths': input_paths,
        'output_path': str(OUTPUT_MP4),
        'reencode': False,
    }
    log(f'POST {CONCAT_ENDPOINT} inputs={len(input_paths)} output={OUTPUT_MP4}')
    response = post_json(CONCAT_ENDPOINT, payload, timeout=3600)
    media = probe(OUTPUT_MP4)
    duration_diff = media['duration_sec'] - validated_total
    target_diff = media['duration_sec'] - target_total
    valid = (
        response.get('status') == 'ok'
        and OUTPUT_MP4.exists()
        and media['has_audio']
        and media['has_video']
        and abs(duration_diff) <= DURATION_TOLERANCE
    )
    probe_doc = {
        'project_id': PROJECT.name,
        'step': 15,
        'main_audio_video_path': str(OUTPUT_MP4),
        'main_duration_sec': media['duration_sec'],
        'target_total_duration_sec': target_total,
        'validated_parts_total_duration_sec': validated_total,
        'diff_vs_validated_parts_sec': round(duration_diff, 3),
        'diff_vs_target_ranges_sec': round(target_diff, 3),
        'has_audio': media['has_audio'],
        'has_video': media['has_video'],
        'format_name': media['format_name'],
        'size': media['size'],
        'video_streams': media['video_streams'],
        'audio_streams': media['audio_streams'],
    }
    report = {
        'project_id': PROJECT.name,
        'step': 15,
        'status': 'completed' if valid else 'failed_validation',
        'endpoint': CONCAT_ENDPOINT,
        'concat_response': response,
        'input_parts': selected_parts,
        'output_path': str(OUTPUT_MP4),
        'probe_path': str(PROBE_JSON),
        'duration_tolerance_sec': DURATION_TOLERANCE,
        'validation': {
            'output_exists': OUTPUT_MP4.exists(),
            'has_audio': media['has_audio'],
            'has_video': media['has_video'],
            'parts_count': len(selected_parts),
            'duration_diff_vs_validated_parts_sec': round(duration_diff, 3),
            'duration_diff_vs_target_ranges_sec': round(target_diff, 3),
            'valid': valid,
        },
    }
    PROBE_JSON.write_text(json.dumps(probe_doc, ensure_ascii=False, indent=2), encoding='utf-8')
    CONCAT_REPORT.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding='utf-8')
    MANIFEST.write_text(json.dumps({
        'project_id': PROJECT.name,
        'step': 15,
        'status': report['status'],
        'endpoint': CONCAT_ENDPOINT,
        'output_path': str(OUTPUT_MP4),
        'probe_path': str(PROBE_JSON),
        'concat_report': str(CONCAT_REPORT),
        'parts_count': len(selected_parts),
        'duration_sec': media['duration_sec'],
    }, ensure_ascii=False, indent=2), encoding='utf-8')
    if not valid:
        raise SystemExit('failed_validation')
    log('DONE ' + json.dumps({'status': report['status'], 'duration': media['duration_sec'], 'parts': len(selected_parts)}, ensure_ascii=False))


if __name__ == '__main__':
    main()
