#!/usr/bin/env python3
"""Smoke-test EAPIL/OpenAI-compatible structured output behavior."""

from __future__ import annotations

import argparse
import base64
import json
import mimetypes
import os
import sys
import time
from pathlib import Path
from typing import Any
from urllib import request


SCHEMA: dict[str, Any] = {
    "type": "object",
    "additionalProperties": False,
    "properties": {
        "ok": {"type": "boolean"},
        "label": {"type": "string", "maxLength": 64},
        "score": {"type": "number", "minimum": 0, "maximum": 1},
    },
    "required": ["ok", "label", "score"],
}


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


def endpoint_url(base_url: str, endpoint: str) -> str:
    stripped = base_url.rstrip("/")
    if stripped.endswith("/v1"):
        return f"{stripped}/{endpoint}"
    return f"{stripped}/v1/{endpoint}"


def responses_payload(args: argparse.Namespace) -> dict[str, Any]:
    content: list[dict[str, Any]] = [
        {
            "type": "input_text",
            "text": (
                "只输出一个 JSON object，不要 markdown。"
                "判断输入是否可用于结构化输出 smoke test。"
            ),
        }
    ]
    for image in args.image:
        content.append(
            {
                "type": "input_image",
                "image_url": data_url(Path(image)),
                "detail": args.image_detail,
            }
        )
    payload: dict[str, Any] = {
        "model": args.model,
        "input": [{"role": "user", "content": content}],
        "text": {
            "format": {
                "type": "json_schema",
                "name": "eapil_llmapi_structured_smoke",
                "strict": True,
                "schema": SCHEMA,
            }
        },
        "truncation": "disabled",
        "max_output_tokens": args.max_output_tokens,
    }
    if args.reasoning_effort not in {"auto", "default", "omit"}:
        payload["reasoning"] = {"effort": args.reasoning_effort}
    return payload


def chat_payload(args: argparse.Namespace) -> dict[str, Any]:
    content: list[dict[str, Any]] = [
        {
            "type": "text",
            "text": (
                "只输出一个 JSON object，不要 markdown。"
                "判断输入是否可用于结构化输出 smoke test。"
            ),
        }
    ]
    for image in args.image:
        content.append(
            {
                "type": "image_url",
                "image_url": {"url": data_url(Path(image)), "detail": args.image_detail},
            }
        )
    payload: dict[str, Any] = {
        "model": args.model,
        "messages": [{"role": "user", "content": content}],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "eapil_llmapi_structured_smoke",
                "strict": True,
                "schema": SCHEMA,
            },
        },
        "temperature": 0,
        "max_tokens": args.max_output_tokens,
    }
    if args.reasoning_effort not in {"auto", "default", "omit"}:
        payload["reasoning_effort"] = args.reasoning_effort
    return payload


def extract_responses_text(response: dict[str, Any]) -> str:
    if isinstance(response.get("output_text"), str):
        return str(response["output_text"])
    texts: list[str] = []
    for item in response.get("output") or []:
        if not isinstance(item, dict):
            continue
        for content in item.get("content") or []:
            if isinstance(content, dict) and isinstance(content.get("text"), str):
                texts.append(str(content["text"]))
    return "\n".join(texts) if texts else json.dumps(response, ensure_ascii=False)


def extract_chat_text(response: dict[str, Any]) -> str:
    choices = response.get("choices")
    if isinstance(choices, list) and choices:
        message = choices[0].get("message") if isinstance(choices[0], dict) else None
        content = message.get("content") if isinstance(message, dict) else None
        if isinstance(content, str):
            return content
    return json.dumps(response, ensure_ascii=False)


def post_json(url: str, payload: dict[str, Any], api_key: str, timeout_s: float) -> dict[str, Any]:
    body = json.dumps(payload).encode("utf-8")
    req = request.Request(
        url,
        data=body,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with request.urlopen(req, timeout=timeout_s) as response:  # noqa: S310
        return json.loads(response.read().decode("utf-8"))


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("--base-url", default="https://api-n-cd.playones.com/v1")
    parser.add_argument("--model", default="gpt-5.5")
    parser.add_argument("--endpoint", choices=["responses", "chat"], default="responses")
    parser.add_argument("--reasoning-effort", default="none")
    parser.add_argument("--max-output-tokens", type=int, default=300)
    parser.add_argument("--image", action="append", default=[])
    parser.add_argument("--image-detail", default="low")
    parser.add_argument("--api-key-env", default="OPENAI_API_KEY")
    parser.add_argument("--timeout-s", type=float, default=240.0)
    parser.add_argument("--dry-run", action="store_true")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    payload = responses_payload(args) if args.endpoint == "responses" else chat_payload(args)
    path = "responses" if args.endpoint == "responses" else "chat/completions"
    url = endpoint_url(args.base_url, path)
    if args.dry_run:
        print(json.dumps({"url": url, "payload": payload}, ensure_ascii=False, indent=2))
        return 0
    api_key = os.getenv(args.api_key_env)
    if not api_key:
        print(f"Missing API key env: {args.api_key_env}", file=sys.stderr)
        return 2
    started = time.perf_counter()
    response = post_json(url, payload, api_key, args.timeout_s)
    elapsed = time.perf_counter() - started
    raw_text = (
        extract_responses_text(response)
        if args.endpoint == "responses"
        else extract_chat_text(response)
    )
    try:
        parsed = json.loads(raw_text)
        parse_ok = isinstance(parsed, dict)
    except json.JSONDecodeError:
        parse_ok = False
    output_types = [
        item.get("type")
        for item in response.get("output", [])
        if isinstance(item, dict)
    ]
    print(
        json.dumps(
            {
                "endpoint": args.endpoint,
                "elapsed_s": round(elapsed, 3),
                "usage": response.get("usage"),
                "raw_text_chars": len(raw_text),
                "raw_text_preview": raw_text[:240],
                "parse_ok": parse_ok,
                "has_encrypted_reasoning": "encrypted_content" in json.dumps(
                    response,
                    ensure_ascii=False,
                ),
                "output_item_types": output_types,
            },
            ensure_ascii=False,
            indent=2,
        )
    )
    return 0 if parse_ok else 1


if __name__ == "__main__":
    raise SystemExit(main())
