"""OpenAI-compatible TTS/ASR adapter bound to the shared LLM API."""

from __future__ import annotations

import asyncio
import base64
import logging
import os
import time
from typing import Any, Optional

import httpx
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

logger = logging.getLogger(__name__)


def _int_env(name: str, default: int) -> int:
    try:
        return max(1, int(os.getenv(name, str(default))))
    except (TypeError, ValueError):
        return default


meta_router = APIRouter()

LLM_BASE_URL = (os.getenv("LLM_BASE_URL", "https://api.openai.com/v1/") or "").strip().rstrip("/")
LLM_API_KEY = (os.getenv("LLM_API_KEY", "") or "").strip()

DEFAULT_TTS_MODEL_ID = "Qwen3-TTS-12Hz-1.7B-VoiceDesign-8bit"
DEFAULT_ASR_MODEL_ID = "Qwen3-ASR-0.6B-8bit"
DEFAULT_TTS_INSTRUCTIONS = (
    os.getenv("TTS_DEFAULT_INSTRUCTIONS", "A clear, natural voice speaking Mandarin Chinese.")
    or "A clear, natural voice speaking Mandarin Chinese."
).strip()

TTS_MODEL_ID = (os.getenv("TTS_MODEL_ID", DEFAULT_TTS_MODEL_ID) or DEFAULT_TTS_MODEL_ID).strip()
ASR_MODEL_ID = (os.getenv("ASR_MODEL_ID", DEFAULT_ASR_MODEL_ID) or DEFAULT_ASR_MODEL_ID).strip()

TTS_MAX_TEXT_CHARS = _int_env("TTS_ASR_MAX_TEXT_CHARS", 4096)
ASR_MAX_AUDIO_BYTES = _int_env("ASR_MAX_AUDIO_BYTES", 100 * 1024 * 1024)
TTS_TIMEOUT_SECONDS = _int_env("TTS_ASR_TTS_TIMEOUT_SECONDS", 180)
ASR_TIMEOUT_SECONDS = _int_env("TTS_ASR_ASR_TIMEOUT_SECONDS", 300)
HEALTHCHECK_TIMEOUT_SECONDS = _int_env("TTS_ASR_HEALTHCHECK_TIMEOUT_SECONDS", 5)
SPEECH_MAX_CONNECTIONS = _int_env("TTS_ASR_MAX_CONNECTIONS", 16)
SPEECH_MAX_KEEPALIVE_CONNECTIONS = _int_env("TTS_ASR_MAX_KEEPALIVE_CONNECTIONS", 8)

_httpx_client: Optional[httpx.AsyncClient] = None
_httpx_client_lock = asyncio.Lock()


def _read_uint16(data: bytes, offset: int) -> Optional[int]:
    if len(data) < offset + 2:
        return None
    return int.from_bytes(data[offset : offset + 2], "little", signed=False)


def _read_uint32(data: bytes, offset: int) -> Optional[int]:
    if len(data) < offset + 4:
        return None
    return int.from_bytes(data[offset : offset + 4], "little", signed=False)


def _parse_wav_duration_ms(audio_bytes: bytes) -> int:
    if len(audio_bytes) < 44 or audio_bytes[:4] != b"RIFF" or audio_bytes[8:12] != b"WAVE":
        return 0

    data_size = 0
    byte_rate = 0
    offset = 12

    while offset + 8 <= len(audio_bytes):
        chunk_id = audio_bytes[offset : offset + 4]
        chunk_size = _read_uint32(audio_bytes, offset + 4)
        if chunk_size is None:
            break
        chunk_start = offset + 8
        chunk_end = min(chunk_start + chunk_size, len(audio_bytes))

        if chunk_id == b"fmt ":
            audio_format = _read_uint16(audio_bytes, chunk_start)
            channels = _read_uint16(audio_bytes, chunk_start + 2)
            sample_rate = _read_uint32(audio_bytes, chunk_start + 4)
            bits_per_sample = _read_uint16(audio_bytes, chunk_start + 14)
            if audio_format == 1 and channels and sample_rate and bits_per_sample:
                byte_rate = int(sample_rate * channels * bits_per_sample // 8)

        if chunk_id == b"data":
            data_size = chunk_size
        offset = chunk_end + (chunk_end - chunk_start) % 2

    if data_size and byte_rate:
        return max(0, int(data_size * 1000 / byte_rate))
    return 0


def _duration_from_audio_bytes(audio_bytes: bytes) -> int:
    return _parse_wav_duration_ms(audio_bytes)


def _audio_bytes_to_base64(audio_bytes: bytes) -> str:
    return base64.b64encode(audio_bytes).decode("utf-8")


def _normalize_tts_text(text: str) -> str:
    value = (text or "").strip()
    if not value:
        raise HTTPException(status_code=400, detail="TTS 文本为空")
    if len(value) > TTS_MAX_TEXT_CHARS:
        raise HTTPException(status_code=400, detail=f"TTS 文本过长，超过限制 {TTS_MAX_TEXT_CHARS} 个字符")
    return value


def _normalize_output_format(output_format: str) -> str:
    value = (output_format or "wav").strip().lower()
    if value not in {"wav", "mp3"}:
        raise HTTPException(status_code=400, detail="不支持的 TTS 输出格式")
    return value


def _normalize_asr_language(language: Optional[str]) -> Optional[str]:
    if not language:
        return None
    value = str(language).strip().lower()
    if value in {"", "auto"}:
        return None
    mapping = {
        "zh-cn": "zh",
        "zh-hans": "zh",
        "en-us": "en",
        "ja-jp": "ja",
        "ko-kr": "ko",
    }
    return mapping.get(value, value.split("-")[0])


def _speech_headers() -> dict[str, str]:
    headers = {"Accept": "*/*"}
    if LLM_API_KEY:
        headers["Authorization"] = f"Bearer {LLM_API_KEY}"
        headers["X-API-Key"] = LLM_API_KEY
    return headers


def _raise_http_error(response: httpx.Response, operation: str) -> None:
    try:
        response.raise_for_status()
    except httpx.HTTPStatusError as exc:
        body = (exc.response.text or "").strip()[:1000]
        detail = f"{operation} 请求失败 HTTP {exc.response.status_code}"
        if body:
            detail = f"{detail}: {body}"
        raise HTTPException(status_code=exc.response.status_code, detail=detail) from exc
    except Exception as exc:
        raise HTTPException(status_code=502, detail=f"{operation} 请求失败: {exc}") from exc


def _tts_timeout() -> httpx.Timeout:
    return httpx.Timeout(TTS_TIMEOUT_SECONDS, connect=5.0)


def _asr_timeout() -> httpx.Timeout:
    return httpx.Timeout(ASR_TIMEOUT_SECONDS, connect=5.0)


def _extract_upstream_request_id(response: httpx.Response) -> str:
    for header_name in ("x-request-id", "request-id", "openai-request-id"):
        value = (response.headers.get(header_name) or "").strip()
        if value:
            return value
    return ""


async def _get_speech_client() -> httpx.AsyncClient:
    global _httpx_client

    if _httpx_client is None or getattr(_httpx_client, "is_closed", False):
        limits = httpx.Limits(
            max_connections=SPEECH_MAX_CONNECTIONS,
            max_keepalive_connections=max(1, SPEECH_MAX_KEEPALIVE_CONNECTIONS),
        )
        async with _httpx_client_lock:
            if _httpx_client is None or getattr(_httpx_client, "is_closed", False):
                _httpx_client = httpx.AsyncClient(
                    base_url=LLM_BASE_URL,
                    timeout=_tts_timeout(),
                    headers=_speech_headers(),
                    follow_redirects=True,
                    limits=limits,
                )
    return _httpx_client


async def close_speech_client() -> None:
    global _httpx_client
    if _httpx_client is not None and not getattr(_httpx_client, "is_closed", False):
        await _httpx_client.aclose()
    _httpx_client = None


async def _call_tts_api(text: str, instruct: str = "", speaker: str = "Vivian", output_format: str = "wav") -> dict[str, Any]:
    normalized_text = _normalize_tts_text(text)
    normalized_format = _normalize_output_format(output_format)
    client = await _get_speech_client()

    payload: dict[str, Any] = {
        "model": TTS_MODEL_ID,
        "input": normalized_text,
        "response_format": normalized_format,
        "voice": speaker or "Vivian",
    }
    payload["instructions"] = (instruct or "").strip() or DEFAULT_TTS_INSTRUCTIONS

    started_at = time.perf_counter()
    response = await client.post("audio/speech", json=payload, timeout=_tts_timeout(), headers=_speech_headers())
    elapsed_ms = int((time.perf_counter() - started_at) * 1000)
    _raise_http_error(response, "TTS")
    audio_bytes = response.content
    if not audio_bytes:
        raise HTTPException(status_code=502, detail="TTS API 返回音频为空")
    return {
        "audio_bytes": audio_bytes,
        "request_ms": elapsed_ms,
        "upstream_request_id": _extract_upstream_request_id(response),
    }


async def _call_asr_api(audio_bytes: bytes, language: Optional[str] = "zh-CN") -> dict[str, Any]:
    if not audio_bytes:
        raise HTTPException(status_code=400, detail="ASR 音频内容为空")
    if len(audio_bytes) > ASR_MAX_AUDIO_BYTES:
        raise HTTPException(status_code=400, detail=f"ASR 音频过大，超过限制 {ASR_MAX_AUDIO_BYTES} 字节")

    normalized_language = _normalize_asr_language(language)
    client = await _get_speech_client()
    files = {"file": ("audio.wav", audio_bytes, "audio/wav")}
    data = {"model": ASR_MODEL_ID}
    if normalized_language:
        data["language"] = normalized_language

    started_at = time.perf_counter()
    response = await client.post(
        "audio/transcriptions",
        files=files,
        data=data,
        timeout=_asr_timeout(),
        headers=_speech_headers(),
    )
    elapsed_ms = int((time.perf_counter() - started_at) * 1000)
    _raise_http_error(response, "ASR")
    try:
        result = response.json()
    except ValueError as exc:
        raise HTTPException(status_code=502, detail="ASR API 返回非 JSON 数据") from exc

    if not isinstance(result, dict):
        raise HTTPException(status_code=502, detail="ASR API 返回结构异常")

    text = str(result.get("text", "") or "").strip()
    if not text:
        raise HTTPException(status_code=422, detail="ASR API 返回结果为空")

    detected_language = result.get("language") or normalized_language or "auto"
    return {
        "text": text,
        "language": str(detected_language),
        "request_ms": elapsed_ms,
        "upstream_request_id": _extract_upstream_request_id(response),
    }


async def generate_tts_response(
    text: str,
    instruct: str = "",
    speaker: str = "Vivian",
    output_format: str = "wav",
) -> dict[str, Any]:
    result = await _call_tts_api(
        text=text,
        instruct=instruct or "",
        speaker=speaker or "Vivian",
        output_format=output_format or "wav",
    )
    audio_bytes = bytes(result["audio_bytes"])
    return {
        "audio_base64": _audio_bytes_to_base64(audio_bytes),
        "format": _normalize_output_format(output_format or "wav"),
        "duration_ms": _duration_from_audio_bytes(audio_bytes),
        "audio_bytes": len(audio_bytes),
        "text_chars": len(_normalize_tts_text(text)),
        "speaker": speaker or "Vivian",
        "model": TTS_MODEL_ID,
        "request_ms": int(result.get("request_ms", 0) or 0),
        "upstream_request_id": str(result.get("upstream_request_id", "") or ""),
    }


async def generate_asr_response(audio_bytes: bytes, language: Optional[str] = "zh-CN") -> dict[str, Any]:
    result = await _call_asr_api(bytes(audio_bytes or b""), language or "zh-CN")
    return {
        "text": str(result["text"]),
        "language": str(result["language"]),
        "audio_bytes": len(audio_bytes or b""),
        "model": ASR_MODEL_ID,
        "request_ms": int(result.get("request_ms", 0) or 0),
        "upstream_request_id": str(result.get("upstream_request_id", "") or ""),
    }


class TTSResponse(BaseModel):
    audio_base64: str = ""
    format: str = "wav"
    duration_ms: int = 0
    audio_bytes: int = 0
    text_chars: int = 0
    speaker: str = "Vivian"
    model: str = TTS_MODEL_ID
    request_ms: int = 0
    upstream_request_id: str = ""


class ASRResponse(BaseModel):
    text: str = ""
    language: Optional[str] = None
    audio_bytes: int = 0
    model: str = ASR_MODEL_ID
    request_ms: int = 0
    upstream_request_id: str = ""


class ModelStatus(BaseModel):
    llm_url: str
    tts_model: str
    asr_model: str
    status: dict[str, Any]


def _status_payload() -> dict[str, Any]:
    return {
        "llm_url": LLM_BASE_URL or "",
        "tts_model": TTS_MODEL_ID,
        "asr_model": ASR_MODEL_ID,
        "status": {
            "api_configured": bool(LLM_BASE_URL),
            "api_key_configured": bool(LLM_API_KEY),
            "tts_model": TTS_MODEL_ID,
            "asr_model": ASR_MODEL_ID,
            "tts_timeout_seconds": TTS_TIMEOUT_SECONDS,
            "asr_timeout_seconds": ASR_TIMEOUT_SECONDS,
            "healthcheck_timeout_seconds": HEALTHCHECK_TIMEOUT_SECONDS,
            "max_connections": SPEECH_MAX_CONNECTIONS,
            "keepalive_connections": max(1, SPEECH_MAX_KEEPALIVE_CONNECTIONS),
            "max_tts_text_chars": TTS_MAX_TEXT_CHARS,
            "max_asr_audio_bytes": ASR_MAX_AUDIO_BYTES,
        },
    }


@meta_router.get("/status", response_model=ModelStatus)
async def get_status():
    return _status_payload()


@meta_router.get("/config")
async def get_config():
    return _status_payload()


def register_tts_asr_routes(app) -> None:
    app.include_router(meta_router, prefix="/v1/tts-asr")
