#!/usr/bin/env python3
"""
视频上传工具 - 将本地 MP4 文件通过 multipart/form-data 上传到 ab-api，
获取可用 URL（直传阿里云 OSS，同步返回）。

默认行为:
  读取本地文件，以 multipart/form-data 形式 POST 到
  `<MM_API_BASE_URL>/file/uploadMediaFile`，解析响应 JSON，输出 fileUrl。

用法:
  python3 upload_video.py --file output/video.mp4
  python3 upload_video.py --file output/video.mp4 --title "我的视频"
  python3 upload_video.py --file output/video.mp4 --json-output

环境变量:
  MM_API_BASE_URL - API 根地址（默认 MM_API_BASE_URL 环境变量或 https://api-agent.remixmate.ai/api）

注意:
  - `uploadMediaFile` 接口视频大小限制 ≤ 50MB，音频 ≤ 15MB
  - 进度信息输出到 stderr，最终 URL 输出到 stdout（最后一行）
"""

import argparse
import http.client
import json
import mimetypes
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
import uuid

DEFAULT_API_BASE_URL = "https://api-agent.remixmate.ai/api"


def _guess_content_type(file_path: str) -> str:
    ctype, _ = mimetypes.guess_type(file_path)
    return ctype or "application/octet-stream"


def _build_multipart_body(file_path: str, title: str = None) -> (bytes, str):
    """
    手动构建 multipart/form-data body。

    返回 (body_bytes, content_type_header)。
    """
    boundary = "----MediaMakerBoundary" + uuid.uuid4().hex
    crlf = b"\r\n"
    parts = []

    # title 字段（可选）
    if title:
        parts.append(("--" + boundary).encode("utf-8"))
        parts.append(b'Content-Disposition: form-data; name="title"')
        parts.append(b"")
        parts.append(title.encode("utf-8"))

    # file 字段
    file_name = os.path.basename(file_path)
    content_type = _guess_content_type(file_path)
    with open(file_path, "rb") as f:
        file_data = f.read()

    parts.append(("--" + boundary).encode("utf-8"))
    parts.append(
        (
            'Content-Disposition: form-data; name="file"; filename="%s"'
            % file_name
        ).encode("utf-8")
    )
    parts.append(("Content-Type: %s" % content_type).encode("utf-8"))
    parts.append(b"")
    parts.append(file_data)

    # 结尾 boundary
    parts.append(("--" + boundary + "--").encode("utf-8"))
    parts.append(b"")

    body = crlf.join(parts)
    ct_header = "multipart/form-data; boundary=%s" % boundary
    return body, ct_header


def upload_media_file(
    file_path: str,
    api_base_url: str = None,
    title: str = None,
    timeout: int = 600,
) -> dict:
    """
    将本地媒体文件以 multipart/form-data 形式上传到 ab-api。

    返回服务端 data 字段，通常形如 {"fileUrl": "https://..."}。
    上传失败会抛出异常。
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"file not found: {file_path}")

    api_base_url = (api_base_url or os.environ.get("MM_API_BASE_URL") or DEFAULT_API_BASE_URL).rstrip("/")
    url = f"{api_base_url}/file/uploadMediaFile"

    file_size = os.path.getsize(file_path)
    print(f"📤 Uploading: {file_path} ({file_size / 1024 / 1024:.2f} MB)", file=sys.stderr)
    print(f"   target: {url}", file=sys.stderr)

    body, content_type = _build_multipart_body(file_path, title=title)
    headers = {
        "Content-Type": content_type,
        "Content-Length": str(len(body)),
    }

    # Use http.client directly to avoid urllib RemoteDisconnected issues
    # with servers that return 'connection: close'
    parsed = urllib.parse.urlparse(url)
    host = parsed.hostname
    port = parsed.port
    path = parsed.path

    if parsed.scheme == "https":
        import ssl
        conn = http.client.HTTPSConnection(host, port, timeout=timeout,
                                           context=ssl.create_default_context())
    else:
        conn = http.client.HTTPConnection(host, port, timeout=timeout)

    try:
        conn.request("POST", path, body=body, headers=headers)
        resp = conn.getresponse()
        resp_body = resp.read().decode("utf-8")

        if resp.status >= 400:
            raise RuntimeError(f"upload failed (HTTP {resp.status}): {resp_body}")
    except http.client.RemoteDisconnected as e:
        raise RuntimeError(f"server disconnected: {e}") from e
    except (OSError, Exception) as e:
        if isinstance(e, RuntimeError):
            raise
        raise RuntimeError(f"network error: {e}") from e
    finally:
        conn.close()

    try:
        result = json.loads(resp_body)
    except json.JSONDecodeError as e:
        raise RuntimeError(f"failed to parse response: {resp_body}") from e

    # ab-api shared response: { code: 0, data: {...}, msg: "..." }
    if isinstance(result, dict) and result.get("code") not in (0, 200):
        raise RuntimeError(f"server returned an error: code={result.get('code')}, msg={result.get('msg')}")

    data = result.get("data") if isinstance(result, dict) else None
    if not isinstance(data, dict) or not data.get("fileUrl"):
        raise RuntimeError(f"response is missing fileUrl: {resp_body}")

    print(f"✅ uploaded: {data['fileUrl']}", file=sys.stderr)
    return data


def main():
    parser = argparse.ArgumentParser(description="Upload a media file to ab-api / Alibaba Cloud OSS")
    parser.add_argument("--file", required=True, help="Local file path")
    parser.add_argument("--title", default=None, help="File title (optional, defaults to filename)")
    parser.add_argument(
        "--api-base-url",
        default=None,
        help="API root URL (defaults to MM_API_BASE_URL env var or https://api-agent.remixmate.ai/api)",
    )
    parser.add_argument("--timeout", type=int, default=600, help="Upload timeout in seconds (default 600)")
    parser.add_argument("--json-output", action="store_true", help="Emit the full response as JSON on stdout")

    args = parser.parse_args()

    try:
        data = upload_media_file(
            file_path=args.file,
            api_base_url=args.api_base_url,
            title=args.title,
            timeout=args.timeout,
        )
    except Exception as e:
        print(f"❌ {e}", file=sys.stderr)
        sys.exit(1)

    if args.json_output:
        print(json.dumps(data, ensure_ascii=False))
    else:
        # 最终 URL 输出到 stdout（最后一行），方便其他脚本捕获
        print(data["fileUrl"])


if __name__ == "__main__":
    main()
