#!/usr/bin/env python3
"""分弦端到端验收：两轨、完整分轨、下载与输入拒绝。"""
import json
import os
import sys
import time
import urllib.error
import urllib.request
import uuid

BASE = "http://127.0.0.1:8642"
SONG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "test-song.wav")
FAIL = []


def check(desc, actual, expect):
    if actual == expect:
        print(f"  ✓ {desc}")
    else:
        print(f"  ✗ {desc}（期望 {expect}，实得 {actual}）")
        FAIL.append(desc)


def code(url, method="GET", data=None, headers=None):
    req = urllib.request.Request(url, data=data, method=method, headers=headers or {})
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            return response.status
    except urllib.error.HTTPError as exc:
        return exc.code


def get(url):
    return json.load(urllib.request.urlopen(url, timeout=30))


def post_file(filepath, extra):
    boundary = uuid.uuid4().hex
    with open(filepath, "rb") as file:
        audio = file.read()
    parts = []
    for key, value in extra.items():
        parts.append(
            f"--{boundary}\r\nContent-Disposition: form-data; "
            f'name="{key}"\r\n\r\n{value}\r\n'.encode())
    name = os.path.basename(filepath)
    parts.append(
        f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; "
        f"filename=\"{name}\"\r\nContent-Type: audio/wav\r\n\r\n".encode()
        + audio + b"\r\n")
    parts.append(f"--{boundary}--\r\n".encode())
    request = urllib.request.Request(
        BASE + "/api/analyze", data=b"".join(parts),
        headers={"Content-Type": f"multipart/form-data; boundary={boundary}"})
    try:
        return json.load(urllib.request.urlopen(request, timeout=60))
    except urllib.error.HTTPError as exc:
        return {"__status": exc.code, "detail": exc.read().decode()[:200]}


def wait_job(job, rounds):
    status = ""
    for _ in range(rounds):
        status = get(f"{BASE}/api/jobs/{job}").get("status", "")
        if status in ("done", "error"):
            return status
        time.sleep(2)
    return status


def main():
    print("[1] 服务与页面")
    check("首页 200", code(BASE + "/"), 200)
    health = get(BASE + "/api/health")
    check("健康检查识别为分弦 v1.5.0",
          (health.get("app"), health.get("status"), health.get("version")),
          ("fenxuan", "ok", "1.5.0"))
    check("app.js 200", code(BASE + "/app.js"), 200)
    request = urllib.request.Request(BASE + "/")
    with urllib.request.urlopen(request, timeout=10) as response:
        check("首页禁缓存头", response.headers.get("Cache-Control"), "no-cache")

    print("[2] 人声 / 伴奏")
    first = post_file(SONG, {"profile": "vocal", "output": "both"})
    vocal_job = first.get("job", "")
    check("两轨任务已创建", bool(vocal_job), True)
    check("两轨分离完成", wait_job(vocal_job, 90), "done")
    vocal = get(f"{BASE}/api/jobs/{vocal_job}")["result"]
    vocal_ok = (
        vocal["profile"] == "vocal"
        and {track["name"] for track in vocal["tracks"]} == {"人声", "伴奏"}
        and all(track["quality"]["passed"] for track in vocal["tracks"])
        and all(set(track["files"]) == {"mp3", "wav"} for track in vocal["tracks"])
    )
    check("两轨只交付清晰的人声与伴奏", vocal_ok, True)
    for track in vocal["tracks"]:
        for item in track["files"].values():
            check(f"{track['name']} 下载 200", code(BASE + item["url"]), 200)

    print("[3] 勾选后才打包")
    payload = json.dumps({"job": vocal_job, "tracks": [0, 1]}).encode()
    check("选中音轨打包 200", code(
        BASE + "/api/export-zip", "POST", payload,
        {"Content-Type": "application/json"}), 200)
    check("空选择明确拒绝", code(
        BASE + "/api/export-zip", "POST",
        json.dumps({"job": vocal_job, "tracks": []}).encode(),
        {"Content-Type": "application/json"}), 400)

    print("[4] 完整乐器分轨")
    second = post_file(SONG, {"profile": "stems6", "output": "wav"})
    stems_job = second.get("job", "")
    check("完整分轨任务已创建", bool(stems_job), True)
    check("完整分轨完成", wait_job(stems_job, 240), "done")
    stems = get(f"{BASE}/api/jobs/{stems_job}")["result"]
    allowed = {"人声", "鼓", "贝斯", "吉他", "钢琴", "其他器乐"}
    names = [track["name"] for track in stems["tracks"]]
    stems_ok = (
        stems["profile"] == "stems6" and bool(names)
        and len(names) == len(set(names)) and set(names) <= allowed
        and "伴奏" not in names
        and all(track["quality"]["passed"] for track in stems["tracks"])
        and all(set(track["files"]) == {"wav"} for track in stems["tracks"])
    )
    check("原曲直接得到一组清晰乐器轨", stems_ok, True)

    print("[5] 无效输入在计算前停止")
    check("旧中间档不再接受",
          post_file(SONG, {"profile": "stems4"}).get("__status"), 400)
    check("非法档位明确拒绝",
          post_file(SONG, {"profile": "bogus"}).get("__status"), 400)
    check("非法输出格式明确拒绝",
          post_file(SONG, {"profile": "vocal", "output": "flac"}).get("__status"), 400)
    check("不存在 job 404", code(BASE + "/api/jobs/deadbeef"), 404)

    print()
    if FAIL:
        print(f"ACCEPTANCE: FAILED（{len(FAIL)} 项）")
        return 1
    print("ACCEPTANCE: ALL PASS")
    return 0


if __name__ == "__main__":
    sys.exit(main())
