"""开放API时间线示例：环境变量供Key，业务记录固定幂等ID，音频先保存，再查询并导出时间线。

安装requests后设置HEYIN_API_KEY及HEYIN_SHARED_VOICE_ID运行；默认保存到timeline-demo。
同目录重跑复用原业务参数和任务；新业务换HEYIN_OUTPUT_DIR。需要已开放时间线的Key。
"""
import json
import os
import time
import uuid
from pathlib import Path
from urllib.parse import urljoin, urlsplit

import requests

BASE = os.getenv("HEYIN_API_BASE", "https://www.heyintts.cn/api/open").rstrip("/")


def endpoint(path):
    """只向配置站点发送密钥，服务端返回的下载路径也必须同源。"""
    url = urljoin(BASE+"/", path) if path.startswith(("/api/", "http://", "https://")) else BASE+"/"+path.lstrip("/")
    source, target = urlsplit(BASE), urlsplit(url)
    if (source.scheme, source.netloc) != (target.scheme, target.netloc) or target.scheme != "https":
        raise ValueError("接口和下载地址必须使用配置站点的HTTPS同源地址")
    return url


def fetch(session, method, path, **kwargs):
    """仅对稳定业务ID的请求执行有限网络重试，永久错误立即交给调用方。"""
    for attempt in range(3):
        try:
            response = session.request(method, endpoint(path), timeout=180, allow_redirects=False, **kwargs)
        except (requests.ConnectionError, requests.Timeout):
            if attempt == 2:
                raise
            time.sleep(3)
            continue
        if response.status_code in (429, 502, 503) and attempt < 2:
            try:
                delay = min(60, max(1, int(response.headers.get("Retry-After", "3"))))
            except ValueError:
                delay = 3
            time.sleep(delay)
            continue
        response.raise_for_status()
        if 300 <= response.status_code < 400:
            raise RuntimeError("下载出现重定向，请核对站点地址；示例不会转发密钥到重定向目标")
        return response
    raise RuntimeError("请求未完成；稍后在同一目录重跑")


def save_json(path, value):
    """原子更新业务记录，客户端中断后仍能复用相同请求。"""
    temporary = path.with_suffix(".part")
    temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")
    temporary.replace(path)


def wait(session, path, deadline, field, success):
    """轮询音频与时间线分别处理，不能把音频成功误认为字幕完成。"""
    while time.monotonic() < deadline:
        value = fetch(session, "GET", path).json()
        state = value[field]
        if state == success:
            return value
        if state in ("failed", "partial", "unavailable", "cancelled"):
            raise RuntimeError("任务状态为"+state+"，请核对状态详情；已有音频保持在输出目录")
        time.sleep(max(2, min(10, value.get("timeline_poll_after_ms", 3000)/1000)))
    raise TimeoutError("等待超时；保留业务记录，稍后在同一目录重跑")


def main():
    """提交异步任务，先下载原音频，再下载四种字幕和两份内嵌副本。"""
    directory = Path(os.getenv("HEYIN_OUTPUT_DIR", "timeline-demo"))
    directory.mkdir(parents=True, exist_ok=True)
    business_file = directory / "business.json"
    if business_file.exists():
        business = json.loads(business_file.read_text(encoding="utf-8"))
        if business["base"] != BASE:
            raise ValueError("业务记录的站点与当前配置不同，新业务请使用新目录")
    else:
        business = {"base": BASE, "payload": {"shared_voice_id": int(os.environ["HEYIN_SHARED_VOICE_ID"]),
            "text": "你好，欢迎使用时间线。", "request_id": uuid.uuid4().hex, "output_format": "mp3",
            "timeline_mode": "auto"}}
        save_json(business_file, business)
    with requests.Session() as session:
        session.headers["X-API-Key"] = os.environ["HEYIN_API_KEY"]
        if not business.get("job_id"):
            job = fetch(session, "POST", "/library/jobs", json=business["payload"]).json()
            business["job_id"] = job["job_id"]
            save_json(business_file, business)
        deadline = time.monotonic()+3600
        job = wait(session, "/jobs/"+business["job_id"], deadline, "status", "succeeded")
        result = job["result"]
        (directory / "audio.mp3").write_bytes(fetch(session, "GET", result["audio_url"]).content)
        timeline = wait(session, result["timeline_url"], deadline, "timeline_status", "ready")
        save_json(directory / "timeline-status.json", timeline)
        base = "/history/"+result["history_id"]
        exports = [("/subtitles?format="+fmt, "timeline."+fmt) for fmt in ("json", "srt", "vtt", "ass")]
        exports += [("/audio-with-timeline?format=auto", "embedded.mp3"), ("/audio-with-timeline?format=wav", "embedded.wav")]
        for suffix, name in exports:
            temporary = directory / (name+".part")
            temporary.write_bytes(fetch(session, "GET", base+suffix).content)
            temporary.replace(directory / name)
    print("已保存原音频、四种字幕和两份内嵌副本：", directory.resolve())


if __name__ == "__main__":
    main()
