"""Qwen3-TTS HTTP server — OpenAI-compatible /v1/audio/speech endpoint."""
import argparse, io, os, sys
from flask import Flask, request, send_file, jsonify

app = Flask(__name__)
MODEL_DIR = "/models/qwen3-tts"
engine = None

SPEAKERS = ["Vivian","Serena","uncle_fu","Dylan","Eric","Ryan","Aiden","ono_anna","Sohee"]
LANGUAGES = ["english","chinese","japanese","korean","german","spanish","french","russian","italian","portuguese"]

def init_engine():
    global engine
    # TTSEngine uses Path.cwd() as project_root and builds relative paths from it.
    # model_dir must be a subdirectory of cwd for relative_to() to work.
    parent = os.path.dirname(os.path.abspath(MODEL_DIR))
    basename = os.path.basename(MODEL_DIR)
    os.chdir(parent)
    from qwen3_tts_gguf.inference import TTSEngine
    engine = TTSEngine(model_dir=basename, onnx_provider="CPUExecutionProvider")
    if not engine.ready:
        print(f"[server] WARNING: engine created but not ready", flush=True)

@app.route("/health")
def health():
    ready = engine is not None and engine.ready
    return jsonify({"status": "ok" if ready else "loading", "speakers": SPEAKERS, "languages": LANGUAGES})

@app.route("/v1/audio/speech", methods=["POST"])
def speech():
    if engine is None or not engine.ready:
        return jsonify({"error": "engine not ready"}), 503
    data = request.get_json(force=True)
    text = data.get("input", "")
    if not text:
        return jsonify({"error": "missing input"}), 400
    speaker = data.get("voice", "Vivian")
    if speaker not in SPEAKERS:
        speaker = "Vivian"
    language = data.get("language", "english")
    if language not in LANGUAGES:
        language = "english"
    instruct = data.get("instruct", "")
    try:
        import numpy as np, wave as _wave
        from qwen3_tts_gguf.inference import TTSConfig
        stream = engine.create_stream(n_ctx=2048)
        if stream is None:
            return jsonify({"error": "failed to create stream"}), 500
        cfg = TTSConfig()
        result = stream.custom(text=text, speaker=speaker, language=language,
                               instruct=instruct, config=cfg)
        stream.join()
        if result is None:
            return jsonify({"error": "synthesis returned None"}), 500
        audio = result.audio if hasattr(result, 'audio') and result.audio is not None else None
        if audio is None:
            tmp = os.path.join("/tmp", "tts_out.wav")
            result.save(tmp)
            import soundfile as sf
            audio, _ = sf.read(tmp, dtype='float32')
            os.remove(tmp)
        buf = io.BytesIO()
        with _wave.open(buf, "wb") as wf:
            wf.setnchannels(1)
            wf.setsampwidth(2)
            wf.setframerate(24000)
            pcm = (np.clip(audio, -1.0, 1.0) * 32767).astype(np.int16).tobytes()
            wf.writeframes(pcm)
        buf.seek(0)
        return send_file(buf, mimetype="audio/wav", download_name="speech.wav")
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--host", default="0.0.0.0")
    p.add_argument("--port", type=int, default=8072)
    p.add_argument("--model-dir", default=MODEL_DIR)
    a = p.parse_args()
    MODEL_DIR = a.model_dir
    init_engine()
    app.run(host=a.host, port=a.port)
