#!/usr/bin/env python3
import argparse
import json
from pathlib import Path

import cv2


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Compose a processed ROI span back into a full video.")
    p.add_argument("--input", required=True, help="Base full-frame input video")
    p.add_argument("--roi-video", required=True, help="Processed ROI clip covering start..end frames")
    p.add_argument("--output", required=True, help="Output video path (.mkv=FFV1, otherwise mp4v)")
    p.add_argument("--start-frame", type=int, required=True, help="Inclusive start frame index")
    p.add_argument("--end-frame", type=int, required=True, help="Inclusive end frame index")
    p.add_argument("--x1", type=int, required=True, help="ROI left")
    p.add_argument("--y1", type=int, required=True, help="ROI top")
    p.add_argument("--x2", type=int, required=True, help="ROI right")
    p.add_argument("--y2", type=int, required=True, help="ROI bottom")
    p.add_argument("--report-json", default="", help="Optional report json path")
    return p.parse_args()


def main() -> None:
    args = parse_args()
    if args.end_frame < args.start_frame:
        raise RuntimeError("end-frame must be >= start-frame")
    if not (args.x2 > args.x1 and args.y2 > args.y1):
        raise RuntimeError("ROI must satisfy x2>x1 and y2>y1")

    in_path = Path(args.input)
    roi_path = Path(args.roi_video)
    out_path = Path(args.output)
    report_path = Path(args.report_json) if args.report_json else None
    out_path.parent.mkdir(parents=True, exist_ok=True)
    if report_path is not None:
        report_path.parent.mkdir(parents=True, exist_ok=True)

    span = args.end_frame - args.start_frame + 1
    roi_w = args.x2 - args.x1
    roi_h = args.y2 - args.y1

    cap_roi = cv2.VideoCapture(str(roi_path))
    if not cap_roi.isOpened():
        raise RuntimeError(f"Cannot open ROI video: {roi_path}")
    roi_frames: list = []
    while True:
        ok, frame = cap_roi.read()
        if not ok:
            break
        roi_frames.append(frame)
    cap_roi.release()
    if len(roi_frames) != span:
        raise RuntimeError(f"Expected ROI frames={span}, got={len(roi_frames)}")

    cap = cv2.VideoCapture(str(in_path))
    if not cap.isOpened():
        raise RuntimeError(f"Cannot open input: {in_path}")
    fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    if args.x1 < 0 or args.y1 < 0 or args.x2 > width or args.y2 > height:
        raise RuntimeError(f"ROI out of bounds for input resolution {width}x{height}")

    fourcc = cv2.VideoWriter_fourcc(*("FFV1" if out_path.suffix.lower() in {".mkv", ".avi"} else "mp4v"))
    writer = cv2.VideoWriter(str(out_path), fourcc, fps, (width, height))
    if not writer.isOpened():
        raise RuntimeError(f"Cannot open output writer: {out_path}")

    frame_idx = 0
    replaced = 0
    while True:
        ok, frame = cap.read()
        if not ok:
            break
        if args.start_frame <= frame_idx <= args.end_frame:
            roi = roi_frames[frame_idx - args.start_frame]
            if roi.shape[1] != roi_w or roi.shape[0] != roi_h:
                roi = cv2.resize(roi, (roi_w, roi_h), interpolation=cv2.INTER_LINEAR)
            frame[args.y1 : args.y2, args.x1 : args.x2] = roi
            replaced += 1
        writer.write(frame)
        frame_idx += 1

    cap.release()
    writer.release()

    report = {
        "input": str(in_path),
        "roi_video": str(roi_path),
        "output": str(out_path),
        "start_frame": args.start_frame,
        "end_frame": args.end_frame,
        "replaced_frames": replaced,
        "video_frames_written": frame_idx,
        "roi": {
            "x1": args.x1,
            "y1": args.y1,
            "x2": args.x2,
            "y2": args.y2,
            "width": roi_w,
            "height": roi_h,
        },
    }
    if report_path is not None:
        report_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
    print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
