#!/usr/bin/env python3
"""
统一资源生成调度器
用法:
  python3 generate.py image "prompt" --output-dir /abs/path [--aspect-ratio 16:9] [--output path]
  python3 generate.py music "prompt" --output-dir /abs/path [--lyrics "歌词"] [--instrumental] [--output path]
"""
import argparse
import json
import os
import sys
import tempfile
import time
from datetime import datetime

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_PATH = os.path.join(SCRIPT_DIR, "..", "config.json")


def load_config():
    with open(CONFIG_PATH, "r", encoding="utf-8") as f:
        return json.load(f)


def ensure_output_dir(path):
    """Ensure output directory exists and is writable. Fallback to /tmp/media/ if not."""
    try:
        os.makedirs(path, exist_ok=True)
        return path
    except OSError:
        fallback = os.path.join(tempfile.gettempdir(), "media")
        os.makedirs(fallback, exist_ok=True)
        print(f"[warn] {path} not writable, using {fallback}", file=sys.stderr)
        return fallback


def ratio_to_size(ratio_str, max_dim=2048):
    """Parse '16:9' style ratio and compute (width, height) capped at max_dim."""
    if not ratio_str or ratio_str == "1:1":
        return 1024, 1024

    parts = ratio_str.split(":")
    if len(parts) != 2:
        return 1024, 1024

    try:
        rw, rh = float(parts[0]), float(parts[1])
    except ValueError:
        return 1024, 1024

    # Scale so the longer side = max_dim
    scale = max_dim / max(rw, rh)
    w = round(rw * scale)
    h = round(rh * scale)
    return w, h


def make_output_path(out_dir, name, resource_type, params, ext):
    """Generate filename with Chinese name and key parameters.

    Format: {name}_{params}_{timestamp}.{ext}
    Example: 日落风景_2048x1152_20260414_123456.png
    """
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")

    # Extract key parameters for filename
    param_parts = []
    if resource_type == "image":
        aspect_ratio = params.get("aspect_ratio", "1:1")
        w, h = ratio_to_size(aspect_ratio)
        param_parts.append(f"{w}x{h}")
    elif resource_type == "music":
        if params.get("instrumental"):
            param_parts.append("instrumental")

    parts = [name]
    if param_parts:
        parts.extend(param_parts)
    parts.append(ts)

    filename = "_".join(parts) + f".{ext}"
    return os.path.join(out_dir, filename)


def upsert_log(log_path, entry):
    """Insert or update a log entry by id. Never raises — logging failure is non-fatal."""
    try:
        os.makedirs(os.path.dirname(log_path), exist_ok=True)
        if os.path.exists(log_path):
            with open(log_path, "r", encoding="utf-8") as f:
                log = json.load(f)
        else:
            log = []

        for i, e in enumerate(log):
            if e.get("id") == entry["id"]:
                log[i] = entry
                break
        else:
            log.append(entry)

        with open(log_path, "w", encoding="utf-8") as f:
            json.dump(log, f, ensure_ascii=False, indent=2)
    except Exception as e:
        print(f"[warn] log write failed: {e}", file=sys.stderr)


def dispatch(resource_type, prompt, **kwargs):
    """Route to providers sequentially as a priority array.

    Always tries the first provider, then falls back to the next on failure.
    Returns (files, used_provider_cfg).
    """
    cfg = load_config()
    resource_cfg = cfg.get(resource_type)
    if not resource_cfg:
        raise ValueError(f"unknown resource type: {resource_type}")

    # 支持旧版 dict(primary/fallback) 过渡到新版 list 
    if isinstance(resource_cfg, dict):
        providers = [resource_cfg["primary"]]
        if resource_cfg.get("fallback"):
            providers.append(resource_cfg["fallback"])
    elif isinstance(resource_cfg, list):
        providers = resource_cfg
    else:
        raise ValueError(f"invalid config structure for {resource_type}")

    sys.path.insert(0, SCRIPT_DIR)
    from providers import get_adapter

    errors = []
    for provider_cfg in providers:
        name = provider_cfg["provider"]
        try:
            adapter = get_adapter(name)
            files = adapter.generate(prompt, config=provider_cfg, **kwargs)
            return files, provider_cfg
        except Exception as e:
            msg = f"[{name}] failed: {e}"
            print(msg, file=sys.stderr)
            errors.append(msg)

    raise RuntimeError("all providers failed:\n" + "\n".join(errors))


def main():
    parser = argparse.ArgumentParser(description="Unified media generation dispatcher")
    parser.add_argument("type", choices=["image", "music", "video", "i2v"], help="Resource type")
    parser.add_argument("prompt", nargs="?", default="", help="Generation prompt")
    parser.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio (image: 1:1; video: 16:9/9:16/4:3/1:1)")
    parser.add_argument("--lyrics", default=None, help="Lyrics text (music only)")
    parser.add_argument("--instrumental", action="store_true", help="Instrumental mode (music only)")
    parser.add_argument("--duration", type=int, default=None, help="Video duration in seconds (video/i2v, default 6)")
    parser.add_argument("--resolution", default=None, help="Video resolution: 720P/768P/1080P (video/i2v)")
    parser.add_argument("--first-frame-image", default=None, help="First frame image URL (i2v only, required)")
    parser.add_argument("--output", default=None, help="Output file path")
    parser.add_argument("--output-dir", required=True, help="Absolute path to output directory (required)")
    parser.add_argument("--name", required=True, help="Resource name in Chinese (required, e.g. 日落风景)")
    args = parser.parse_args()

    # i2v 必须有 --first-frame-image
    if args.type == "i2v" and not args.first_frame_image:
        parser.error("i2v (图生视频) 模式必须提供 --first-frame-image 参数")

    out_dir = ensure_output_dir(args.output_dir)

    # Prepare kwargs
    if args.type == "image":
        kwargs = {
            "out_dir": out_dir,
            "aspect_ratio": args.aspect_ratio,
        }
        ext = "png"
    elif args.type == "video":
        # 文生视频 — 纯文本，无图片
        kwargs = {
            "out_dir": out_dir,
            "aspect_ratio": args.aspect_ratio,
        }
        if args.duration:
            kwargs["duration"] = args.duration
        if args.resolution:
            kwargs["resolution"] = args.resolution
        ext = "mp4"
    elif args.type == "i2v":
        # 图生视频 — 必须有首帧图片
        kwargs = {
            "out_dir": out_dir,
            "first_frame_image": args.first_frame_image,
        }
        if args.duration:
            kwargs["duration"] = args.duration
        if args.resolution:
            kwargs["resolution"] = args.resolution
        ext = "mp4"
    else:
        kwargs = {
            "out_dir": out_dir,
            "lyrics": args.lyrics,
            "instrumental": args.instrumental,
        }
        ext = "mp3"

    start_time = time.time()
    start_dt = datetime.now()
    log_path = os.path.join(out_dir, ".myclaw", "media-generation-log.json")
    log_path_global = os.path.expanduser("~/.openclaw/myclaw/log/media-generation-log.json")
    entry_id = start_dt.strftime("%Y%m%d_%H%M%S")

    # Record request immediately
    cfg = load_config()
    provider_queue = [
        {"provider": p["provider"], "model": p["model"]}
        for p in (cfg.get(args.type) or [])
        if isinstance(p, dict)
    ]
    log_entry = {
        "id": entry_id,
        "status": "pending",
        "type": args.type,
        "prompt": args.prompt,
        "params": {k: v for k, v in kwargs.items() if k != "out_dir"},
        "providers_queue": provider_queue,
        "started_at": start_dt.strftime("%Y-%m-%d %H:%M:%S"),
    }
    upsert_log(log_path, log_entry)
    upsert_log(log_path_global, log_entry)

    try:
        # Handle output path: user-specified or auto-named with model info
        if args.output:
            # User specified output path - use it directly
            kwargs["output_path"] = args.output
        else:
            # Auto-naming: generate to temp path first, then rename with model info
            import tempfile
            with tempfile.NamedTemporaryFile(delete=False, suffix=f".{ext}", dir=out_dir) as tmp:
                tmp_path = tmp.name
            kwargs["output_path"] = tmp_path

        files, used_provider = dispatch(args.type, args.prompt, **kwargs)

        # Rename with proper filename containing name and params (only for auto-naming)
        if not args.output:
            proper_path = make_output_path(out_dir, args.name, args.type, kwargs, ext)
            import shutil
            shutil.move(files[0], proper_path)
            files = [proper_path]

        end_dt = datetime.now()
        log_entry.update({
            "status": "success",
            "name": os.path.basename(files[0]) if files else None,
            "files": files,
            "provider": used_provider["provider"],
            "model": used_provider["model"],
            "finished_at": end_dt.strftime("%Y-%m-%d %H:%M:%S"),
            "duration_seconds": round(time.time() - start_time, 2),
        })
        upsert_log(log_path, log_entry)
        upsert_log(log_path_global, log_entry)

        print(json.dumps({"success": True, "files": files}))
    except Exception as e:
        end_dt = datetime.now()
        log_entry.update({
            "status": "failed",
            "error": str(e),
            "finished_at": end_dt.strftime("%Y-%m-%d %H:%M:%S"),
            "duration_seconds": round(time.time() - start_time, 2),
        })
        upsert_log(log_path, log_entry)
        upsert_log(log_path_global, log_entry)

        print(json.dumps({"success": False, "error": str(e)}))
        sys.exit(1)


if __name__ == "__main__":
    main()
