#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
MiniMax Text-to-Image — synchronous generation with optional character reference.

Usage:
  python3 minimax_image.py "A cat in space" -o cat.png
  python3 minimax_image.py "Mountain landscape" -o bg.png --ratio 16:9
  python3 minimax_image.py "Funko Pop figurine waving" -o sticker.png --subject-ref photo.jpg

Env: MINIMAX_API_KEY (required)
"""

import os
import sys
import json
import base64
import argparse
import requests

API_KEY = os.getenv("MINIMAX_API_KEY")
# China Mainland: https://api.minimaxi.com/v1
# Overseas:       https://api.minimax.io/v1
API_BASE = os.getenv("MINIMAX_API_BASE")
if not API_BASE:
    raise SystemExit("ERROR: MINIMAX_API_BASE is not set.")

ASPECT_RATIOS = ["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]


def _headers():
    if not API_KEY:
        raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n  export MINIMAX_API_KEY='your-key'")
    return {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }


def _encode_image(image_path: str) -> str:
    """Read local image file and return base64 data URI."""
    ext = os.path.splitext(image_path)[1].lower().lstrip(".")
    mime_map = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png", "webp": "webp"}
    mime = mime_map.get(ext, "jpeg")
    with open(image_path, "rb") as f:
        raw = f.read()
    return f"data:image/{mime};base64,{base64.b64encode(raw).decode()}"


def generate_image(
    prompt: str,
    model: str = "image-01",
    aspect_ratio: str = "1:1",
    n: int = 1,
    response_format: str = "url",
    prompt_optimizer: bool = False,
    seed: int = None,
    subject_reference: list = None,
) -> dict:
    """Generate image(s). Returns API response dict."""
    payload = {
        "model": model,
        "prompt": prompt,
        "aspect_ratio": aspect_ratio,
        "n": n,
        "response_format": response_format,
        "prompt_optimizer": prompt_optimizer,
    }
    if seed is not None:
        payload["seed"] = seed
    if subject_reference:
        payload["subject_reference"] = subject_reference

    resp = requests.post(
        f"{API_BASE}/image_generation",
        headers=_headers(),
        json=payload,
        timeout=120,
    )
    resp.raise_for_status()
    data = resp.json()

    base_resp = data.get("base_resp", {})
    if base_resp.get("status_code", 0) != 0:
        raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}")

    return data


def download_and_save(url: str, output_path: str):
    """Download image from URL and save."""
    resp = requests.get(url, timeout=60)
    resp.raise_for_status()
    with open(output_path, "wb") as f:
        f.write(resp.content)
    return len(resp.content)


def main():
    p = argparse.ArgumentParser(description="MiniMax Text-to-Image")
    p.add_argument("prompt", help="Image description (max 1500 chars)")
    p.add_argument("-o", "--output", required=True, help="Output file path (.png/.jpg)")
    p.add_argument("--model", default="image-01", help="Model (default: image-01)")
    p.add_argument("--ratio", default="1:1", choices=ASPECT_RATIOS, help="Aspect ratio (default: 1:1)")
    p.add_argument("-n", "--count", type=int, default=1, choices=range(1, 10), help="Number of images (1-9, default: 1)")
    p.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
    p.add_argument("--optimize", action="store_true", help="Enable prompt auto-optimization")
    p.add_argument("--base64", action="store_true", help="Use base64 response instead of URL")
    p.add_argument("--subject-ref", default=None,
                   help="Reference image for character likeness (local path or URL, person only)")
    p.add_argument("--subject-type", default="character",
                   help="Subject reference type (default: character)")
    args = p.parse_args()

    os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)

    subject_ref = None
    if args.subject_ref:
        ref_value = args.subject_ref
        if not ref_value.startswith(("http://", "https://", "data:")):
            ref_value = _encode_image(ref_value)
        subject_ref = [{"type": args.subject_type, "image_file": ref_value}]

    fmt = "base64" if args.base64 else "url"
    result = generate_image(
        prompt=args.prompt,
        model=args.model,
        aspect_ratio=args.ratio,
        n=args.count,
        response_format=fmt,
        prompt_optimizer=args.optimize,
        seed=args.seed,
        subject_reference=subject_ref,
    )

    meta = result.get("metadata", {})
    print(f"Generated: {meta.get('success_count', '?')} success, {meta.get('failed_count', '?')} failed")

    if args.base64:
        images = result.get("data", {}).get("image_base64", [])
        for i, b64 in enumerate(images):
            path = args.output if len(images) == 1 else _numbered_path(args.output, i)
            raw = base64.b64decode(b64)
            with open(path, "wb") as f:
                f.write(raw)
            print(f"OK: {len(raw)} bytes -> {path}")
    else:
        urls = result.get("data", {}).get("image_urls", [])
        for i, url in enumerate(urls):
            path = args.output if len(urls) == 1 else _numbered_path(args.output, i)
            size = download_and_save(url, path)
            print(f"OK: {size} bytes -> {path}")


def _numbered_path(path: str, index: int) -> str:
    """Insert index before extension: out.png -> out-0.png"""
    base, ext = os.path.splitext(path)
    return f"{base}-{index}{ext}"


if __name__ == "__main__":
    main()
