import base64
import re

import httpx

import config

MAX_TTS_CHARS = 2000
COSYVOICE_URL = (
    "https://dashscope.aliyuncs.com/api/v1/services/audio/tts/SpeechSynthesizer"
)
QWEN_TTS_URL = (
    "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
)


def _clean_text_for_tts(text: str) -> str:
    text = re.sub(r"```[\s\S]*?```", "", text)
    text = re.sub(r"`([^`]+)`", r"\1", text)
    text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
    text = re.sub(r"[#*_~]", "", text)
    text = re.sub(r"\s+", " ", text).strip()
    if len(text) > MAX_TTS_CHARS:
        text = text[:MAX_TTS_CHARS]
    return text


class BailianTTS:
    def is_configured(self) -> bool:
        key = config.settings.dashscope_api_key
        return bool(key and key not in ("", "sk-your-dashscope-key"))

    async def synthesize(self, text: str, voice: str | None = None) -> tuple[str, bytes]:
        if not self.is_configured():
            raise RuntimeError("未配置阿里云百炼 API Key")
        cleaned = _clean_text_for_tts(text)
        if not cleaned:
            raise ValueError("没有可合成的文本内容")
        model = config.settings.tts_model
        voice_id = (voice or config.settings.tts_voice).strip()
        if not voice_id:
            raise ValueError("未指定音色")
        if model.startswith("qwen"):
            return await self._synthesize_qwen(cleaned, voice_id)
        return await self._synthesize_cosyvoice(cleaned, voice_id)

    async def _request_audio_bytes(
        self, client: httpx.AsyncClient, audio: dict
    ) -> tuple[str, bytes]:
        if audio.get("data"):
            return "mp3", base64.b64decode(audio["data"])
        url = audio.get("url")
        if not url:
            raise RuntimeError("TTS 响应中无音频数据")
        audio_resp = await client.get(url, timeout=60.0)
        audio_resp.raise_for_status()
        content_type = audio_resp.headers.get("content-type", "")
        fmt = "wav" if "wav" in content_type else "mp3"
        return fmt, audio_resp.content

    def _auth_headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {config.settings.dashscope_api_key}",
            "Content-Type": "application/json",
        }

    async def _synthesize_cosyvoice(self, text: str, voice: str) -> tuple[str, bytes]:
        payload = {
            "model": config.settings.tts_model,
            "input": {
                "text": text,
                "voice": voice,
                "format": "mp3",
                "sample_rate": 22050,
            },
        }
        async with httpx.AsyncClient(timeout=90.0) as client:
            resp = await client.post(
                COSYVOICE_URL, json=payload, headers=self._auth_headers()
            )
            data = resp.json()
            if resp.status_code != 200 or data.get("code"):
                msg = data.get("message") or resp.text
                raise RuntimeError(f"语音合成失败: {msg}")
            audio = data.get("output", {}).get("audio", {})
            return await self._request_audio_bytes(client, audio)

    async def _synthesize_qwen(self, text: str, voice: str) -> tuple[str, bytes]:
        payload = {
            "model": config.settings.tts_model,
            "input": {
                "text": text,
                "voice": voice,
                "language_type": "Chinese",
            },
        }
        async with httpx.AsyncClient(timeout=90.0) as client:
            resp = await client.post(
                QWEN_TTS_URL, json=payload, headers=self._auth_headers()
            )
            data = resp.json()
            if resp.status_code != 200 or data.get("code"):
                msg = data.get("message") or resp.text
                raise RuntimeError(f"语音合成失败: {msg}")
            audio = data.get("output", {}).get("audio", {})
            return await self._request_audio_bytes(client, audio)

    async def test_connection(self) -> str:
        _, audio_bytes = await self.synthesize("语音合成测试成功")
        return f"合成成功，音频大小 {len(audio_bytes)} 字节"
