#!/usr/bin/env python
"""Run Mage-VL-Base image or video inference offline or through SGLang."""

import argparse
import base64
import mimetypes
from pathlib import Path


def image_url(image: str) -> str:
    if image.startswith(("http://", "https://", "data:")):
        return image
    path = Path(image)
    mime = mimetypes.guess_type(path.name)[0] or "image/jpeg"
    encoded = base64.b64encode(path.read_bytes()).decode("ascii")
    return f"data:{mime};base64,{encoded}"


def sample_video(video: str, num_frames: int):
    import cv2
    import numpy as np
    from PIL import Image

    capture = cv2.VideoCapture(video)
    frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
    if frame_count <= 0:
        capture.release()
        raise ValueError(f"Could not read video: {video}")
    indices = np.linspace(0, frame_count - 1, min(num_frames, frame_count), dtype=int)
    frames = []
    for index in indices:
        capture.set(cv2.CAP_PROP_POS_FRAMES, int(index))
        ok, frame = capture.read()
        if not ok:
            capture.release()
            raise ValueError(f"Could not decode frame {index} from: {video}")
        frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
    capture.release()
    return frames


def run_offline(args):
    import os
    import torch
    from PIL import Image
    from transformers import AutoModelForCausalLM, AutoProcessor

    model_path = args.model
    if args.video and args.video_backend == "codec" and args.codec_engine == "neural":
        if not os.path.isdir(model_path):
            from huggingface_hub import snapshot_download

            model_path = snapshot_download(args.model)
    processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(
        model_path, trust_remote_code=True, torch_dtype="auto", device_map="auto"
    ).eval()
    media_type = "image" if args.image else "video"
    messages = [{"role": "user", "content": [
        {"type": media_type}, {"type": "text", "text": args.question},
    ]}]
    text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    if args.image:
        inputs = processor(
            text=[text], images=[Image.open(args.image).convert("RGB")], return_tensors="pt"
        )
    elif args.video_backend == "codec":
        codec_config = {
            "engine": "hevc" if args.codec_engine == "traditional" else "dcvc-rt",
            "target_canvas": args.num_frames,
            "patch": 16,
        }
        if args.codec_engine == "neural":
            codec_config["dcvc"] = {
                "pkg_dir": os.path.join(model_path, "neural_codec"),
                "device": str(model.device),
            }
        inputs = processor(
            text=[text],
            videos=[args.video],
            video_backend="codec",
            max_pixels=args.max_pixels,
            codec_config=codec_config,
            return_tensors="pt",
            padding=True,
        )
    else:
        inputs = processor(
            text=[text],
            videos=[sample_video(args.video, args.num_frames)],
            return_tensors="pt",
            padding=True,
        )
    inputs = {k: (v.to(model.device) if hasattr(v, "to") else v) for k, v in inputs.items()}
    if "pixel_values" in inputs:
        inputs["pixel_values"] = inputs["pixel_values"].to(model.dtype)
    with torch.inference_mode():
        output = model.generate(**inputs, max_new_tokens=args.max_new_tokens, do_sample=False)
    answer = processor.tokenizer.decode(
        output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True
    )
    print(answer.strip())


def run_online(args):
    from openai import OpenAI

    if args.image:
        content = [{"type": "image_url", "image_url": {"url": image_url(args.image)}}]
    else:
        content = [
            {"type": "image_url", "image_url": {"url": frame_url(frame)}}
            for frame in sample_video(args.video, args.num_frames)
        ]
    content.append({"type": "text", "text": args.question})
    client = OpenAI(base_url=args.base_url, api_key=args.api_key)
    response = client.chat.completions.create(
        model=args.model,
        messages=[{"role": "user", "content": content}],
        max_tokens=args.max_new_tokens,
    )
    print(response.choices[0].message.content)


def frame_url(frame) -> str:
    import io

    buffer = io.BytesIO()
    frame.save(buffer, format="JPEG")
    encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
    return f"data:image/jpeg;base64,{encoded}"


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--mode", choices=("offline", "online"), required=True)
    media = parser.add_mutually_exclusive_group(required=True)
    media.add_argument("--image", help="Local image path")
    media.add_argument("--video", help="Local video path")
    parser.add_argument("--video-backend", choices=("frames", "codec"), default="frames")
    parser.add_argument(
        "--codec-engine", choices=("traditional", "neural"), default="traditional"
    )
    parser.add_argument("--num-frames", type=int, default=32)
    parser.add_argument("--max-pixels", type=int, default=150000)
    parser.add_argument("--question", default="Describe this media.")
    parser.add_argument("--model", default="microsoft/Mage-VL")
    parser.add_argument("--max-new-tokens", type=int, default=256)
    parser.add_argument("--base-url", default="http://localhost:30000/v1")
    parser.add_argument("--api-key", default="EMPTY")
    args = parser.parse_args()
    if args.mode == "online" and args.video_backend == "codec":
        parser.error("online video inference supports only --video-backend frames")
    if args.num_frames <= 0:
        parser.error("--num-frames must be positive")
    (run_offline if args.mode == "offline" else run_online)(args)


if __name__ == "__main__":
    main()
